Before this change an @MessageMapping could be matched to any RSocket
interaction type, which is arguably too flexible, makes it difficult to
reason what would happen in case of a significant mismatch of
cardinality, e.g. request for Fire-And-Forget (1-to-0) mapped to a
method that returns Flux, and could result in payloads being ignored,
or not seen unintentionally.
This commit checks @ConnectMapping method on startup and rejects them
if they return any values (sync or async). It also refines each
@MessageMapping to match only the RSocket interaction type it fits
based on the input and output cardinality of the handler method.
Subsequently if a request is not matched, we'll do a second search to
identify partial matches (by route only) and raise a helpful error that
explains which interaction type is actually supported.
The reference docs has been updated to explain the options.
Closes gh-23999
@ -56,6 +56,13 @@ public class RSocketFrameTypeMessageCondition extends AbstractMessageCondition<R
@@ -56,6 +56,13 @@ public class RSocketFrameTypeMessageCondition extends AbstractMessageCondition<R
@ -68,6 +75,10 @@ public class RSocketFrameTypeMessageCondition extends AbstractMessageCondition<R
@@ -68,6 +75,10 @@ public class RSocketFrameTypeMessageCondition extends AbstractMessageCondition<R
@ -124,18 +135,71 @@ public class RSocketFrameTypeMessageCondition extends AbstractMessageCondition<R
@@ -124,18 +135,71 @@ public class RSocketFrameTypeMessageCondition extends AbstractMessageCondition<R
}
/** Condition to match the initial SETUP frame and subsequent metadata pushes. */
@ -263,6 +286,17 @@ public class RSocketMessageHandler extends MessageMappingMessageHandler {
@@ -263,6 +286,17 @@ public class RSocketMessageHandler extends MessageMappingMessageHandler {
"Return type must be void or a void async type: "+handler);
}
}
});
}
@Override
@ -279,10 +313,9 @@ public class RSocketMessageHandler extends MessageMappingMessageHandler {
@@ -279,10 +313,9 @@ public class RSocketMessageHandler extends MessageMappingMessageHandler {
@ -294,6 +327,45 @@ public class RSocketMessageHandler extends MessageMappingMessageHandler {
@@ -294,6 +327,45 @@ public class RSocketMessageHandler extends MessageMappingMessageHandler {
@ -306,7 +378,18 @@ public class RSocketMessageHandler extends MessageMappingMessageHandler {
@@ -306,7 +378,18 @@ public class RSocketMessageHandler extends MessageMappingMessageHandler {
logger.warn("No handler for fireAndForget to '"+destination+"'");
return;
}
thrownewMessageDeliveryException("No handler for destination '"+destination+"'");
@ -586,7 +586,7 @@ indicates only that the message was successfully sent, and not that it was handl
@@ -586,7 +586,7 @@ indicates only that the message was successfully sent, and not that it was handl
== Annotated Responders
RSocket responders can be implemented as `@MessageMapping` and `@ConnectMapping` methods.
`@MessageMapping` methods handle individual requests, and `@ConnectMapping` methods handle
`@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.
@ -760,20 +760,90 @@ class RadarsController {
@@ -760,20 +760,90 @@ class RadarsController {
}
----
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 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:
The following additional arguments are supported for `@MessageMapping` methods:
[cols="1,3",options="header"]
|===
| Method Argument
| Description
* `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.
| `@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` -- 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>>.
| `@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:
[%autowidth]
[cols=2*,options="header"]
|===
| 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