Annotated Responders

RSocket responders can be implemented as @MessageMapping and @ConnectMapping methods. @MessageMapping methods handle individual requests while @ConnectMapping methods handle connection-level events (setup and metadata push). Annotated responders are supported symmetrically, for responding from the server side and for responding from the client side.

Server Responders

To use annotated responders on the server side, add RSocketMessageHandler to your Spring configuration to detect @Controller beans with @MessageMapping and @ConnectMapping methods:

Java
@Configuration
static class ServerConfig {

	@Bean
	public RSocketMessageHandler rsocketMessageHandler() {
		RSocketMessageHandler handler = new RSocketMessageHandler();
		handler.routeMatcher(new PathPatternRouteMatcher());
		return handler;
	}
}
Kotlin
@Configuration
class ServerConfig {

	@Bean
	fun rsocketMessageHandler() = RSocketMessageHandler().apply {
		routeMatcher = PathPatternRouteMatcher()
	}
}

Then start an RSocket server through the Java RSocket API and plug the RSocketMessageHandler for the responder as follows:

Java
ApplicationContext context = ... ;
RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class);

CloseableChannel server =
	RSocketFactory.receive()
		.acceptor(handler.responder())
		.transport(TcpServerTransport.create("localhost", 7000))
		.start()
		.block();
Kotlin
import org.springframework.beans.factory.getBean

val context: ApplicationContext = ...
val handler = context.getBean<RSocketMessageHandler>()

val server = RSocketFactory.receive()
		.acceptor(handler.responder())
		.transport(TcpServerTransport.create("localhost", 7000))
		.start().awaitFirst()

RSocketMessageHandler supports composite and routing metadata by default. You can set its rsocket-metadata-extractor if you need to switch to a different mime type or register additional metadata mime types.

You’ll need to set the Encoder and Decoder instances required for metadata and data formats to support. You’ll likely need the spring-web module for codec implementations.

By default SimpleRouteMatcher is used for matching routes via AntPathMatcher. We recommend plugging in the PathPatternRouteMatcher from spring-web for efficient route matching. RSocket routes can be hierarchical but are not URL paths. Both route matchers are configured to use "." as separator by default and there is no URL decoding as with HTTP URLs.

RSocketMessageHandler can be configured via RSocketStrategies which may be useful if you need to share configuration between a client and a server in the same process:

Java
@Configuration
static class ServerConfig {

	@Bean
	public RSocketMessageHandler rsocketMessageHandler() {
		RSocketMessageHandler handler = new RSocketMessageHandler();
		handler.setRSocketStrategies(rsocketStrategies());
		return handler;
	}

	@Bean
	public RSocketStrategies rsocketStrategies() {
		return RSocketStrategies.builder()
			.encoders(encoders -> encoders.add(new Jackson2CborEncoder))
			.decoders(decoders -> decoders.add(new Jackson2CborDecoder))
			.routeMatcher(new PathPatternRouteMatcher())
			.build();
	}
}
Kotlin
@Configuration
class ServerConfig {

	@Bean
	fun rsocketMessageHandler() = RSocketMessageHandler().apply {
		rSocketStrategies = rsocketStrategies()
	}

	@Bean
	fun rsocketStrategies() = RSocketStrategies.builder()
			.encoders { it.add(Jackson2CborEncoder()) }
			.decoders { it.add(Jackson2CborDecoder()) }
			.routeMatcher(PathPatternRouteMatcher())
			.build()
}

Client Responders

Annotated responders on the client side need to be configured in the RSocketRequester.Builder. For details, see rsocket-requester-client-responder.

@MessageMapping

Once server or client responder configuration is in place, @MessageMapping methods can be used as follows:

Java
@Controller
public class RadarsController {

	@MessageMapping("locate.radars.within")
	public Flux<AirportLocation> radars(MapRequest request) {
		// ...
	}
}
Kotlin
@Controller
class RadarsController {

	@MessageMapping("locate.radars.within")
	fun radars(request: MapRequest): Flow<AirportLocation> {
		// ...
	}
}

The above @MessageMapping method responds to a Request-Stream interaction having the route "locate.radars.within". It supports a flexible method signature with the option to use the following method arguments:

Method Argument Description

@Payload

The payload of the request. This can be a concrete value of asynchronous types like Mono or Flux.

Note: Use of the annotation is optional. A method argument that is not a simple type and is not any of the other supported arguments, is assumed to be the expected payload.

RSocketRequester

Requester for making requests to the remote end.

@DestinationVariable

Value extracted from the route based on variables in the mapping pattern, e.g. @MessageMapping("find.radar.{id}").

@Header

Metadata value registered for extraction as described in rsocket-metadata-extractor.

@Headers Map<String, Object>

All metadata values registered for extraction as described in rsocket-metadata-extractor.

The return value is expected to be one or more Objects to be serialized as response payloads. That can be asynchronous types like Mono or Flux, a concrete value, or either void or a no-value asynchronous type such as Mono<Void>.

The RSocket interaction type that an @MessageMapping method supports is determined from the cardinality of the input (i.e. @Payload argument) and of the output, where cardinality means the following:

Cardinality Description

1

Either an explicit value, or a single-value asynchronous type such as Mono<T>.

Many

A multi-value asynchronous type such as Flux<T>.

0

For input this means the method does not have an @Payload argument.

For output this is void or a no-value asynchronous type such as Mono<Void>.

The table below shows all input and output cardinality combinations and the corresponding interaction type(s):

Input Cardinality Output Cardinality Interaction Types

0, 1

0

Fire-and-Forget, Request-Response

0, 1

1

Request-Response

0, 1

Many

Request-Stream

Many

0, 1, Many

Request-Channel

@ConnectMapping

@ConnectMapping handles the SETUP frame at the start of an RSocket connection, and any subsequent metadata push notifications through the METADATA_PUSH frame, i.e. metadataPush(Payload) in io.rsocket.RSocket.

@ConnectMapping methods support the same arguments as rsocket-annot-messagemapping but based on metadata and data from the SETUP and METADATA_PUSH frames. @ConnectMapping can have a pattern to narrow handling to specific connections that have a route in the metadata, or if no patterns are declared then all connections match.

@ConnectMapping methods cannot return data and must be declared with void or Mono<Void> as the return value. If handling returns an error for a new connection then the connection is rejected. Handling must not be held up to make requests to the RSocketRequester for the connection. See rsocket-requester-server for details.