14.2.1 Working with request-response
Creating an RSocket server in Spring is as simple as creating a controller class, much the same as you would for a web application or REST service. The following controller is an example of an RSocket service that handles greetings from the client and responds with another greeting.
Listing 14.2 A simple RSocket request-response server
package rsocket;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
@Controller
@Slf4j
public class GreetingController{
@MessageMapping("greeting")
public Mono<String> handleGreeting(Mono<String> greetingMono) {
return greetingMono
.doOnNext(greeting ->
log.info("Received a greeting: " + greeting))
.map(greeting -> "Hello back to you!");
}
}As you can see, the key difference between a web controller and an RSocket controller is that instead of handling HTTP requests for a given path (using @GetMapping or @PostMapping), an RSocket controller handles incoming messages on a given route with the @MessageMapping annotation. In this example, the handleGreeting() method is invoked when a request is sent from the client to the route named "greeting".
The handleGreeting() method receives the message payload from the client in a Mono<String> parameter. In this case, the greeting is simple enough that a String is sufficient, but the incoming payload could be a more complex type, if needed. Upon receiving the Mono<String>, the method simply logs the fact that it received the greeting and then uses the map() function on the Mono to create a new Mono<String> to carry the response that is returned to the client.
Although RSocket controllers aren’t handling HTTP requests for a path, the route name can be made to have a pathlike appearance, including variable placeholders that can be passed into the handler method. For example, consider the following twist on the handleGreeting() method:
@MessageMapping("greeting/{name}")
public Mono<String> handleGreeting(
@DestinationVariable("name") String name,
Mono<String> greetingMono) {
return greetingMono
.doOnNext(greeting ->
log.info("Received a greeting from " + name + " : " + greeting))
.map(greeting -> "Hello to you, too, " + name);
}In this case, the route specified in @MessageMapping contains a placeholder variable named "name". It is denoted by curly braces, the same way as path variables in a Spring MVC controller. Likewise, the method accepts a String parameter annotated with @DestinationVariable that references the placeholder variable. Just like Spring MVC’s @PathVariable annotation, @DestinationVariable is used to extract the value specified in the route’s placeholder and pass it into the handler method. Once inside this new version of handleGreeting(), the name specified in the route will be used to return a more personalized greeting to the client.
There’s one more thing you must remember to do when creating an RSocket server: specify the port to listen on. By default, RSocket services are TCP-based and are their own server listening on a specific port. The spring.rsocket.server.port configuration property sets the port for the RSocket server, as shown here:
spring:
rsocket:
server:
port: 7000The spring.rsocket.server.port property serves two purposes: enabling a server and specifying which port the server should listen on. If it is not set, then Spring will assume that your application will be acting as a client only, and no server port will be listening. In this case, we’re starting a server, so setting the spring.rsocket.server.port property as shown in the previous code will start a server listening on port 7000.
Now let’s turn our attention to the RSocket client. In Spring, RSocket clients are implemented using an RSocketRequester. Spring Boot autoconfiguration for RSocket will automatically create a bean of type RSocketRequester.Builder in the Spring application context. You can inject that builder bean into any other bean you need to create an instance of RSocketRequester.
For example, here’s the start of an ApplicationRunner bean that is injected with an RSocketRequester.Builder:
package rsocket;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.rsocket.RSocketRequester;
@Configuration
@Slf4j
public class RSocketClientConfiguration {
@Bean
public ApplicationRunner sender(RSocketRequester.Builder requesterBuilder) {
return args -> {
RSocketRequester tcp = requesterBuilder.tcp("localhost", 7000);
// ... send messages with RSocketRequester ...
};
}
}In this case, the builder is used to create an RSocketRequester that listens on localhost, port 7000. The resulting RSocketRequester can then be used to send messages to the server.
In a request-response model, the request will need to (at least) specify the route and the data payload. As you’ll recall, our server’s controller is handling requests for the route named "greeting" and expects a String input. It also returns a String output. The following complete listing of client code shows how to send a greeting to the server and handle the response.
Listing 14.3 Sending a request from a client
RSocketRequester tcp = requesterBuilder.tcp("localhost", 7000);
// ... send messages with RSocketRequester ...
tcp
.route("greeting")
.data("Hello RSocket!")
.retrieveMono(String.class)
.subscribe(response -> log.info("Got a response: " + response));This sends a greeting of "Hello RSocket!" to the server on the "greeting" route. Notice that it also expects a Mono<String> in return, as specified in the call to retrieveMono(). The subscribe() method subscribes to the returned Mono and handles its payload by logging the value.
Now let’s say you want to send a greeting to the other route that accepts a variable value in its route. The client-side code works pretty much the same, except that you include the variable placeholder in the value given to route() along with the value it should contain as follows:
String who = "Craig";
tcp
.route("greeting/{name}", who)
.data("Hello RSocket!")
.retrieveMono(String.class)
.subscribe(response -> log.info("Got a response: " + response));Here, the message will be sent to the route named "greeting/Craig", which will be handled by the controller handler method whose @MessageMapping specified the route "greeting/{name}". Although you could also hardcode the name in the route or use String concatenation to create the route name, using a placeholder in the client makes it really easy to drop in a value without the messiness of String concatenation.
Although the request-response model is probably the easiest of RSocket’s communication models to wrap your head around, it’s just the beginning. Let’s see how to handle requests that could potentially return several responses with the requeststream model.
