Add initial support for RSockets (#2902)

* Add initial support for RSockets

* Add `spring-integration-rsocket` module and respective dependencies
* Implement `RSocketOutboundGateway` based on the Spring Messaging
`RSocketRequester`.
This component supports dynamic RSocket properties via expressions
against request message.
to handle `Publisher` for requests, it must be present in the request
message `payload` instead of `FluxMessageChannel` upstream, since the
last one just flattens events to be handled in the `MessageHandler` one
by one.
The result `Mono` is subscribed downstream in the `FluxMessageChannel`
or directly by the `AbstractReplyProducingMessageHandler`.
If result is a `Flux` it is just wrapped into the `Mono` to be processed
downstream by end-user code.
The point is that these request/replies are volatile and live in the
particular context meanwhile a `FluxMessageChannel` is long living
publisher in the application context boundaries.
* The `RSocketOutboundGatewayIntegrationTests` is an adapted copy of
`RSocketClientToServerIntegrationTests` from Spring Messaging
* Add `doOnError()` into the `Flux` created in the
`AbstractMessageProducingHandler` for `Publisher` replies

* * Use singular for the `RSocket` term
* Use no-op `Consumer` for the `strategiesConfigurer` and
`factoryConfigurer` in the `RSocketOutboundGateway` and also
`Assert.notNull()` in the appropriate setters to avoid null check during
`RSocketRequester.builder()` initialization
* Use `TcpServer.create().port(0)` in the
`RSocketOutboundGatewayIntegrationTests` to allow to select free OS port
and bind into it.
The selected port is used later for client configuration in the
`RSocketOutboundGateway` bean definition

* * Change `RSocketOutboundGatewayIntegrationTests.PORT` to lower case
This commit is contained in:
Artem Bilan
2019-04-25 14:07:56 -04:00
committed by Gary Russell
parent f8f69c9129
commit 89e11f2c46
14 changed files with 846 additions and 12 deletions

View File

@@ -82,8 +82,8 @@ public class FluxMessageChannel extends AbstractMessageChannel
ConnectableFlux<?> connectableFlux =
Flux.from(publisher)
.handle((message, sink) -> sink.next(send(message)))
.onErrorContinue((throwable, o) -> logger.warn("Error during processing event: " + o, throwable)
)
.onErrorContinue((throwable, event) ->
logger.warn("Error during processing event: " + event, throwable))
.doOnComplete(() -> this.publishers.remove(publisher))
.publish();

View File

@@ -29,6 +29,7 @@ import org.reactivestreams.Publisher;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.channel.ReactiveStreamsSubscribableChannel;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.routingslip.RoutingSlipRouteStrategy;
@@ -283,6 +284,7 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
((ReactiveStreamsSubscribableChannel) messageChannel)
.subscribeTo(
Flux.from((Publisher<?>) reply)
.doOnError((ex) -> sendErrorMessage(requestMessage, ex))
.map(result -> createOutputMessage(result, requestHeaders)));
}
}
@@ -311,25 +313,22 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
}
private void asyncNonReactiveReply(Message<?> requestMessage, Object reply, Object replyChannel) {
ListenableFuture<?> future;
if (reply instanceof ListenableFuture<?>) {
future = (ListenableFuture<?>) reply;
}
else {
SettableListenableFuture<Object> settableListenableFuture = new SettableListenableFuture<>();
Mono.from((Publisher<?>) reply)
.subscribe(settableListenableFuture::set, settableListenableFuture::setException);
future = settableListenableFuture;
}
future.addCallback(new ReplyFutureCallback(requestMessage, replyChannel));
}
private Object getOutputChannelFromRoutingSlip(Object reply, Message<?> requestMessage, List<?> routingSlip,
AtomicInteger routingSlipIndex) {
if (routingSlipIndex.get() >= routingSlip.size()) {
return null;
}
@@ -365,7 +364,7 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
}
protected Message<?> createOutputMessage(Object output, MessageHeaders requestHeaders) {
AbstractIntegrationMessageBuilder<?> builder = null;
AbstractIntegrationMessageBuilder<?> builder;
if (output instanceof Message<?>) {
if (this.noHeadersPropagation || !shouldCopyRequestHeaders()) {
return (Message<?>) output;
@@ -449,7 +448,7 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
}
catch (Exception e) {
Exception exceptionToLog =
IntegrationUtils.wrapInHandlingExceptionIfNecessary(requestMessage, () -> null, e);
IntegrationUtils.wrapInHandlingExceptionIfNecessary(requestMessage, () -> null, e);
logger.error("Failed to send async reply", exceptionToLog);
}
}
@@ -459,7 +458,7 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
Object errorChannel = requestHeaders.getErrorChannel();
if (errorChannel == null) {
try {
errorChannel = getChannelResolver().resolveDestination("errorChannel");
errorChannel = getChannelResolver().resolveDestination(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME);
}
catch (DestinationResolutionException e) {
// ignore

View File

@@ -87,6 +87,9 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
this.beanClassLoader = beanClassLoader;
}
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
@Override
protected final void onInit() {