parent
							
								
									2572cd0503
								
							
						
					
					
						commit
						88bb2aabbf
					
				|  | @ -0,0 +1,628 @@ | |||
| [[rsocket]] | ||||
| = RSocket | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-overview]] | ||||
| == Overview | ||||
| 
 | ||||
| RSocket is an application protocol for multiplexed, duplex communication over TCP, | ||||
| WebSocket, and other byte stream transports, using one of the following interaction | ||||
| models: | ||||
| 
 | ||||
| * `Request-Response` -- send one message and receive one back. | ||||
| * `Request-Stream` -- send one message and receive a stream of messages back. | ||||
| * `Channel` -- send streams of messages in both directions. | ||||
| * `Fire-and-Forget` -- send a one-way message. | ||||
| 
 | ||||
| Once the initial connection is made, the "client" vs "server" distinction is lost as | ||||
| both sides become symmetrical and each side can initiate one of the above interactions. | ||||
| This is why in the protocol calls the participating sides "requester" and "responder" | ||||
| while the above interactions are called "requests" or "streams". | ||||
| 
 | ||||
| Below are key features built into the protocol: | ||||
| 
 | ||||
| * https://www.reactive-streams.org/[Reactive Streams] semantics across network boundary -- | ||||
| for streaming requests such as `Request-Stream` and `Channel`, back pressure signals | ||||
| travel between requester and responder, allowing a requester to slow down a responder at | ||||
| the source, hence reducing reliance on network layer congestion control, and the need | ||||
| for buffering at the network or any level. | ||||
| * Request throttling -- this feature is named "leasing" after the `LEASE` frame that | ||||
| can be sent from each end to limit the total number of requests allowed by other end | ||||
| for a given time. Leases are renewed periodically. | ||||
| * Session resumption -- this is designed for loss of connectivity and requires some state | ||||
| to be maintained. The state management is transparent for applications, and works well | ||||
| in combination with back pressure which can stop a producer when possible and reduce | ||||
| the amount of state required. | ||||
| * Fragmentation and re-assembly of large messages. | ||||
| * Keepalive (heartbeats). | ||||
| 
 | ||||
| RSocket has https://github.com/rsocket[implementations] in multiple languages. The | ||||
| https://github.com/rsocket/rsocket-java[Java library] is built on | ||||
| https://projectreactor.io/[Project Reactor], and Reactor Netty for the transport. | ||||
| That means signals from Reactive Streams Publishers in your application propagate | ||||
| transparently through RSocket across the network. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-protocol]] | ||||
| === The Protocol | ||||
| 
 | ||||
| One of the benefits of RSocket is that it has a well defined behavior on the wire and an | ||||
| easy to read http://rsocket.io/docs/Protocol[specification] along with some protocol | ||||
| https://github.com/rsocket/rsocket/tree/master/Extensions[extensions]. Therefore it is | ||||
| a good idea to read the spec, independent of language implementations and higher level | ||||
| framework APIs. This section provides a succinct overview to establish some context. | ||||
| 
 | ||||
| **Connecting** | ||||
| 
 | ||||
| Initially a client connects to a server via some low level streaming transport such | ||||
| as TCP or WebSocket and sends a `SETUP` frame to the server to define parameters for the | ||||
| connection. | ||||
| 
 | ||||
| The server may reject the `SETUP` frame, but generally after it is sent (for the client) | ||||
| and received (for the server), both sides can begin to make requests, unless `SETUP` | ||||
| indicates use of leasing semantics to limit the number of requests, in which case | ||||
| both sides must wait for a `LEASE` frame from the other end to permit making requests. | ||||
| 
 | ||||
| **Making Requests** | ||||
| 
 | ||||
| Once a connection is established, both sides may initiate a request through one of the | ||||
| frames `REQUEST_RESPONSE`, `REQUEST_STREAM`, `REQUEST_CHANNEL`, or `REQUEST_FNF`. Each of | ||||
| those frames carries one message from the requester to the responder. | ||||
| 
 | ||||
| The responder may then return `PAYLOAD` frames with response messages, and in the case | ||||
| of `REQUEST_CHANNEL` the requester may also send `PAYLOAD` frames with more request | ||||
| messages. | ||||
| 
 | ||||
| When a request involves a stream of messages such as as `Request-Stream` and `Channel`, | ||||
| the responder must respect demand signals from the requester. Demand is expressed as a | ||||
| number of messages. Initial demand is specified in `REQUEST_STREAM` and | ||||
| `REQUEST_CHANNEL` frames. Subsequent demand is signaled via `REQUEST_N` frames. | ||||
| 
 | ||||
| Each side may also send metadata notifications, via the `METADATA_PUSH` frame, that do not | ||||
| pertain to any individual request but rather to the connection as a whole. | ||||
| 
 | ||||
| **Message Format** | ||||
| 
 | ||||
| RSocket messages contain data and metadata. Metadata can be used to send a route, a | ||||
| security token, etc. Data and metadata can be formatted differently. Mime types for each | ||||
| are declared in the `SETUP` frame and apply to all requests on a given connection. | ||||
| 
 | ||||
| While all messages can have metadata, typically metadata such as a route are per-request | ||||
| and therefore only included in the first message on a request, i.e. with one of the frames | ||||
| `REQUEST_RESPONSE`, `REQUEST_STREAM`, `REQUEST_CHANNEL`, or `REQUEST_FNF`. | ||||
| 
 | ||||
| Protocol extensions define common metadata formats for use in applications: | ||||
| 
 | ||||
| * https://github.com/rsocket/rsocket/blob/master/Extensions/CompositeMetadata.md[Composite Metadata] | ||||
| -- multiple, independently formatted metadata entries. | ||||
| * https://github.com/rsocket/rsocket/blob/master/Routing.md[Routing] -- the route for a | ||||
| request. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-java]] | ||||
| === Java Implementation | ||||
| 
 | ||||
| The https://github.com/rsocket/rsocket-java[Java implementation] for RSocket is built on | ||||
| https://projectreactor.io/[Project Reactor]. The transports for  TCP and WebSocket are | ||||
| built on https://github.com/reactor/reactor-netty[Reactor Netty]. As a Reactive Streams | ||||
| library, Reactor simplifies the job of implementing the protocol. For applications it is | ||||
| a natural fit to use `Flux` and `Mono` with declarative operators and transparent back | ||||
| pressure support. | ||||
| 
 | ||||
| The API in RSocket Java is intentionally minimal and basic. It focuses on protocol | ||||
| features and leaves the application programming model (e.g. RPC codegen vs other) as a | ||||
| higher level, independent concern. | ||||
| 
 | ||||
| The main contract | ||||
| https://github.com/rsocket/rsocket-java/blob/master/rsocket-core/src/main/java/io/rsocket/RSocket.java[io.rsocket.RSocket] | ||||
| models the four request interaction types with `Mono` representing a promise for a | ||||
| single message, `Flux` a stream of messages, and `io.rsocket.Payload` the actual | ||||
| message with access to data and metadata as byte buffers. The `RSocket` contract is used | ||||
| symmetrically. For requesting, the application is given an `RSocket` to perform | ||||
| requests with. For responding, the application implements `RSocket` to handle requests. | ||||
| 
 | ||||
| This is not meant to be a thorough introduction. For the most part, Spring applications | ||||
| will not have to use its API directly. However it may be important to see or experiment | ||||
| with RSocket independent of Spring. The RSocket Java repository contains a number of | ||||
| https://github.com/rsocket/rsocket-java/tree/develop/rsocket-examples[sample apps] that | ||||
| demonstrate its API and protocol features. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-spring]] | ||||
| === Spring Support | ||||
| 
 | ||||
| The `spring-messaging` module contains the following: | ||||
| 
 | ||||
| * <<rsocket-requester>> -- fluent API to make requests through an `io.rsocket.RSocket` | ||||
| with data and metadata encoding/decoding. | ||||
| * <<rsocket-annot-responders>> -- `@MessageMapping` annotated handler methods for responding. | ||||
| 
 | ||||
| The `spring-web` module contains `Encoder` and `Decoder` implementations such as Jackson | ||||
| CBOR/JSON, and Protobuf that RSocket applications will likely need. It also contains the | ||||
| `PathPatternParser` that can be plugged in for efficient route matching. | ||||
| 
 | ||||
| Spring Security... | ||||
| 
 | ||||
| Spring Boot... | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-requester]] | ||||
| == RSocketRequester | ||||
| 
 | ||||
| `RSocketRequester` provides a fluent API to perform RSocket requests, accepting and | ||||
| returning objects for data and metadata instead of low level data buffers. It can be used | ||||
| symmetrically, to make requests from clients and to make requests from servers. | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-requester-client]] | ||||
| === Client Requester | ||||
| 
 | ||||
| To obtain an `RSocketRequester` on the client side requires connecting to a server along with | ||||
| preparing and sending the initial RSocket `SETUP` frame. `RSocketRequester` provides a | ||||
| builder for that. Internally uses RSocket Java's `RSocketFactory`. | ||||
| 
 | ||||
| This is the most basic way to connect with default settings: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	Mono<RSocketRequester> mono = RSocketRequester.builder() | ||||
| 		.connectTcp("localhost", 7000); | ||||
| 
 | ||||
| 	Mono<RSocketRequester> requesterMono = RSocketRequester.builder() | ||||
| 		.connectWebSocket(URI.create("http://example.org:8080/rsocket")); | ||||
| ---- | ||||
| 
 | ||||
| The above is deferred. To actually connect and use the requester: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	// Connect asynchronously | ||||
| 	RSocketRequester.builder().connectTcp("localhost", 7000) | ||||
| 		.subscribe(requester -> { | ||||
| 			// ... | ||||
| 		}); | ||||
| 
 | ||||
| 	// Or block | ||||
| 	RSocketRequester requester = RSocketRequester.builder() | ||||
| 		.connectTcp("localhost", 7000) | ||||
| 		.block(Duration.ofSeconds(5)); | ||||
| ---- | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-requester-client-setup]] | ||||
| ==== Connection Setup | ||||
| 
 | ||||
| `RSocketRequester.Builder` provides the following to customize the initial `SETUP` frame: | ||||
| 
 | ||||
| * `dataMimeType(MimeType)` -- set the mime type for data on the connection. | ||||
| * `metadataMimeType(MimeType)` -- set the mime type for metadata on the connection. | ||||
| * `setupData(Object)` -- data to include in the `SETUP`. | ||||
| * `setupRoute(String, Object...)` -- route in the metadata to include in the `SETUP`. | ||||
| * `setupMetadata(Object, MimeType)` -- other metadata to include in the `SETUP`. | ||||
| 
 | ||||
| For data, the default mime type is derived from the first configured `Decoder`. For | ||||
| metadata, the default mime type is composite metadata which allows multiple metadata | ||||
| value and mime type pairs per request. Typically both don't need to be changed. | ||||
| 
 | ||||
| Data and metadata in the `SETUP` frame is optional. On the server side, | ||||
| a `@ConnectMapping` methods can be used to handle the start of a connection and the | ||||
| content of the `SETUP` frame. Metadata may include connection level security info. | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-requester-client-strategies]] | ||||
| ==== Strategies | ||||
| 
 | ||||
| `RSocketRequester.Builder` accepts `RSocketStrategies` to configure the requester. | ||||
| You'll need to use this to provide encoders and decoders for (de)-serialization of data and | ||||
| metadata values. By default only the basic codecs from `spring-core` for `String`, | ||||
| `byte[]`, and `ByteBuffer` are registered. Adding `spring-web` provides access to more that | ||||
| can be registered as follows: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	RSocketStrategies strategies = RSocketStrategies.builder() | ||||
| 		.encoders(encoders -> encoders.add(new Jackson2CborEncoder)) | ||||
| 		.decoder(decoders -> decoders.add(new Jackson2CborDecoder)) | ||||
| 		.build(); | ||||
| 
 | ||||
| 	RSocketRequester.builder() | ||||
| 		.rsocketStrategies(strategies) | ||||
| 		.connectTcp("localhost", 7000); | ||||
| ---- | ||||
| 
 | ||||
| `RSocketStrategies` is designed for re-use. In some scenarios, e.g. client and server in | ||||
| the same application, it may be preferable to declare it in Spring configuration. | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-requester-client-responder]] | ||||
| ==== Client Responders | ||||
| 
 | ||||
| `RSocketRequester.Builder` can be used to configure responders to requests from the | ||||
| server. | ||||
| 
 | ||||
| You can use annotated handlers for client-side responding based on the same | ||||
| infrastructure that's used on a server, but registered programmatically as follows: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	RSocketStrategies strategies = RSocketStrategies.builder() | ||||
| 		.routeMatcher(new PathPatternRouteMatcher())  <1> | ||||
| 		.build(); | ||||
| 
 | ||||
| 	ClientHandler handler = new ClientHandler(); <2> | ||||
| 
 | ||||
| 	RSocketRequester.builder() | ||||
| 		.rsocketFactory(RSocketMessageHandler.clientResponder(strategies, handler)) <3> | ||||
| 		.connectTcp("localhost", 7000); | ||||
| ---- | ||||
| 
 | ||||
| <1> Use `PathPatternRouteMatcher`, if `spring-web` is present, for efficient | ||||
|     route matching. | ||||
| <2> Create responder that contains `@MessageMaping` or `@ConnectMapping` methods. | ||||
| <3> Use static factory method in `RSocketMessageHandler` to register one or more responders. | ||||
| 
 | ||||
| Note the above is only a shortcut designed for programmatic registration of client | ||||
| responders. For alternative scenarios, where client responders are in Spring configuration, | ||||
| you can still declare `RSocketMessageHandler` as a Spring bean and then apply as follows: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	ApplicationContext context = ... ; | ||||
| 	RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class); | ||||
| 
 | ||||
| 	RSocketRequester.builder() | ||||
| 		.rsocketFactory(factory -> factory.acceptor(handler.responder())) | ||||
| 		.connectTcp("localhost", 7000); | ||||
| ---- | ||||
| 
 | ||||
| For the above you may also need to use `setHandlerPredicate` in `RSocketMessageHandler` to | ||||
| switch to a different strategy for detecting client responders, e.g. based on a custom | ||||
| annotation such as `@RSocketClientResponder` vs the default `@Controller`. This | ||||
| is necessary in scenarios with client and server, or multiple clients in the same | ||||
| application. | ||||
| 
 | ||||
| See also <<rsocket-annot-responders>>, for more on the programming model. | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-requester-client-advanced]] | ||||
| ==== Advanced | ||||
| 
 | ||||
| `RSocketRequesterBuilder` provides a callback to expose the underlying | ||||
| `ClientRSocketFactory` from RSocket Java for further configuration options for | ||||
| keepalive intervals, session resumption, interceptors, and more. You can configure options | ||||
| at that level as follows: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	RSocketRequester.builder() | ||||
| 		.rsocketFactory(factory -> { | ||||
| 			// ... | ||||
| 		}) | ||||
| 		.connectTcp("localhost", 7000); | ||||
| ---- | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-requester-server]] | ||||
| === Server Requester | ||||
| 
 | ||||
| To make requests from a server to connected clients is a matter of obtaining the | ||||
| requester for the connected client from the server. | ||||
| 
 | ||||
| In <<rsocket-annot-responders>>, `@ConnectMapping` and `@MessageMapping` methods support an | ||||
| `RSocketRequester` argument. Use it to access the requester for the connection. Keep in | ||||
| mind that `@ConnectMapping` methods are essentially handlers of the `SETUP` frame which | ||||
| must be handled before requests can begin. Therefore, requests at the very start must be | ||||
| decoupled from handling. For example: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	@ConnectMapping | ||||
| 	Mono<Void> handle(RSocketRequester requester) { | ||||
| 		requester.route("status").data("5") | ||||
| 			.retrieveFlux(StatusReport.class) | ||||
| 			.subscribe(bar -> { <1> | ||||
| 				// ... | ||||
| 			}); | ||||
| 		return ... <2> | ||||
| 	} | ||||
| ---- | ||||
| 
 | ||||
| <1> Start the request asynchronously, independent from handling. | ||||
| <2> Perform handling and return completion `Mono<Void>`. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-requester-requests]] | ||||
| === Requests | ||||
| 
 | ||||
| Once you have a <<rsocket-requester-client,client>> or | ||||
| <<rsocket-requester-server,server>> requester, you can make requests as follows: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	ViewBox box = ... ; | ||||
| 
 | ||||
| 	Flux<AirportLocation> locations = | ||||
| 		requester.route("locate.radars.within") <1> | ||||
| 			.data(viewBox) <2> | ||||
| 			.retrieveFlux(AirportLocation.class); <3> | ||||
| 
 | ||||
| ---- | ||||
| 
 | ||||
| <1> Specify a route to include in the metadata of the request message. | ||||
| <2> Provide data for the request message. | ||||
| <3> Declare the expected response. | ||||
| 
 | ||||
| The interaction type is determined implicitly from the cardinality of the input and | ||||
| output. The above example is a `Request-Stream` because one value is sent and a stream | ||||
| of values is received. For the most part you don't need to think about this as long as the | ||||
| choice of input and output matches an RSocket interaction type and the types of input and | ||||
| output expected by the responder. The only example of an invalid combination is many-to-one. | ||||
| 
 | ||||
| The `data(Object)` method also accepts any Reactive Streams `Publisher`, including | ||||
| `Flux` and `Mono`, as well as any other producer of value(s) that is registered in the | ||||
| `ReactiveAdapterRegistry`. For a multi-value `Publisher` such as `Flux` which produces the | ||||
| same types of values, consider using one of the overloaded `data` methods to avoid having | ||||
| type checks and `Encoder` lookup on every element: | ||||
| 
 | ||||
| [source,java,indent=0] | ||||
| [subs="verbatim,quotes"] | ||||
| ---- | ||||
| data(Object producer, Class<?> elementClass); | ||||
| data(Object producer, ParameterizedTypeReference<?> elementTypeRef); | ||||
| ---- | ||||
| 
 | ||||
| The `data(Object)` step is optional. Skip it for requests that don't send data: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 
 | ||||
| 	Mono<AirportLocation> location = | ||||
| 		requester.route("find.radar.EWR")) | ||||
| 			.retrieveMono(AirportLocation.class); | ||||
| ---- | ||||
| 
 | ||||
| Extra metadata values can be added if using composite metadata (the default) and if the | ||||
| values are supported by a registered `Encoder`. For example: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	String securityToken = ... ; | ||||
| 	ViewBox box = ... ; | ||||
| 
 | ||||
| 	Flux<AirportLocation> locations = | ||||
| 		requester.route("locate.radars.within") | ||||
| 			.metadata(securityToken, "message/x.rsocket.authentication.bearer.v0") | ||||
| 			.data(viewBox) | ||||
| 			.retrieveFlux(AirportLocation.class); | ||||
| 
 | ||||
| ---- | ||||
| 
 | ||||
| For `Fire-and-Forget` use the `send()` method that returns `Mono<Void>`. Note that the `Mono` | ||||
| indicates only that the message was successfully sent, and not that it was handled. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-annot-responders]] | ||||
| == Annotated Responders | ||||
| 
 | ||||
| RSocket responders can be implemented as `@MessageMapping` and `@ConnectMapping` methods. | ||||
| `@MessageMapping` methods handle individual requests, and `@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. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-annot-responders-server]] | ||||
| === 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: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	@Configuration | ||||
| 	static class ServerConfig { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		public RSocketMessageHandler rsocketMessageHandler() { | ||||
| 			RSocketMessageHandler handler = new RSocketMessageHandler(); | ||||
| 			handler.routeMatcher(new PathPatternRouteMatcher()); | ||||
| 			return handler; | ||||
| 		} | ||||
| 	} | ||||
| ---- | ||||
| 
 | ||||
| Then start an RSocket server through the Java RSocket API and plug the | ||||
| `RSocketMessageHandler` for the responder as follows: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	ApplicationContext context = ... ; | ||||
| 	RSocketMessageHandler handler = context.getBean(RSocketMessageHandler.class); | ||||
| 
 | ||||
| 	CloseableChannel server = | ||||
| 		RSocketFactory.receive() | ||||
| 			.acceptor(handler.responder()) | ||||
| 			.transport(TcpServerTransport.create("localhost", 7000)) | ||||
| 			.start() | ||||
| 			.block(); | ||||
| ---- | ||||
| 
 | ||||
| `RSocketMessageHandler` supports the composite metadata and the routing metadata formats | ||||
| by default. It can be configured with the <<rsocket-metadata-extractor>> to use if you | ||||
| need to change that 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: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	@Configuration | ||||
| 	static class ServerConfig { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		public RSocketMessageHandler rsocketMessageHandler() { | ||||
| 			RSocketMessageHandler handler = new RSocketMessageHandler(); | ||||
| 			handler.setRSocketStrategies(rsocketStrategies()); | ||||
| 			return handler; | ||||
| 		} | ||||
| 
 | ||||
| 		@Bean | ||||
| 		public RSocketStrategies rsocketStrategies() { | ||||
| 			retrun RSocketStrategies.builder() | ||||
|     			.encoders(encoders -> encoders.add(new Jackson2CborEncoder)) | ||||
|     			.decoder(decoders -> decoders.add(new Jackson2CborDecoder)) | ||||
| 				.routeMatcher(new PathPatternRouteMatcher()) | ||||
| 				.build(); | ||||
| 		} | ||||
| 	} | ||||
| ---- | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-annot-responders-client]] | ||||
| === Client Responders | ||||
| 
 | ||||
| Annotated responders on the client side need to be configured in the | ||||
| `RSocketRequester.Builder`. For details, see | ||||
| <<rsocket-requester-client-responder>>. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-annot-messagemapping]] | ||||
| === @MessageMapping | ||||
| 
 | ||||
| Once <<rsocket-annot-responders-server,server>> or | ||||
| <<rsocket-annot-responders-client,client>> responder configuration is in place, | ||||
| `@MessageMapping` methods can be used as follows: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	@Controller | ||||
| 	public class RadarsController { | ||||
| 
 | ||||
| 		@MessageMapping("locate.radars.within") | ||||
| 		public Flux<AirportLocation> radars(MapRequest request) { | ||||
| 			// ... | ||||
| 		} | ||||
| 	} | ||||
| ---- | ||||
| 
 | ||||
| You don't need to explicit specify the RSocket interaction type. Simply declare the | ||||
| expected input and output, and a route pattern. The supporting infrastructure will adapt | ||||
| matching requests. | ||||
| 
 | ||||
| The following additional arguments are supported for `@MessageMapping` methods: | ||||
| 
 | ||||
| * `RSocketRequester` -- the requester for the connection associated with the request, | ||||
|   to make requests to the remote end. | ||||
| * `@DestinationVariable` -- the value for a variable from the pattern, e.g. | ||||
|   `@MessageMapping("find.radar.{id}")`. | ||||
| * `@Header` -- access to a metadata value registered for extraction, as described in | ||||
|   <<rsocket-metadata-extractor>>. | ||||
| * `@Headers Map<String, Object>` -- access to all metadata values registered for | ||||
|   extraction, as described in <<rsocket-metadata-extractor>>. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-annot-connectmapping]] | ||||
| === @ConnectMapping | ||||
| 
 | ||||
| `@ConnectMapping` handles the `SETUP` frame at the start of an RSocket connection. | ||||
| It can be mapped with a pattern, like an `@MessageMapping` method, and it supports the | ||||
| same arguments as an `@MessageMapping` method but based on the content of the `SETUP` | ||||
| frame. | ||||
| 
 | ||||
| `@ConnectMapping` methods also handle metadata push notifications through | ||||
| the `METADATA_PUSH` frame, i.e. the `metadataPush(Payload)` in `io.rsocket.RSocket`. | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| [[rsocket-metadata-extractor]] | ||||
| == MetadataExtractor | ||||
| 
 | ||||
| Responders must interpret metadata. | ||||
| https://github.com/rsocket/rsocket/blob/master/Extensions/CompositeMetadata.md[Composite metadata] | ||||
| allows independently formatted metadata values (e.g. for routing, security, tracing) each | ||||
| with its own mime type. Applications need a way to configure metadata mime types to | ||||
| support, and a way to access extracted values. | ||||
| 
 | ||||
| `MetadataExtractor` is a contract to take serialized metadata and return decoded | ||||
| name-value pairs that can then be accessed like headers by name, for example via `@Header` | ||||
| in annotated handler methods. | ||||
| 
 | ||||
| `DefaultMetadataExtractor` can be given `Decoder` instances to decode metadata. Out of | ||||
| the box it has built-in support for routing metadata ("message/x.rsocket.routing.v0"), | ||||
| which it decodes to `String` and saves under the "route" key. For any other mime type | ||||
| you'll need to provide a `Decoder` and register the mime type as follows: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(metadataDecoders); | ||||
| 	extractor.metadataToExtract(fooMimeType, Foo.class, "foo"); | ||||
| ---- | ||||
| 
 | ||||
| Composite metadata works well to combine independent metadata values. However the | ||||
| requester might not support composite metadata, or may choose not to use it. For this, | ||||
| `DefaultMetadataExtractor` may needs custom logic to map the decoded value to the output | ||||
| map. Here is an example where JSON is used for metadata: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(metadataDecoders); | ||||
| 	extractor.metadataToExtract( | ||||
| 		MimeType.valueOf("application/vnd.myapp.metadata+json"), | ||||
| 		new ParameterizedTypeReference<Map<String,String>>() {}, | ||||
| 		(jsonMap, outputMap) -> { | ||||
| 			outputMap.putAll(jsonMap); | ||||
| 		}); | ||||
| ---- | ||||
| 
 | ||||
| When configuring `MetadataExtractor` through `RSocketStrategies`, you can let | ||||
| `RSocketStrategies.Builder` create the extractor with the configured decoders, and | ||||
| simply use a callback to customize registrations as follows: | ||||
| 
 | ||||
| [source,java,indent=0,subs="verbatim,quotes",role="primary"] | ||||
| .Java | ||||
| ---- | ||||
| 	RSocketStrategies strategies = RSocketStrategies.builder() | ||||
| 		.metadataExtractorRegistry(registry -> { | ||||
| 			registry.metadataToExtract(fooMimeType, Foo.class, "foo"); | ||||
| 			// ... | ||||
| 		}) | ||||
| 		.build(); | ||||
| ---- | ||||
|  | @ -23,7 +23,6 @@ include::web/webflux-websocket.adoc[leveloffset=+1] | |||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| [[webflux-test]] | ||||
| == Testing | ||||
| [.small]#<<web.adoc#testing, Same in Spring MVC>># | ||||
|  | @ -39,12 +38,7 @@ server. You can use the `WebTestClient` for end-to-end integration tests, too. | |||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| //[[webflux-threading-model]] | ||||
| //=== Threading model | ||||
| // TODO Once we have content for this heading, we should un-comment the heading and | ||||
| // anchor. Until then, we should leave it commented. | ||||
| 
 | ||||
| include::rsocket.adoc[leveloffset=+1] | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue