INT-4300: Add WebFlux Server Support

JIRA: https://jira.spring.io/browse/INT-4300

* Add `ReactiveHttpInboundEndpoint` based on the WebFlux foundation
* Extract `BaseHttpInboundEndpoint` for common
HTTP Inbound Channel Adapters options
* Make `spring-webmvc` and `spring-webflux` as `optional` dependencies
to let end-user to choose
* Refactor `HttpContextUtils` to include constants
for newly added WebFlux support
* Introduce `BaseHttpInboundEndpoint.setRequestPayloadTypeClass()`
for raw `Class<?>` and modify existing `setRequestPayloadType()`
for the `ResolvableType`
* Refactor existing MVC tests and XML components parsers to use
new `setRequestPayloadTypeClass()`
* Add `MessagingGatewaySupport.sendAndReceiveMessageReactive()` to get
a reply from downstream flow reactive back-pressure manner
* Add `IntegrationHandlerResultHandler` implementation to let WebFlux
infrastructure to handle the `Mono<Void>` from the `ReactiveHttpInboundEndpoint`
properly
* Fix `JdbcLockRegistryLeaderInitiatorTests` race condition to assert
the `initiator1` is elected eventually after yielding when the `initiator2`
is stopped

* Fix JavaDocs issue in the `HttpRequestHandlingMessagingGateway`
* Move all the "hard" logic in the `MessagingGatewaySupport#doSendAndReceiveMessageReactive`
to the `Mono` chain ensuring back-pressure when `sendAndReceiveMessageReactive()` is
called not from the Reactive Stream

Add test-case to demonstrate SSE

JIRA: https://jira.spring.io/browse/INT-3625

Some polishing and optimization for the
`MessagingGatewaySupport.doSendAndReceiveMessageReactive()`

More optimization for `MessagingGatewaySupport`

* Upgrade to Reactor 3.1 M3
* Document WebFlux-based components

Minor Doc Polishing
This commit is contained in:
Artem Bilan
2017-06-20 10:14:57 -04:00
committed by Gary Russell
parent e475d9c695
commit d4a99919ed
26 changed files with 2268 additions and 745 deletions

View File

@@ -16,35 +16,48 @@
package org.springframework.integration.gateway;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicLong;
import org.reactivestreams.Subscriber;
import org.springframework.core.AttributeAccessor;
import org.springframework.integration.MessageTimeoutException;
import org.springframework.integration.channel.ReactiveStreamsSubscribableChannel;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.endpoint.ReactiveStreamsConsumer;
import org.springframework.integration.handler.BridgeHandler;
import org.springframework.integration.history.HistoryWritingMessagePostProcessor;
import org.springframework.integration.mapping.InboundMessageMapper;
import org.springframework.integration.mapping.MessageMappingException;
import org.springframework.integration.mapping.OutboundMessageMapper;
import org.springframework.integration.support.DefaultErrorMessageStrategy;
import org.springframework.integration.support.DefaultMessageBuilderFactory;
import org.springframework.integration.support.ErrorMessageStrategy;
import org.springframework.integration.support.ErrorMessageUtils;
import org.springframework.integration.support.MessageBuilderFactory;
import org.springframework.integration.support.MutableMessageBuilder;
import org.springframework.integration.support.converter.SimpleMessageConverter;
import org.springframework.integration.support.management.IntegrationManagedResource;
import org.springframework.integration.support.management.MessageSourceMetrics;
import org.springframework.integration.support.management.TrackableComponent;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;
/**
* A convenient base class for connecting application code to
* {@link MessageChannel}s for sending, receiving, or request-reply operations.
@@ -450,10 +463,9 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
if (requestChannel == null) {
throw new MessagingException("No request channel available. Cannot send request message.");
}
MessageChannel replyChannel = getReplyChannel();
if (replyChannel != null && this.replyMessageCorrelator == null) {
this.registerReplyMessageCorrelator();
}
registerReplyMessageCorrelatorIfNecessary();
Object reply = null;
Throwable error = null;
Message<?> requestMessage = null;
@@ -533,6 +545,129 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
return reply;
}
protected Mono<Message<?>> sendAndReceiveMessageReactive(Object object) {
initializeIfNecessary();
Assert.notNull(object, "request must not be null");
MessageChannel requestChannel = getRequestChannel();
if (requestChannel == null) {
throw new MessagingException("No request channel available. Cannot send request message.");
}
registerReplyMessageCorrelatorIfNecessary();
return doSendAndReceiveMessageReactive(requestChannel, object, false);
}
@SuppressWarnings("unchecked")
private Mono<Message<?>> doSendAndReceiveMessageReactive(MessageChannel requestChannel, Object object,
boolean error) {
return Mono.defer(() -> {
Message<?> message;
try {
message = object instanceof Message<?>
? (Message<?>) object
: this.requestMapper.toMessage(object);
message = this.historyWritingPostProcessor.postProcessMessage(message);
}
catch (Exception e) {
throw new MessageMappingException("Cannot map to message: " + object, e);
}
Object originalReplyChannelHeader = message.getHeaders().getReplyChannel();
Object originalErrorChannelHeader = message.getHeaders().getErrorChannel();
FutureReplyChannel replyChannel = new FutureReplyChannel();
Message<?> requestMessage = MutableMessageBuilder.fromMessage(message)
.setReplyChannel(replyChannel)
.setHeader(this.messagingTemplate.getSendTimeoutHeader(), null)
.setHeader(this.messagingTemplate.getReceiveTimeoutHeader(), null)
.setErrorChannel(replyChannel)
.build();
if (requestChannel instanceof ReactiveStreamsSubscribableChannel) {
((ReactiveStreamsSubscribableChannel) requestChannel)
.subscribeTo(Mono.just(requestMessage));
}
else {
long sendTimeout = sendTimeout(requestMessage);
boolean sent =
sendTimeout >= 0
? requestChannel.send(requestMessage, sendTimeout)
: requestChannel.send(requestMessage);
if (!sent) {
throw new MessageDeliveryException(requestMessage,
"Failed to send message to channel '" + requestChannel +
"' within timeout: " + sendTimeout);
}
}
return Mono.fromFuture(replyChannel.messageFuture)
.doOnSubscribe(s -> {
if (!error && this.countsEnabled) {
this.messageCount.incrementAndGet();
}
})
.<Message<?>>map(replyMessage ->
MessageBuilder.fromMessage(replyMessage)
.setHeader(MessageHeaders.REPLY_CHANNEL, originalReplyChannelHeader)
.setHeader(MessageHeaders.ERROR_CHANNEL, originalErrorChannelHeader)
.build())
.onErrorResume(t -> error ? Mono.error(t) : handleSendError(requestMessage, t));
});
}
private Mono<Message<?>> handleSendError(Message<?> requestMessage, Throwable exception) {
if (logger.isDebugEnabled()) {
logger.debug("failure occurred in gateway sendAndReceiveReactive: " + exception.getMessage());
}
MessageChannel errorChannel = getErrorChannel();
if (errorChannel != null) {
ErrorMessage errorMessage = buildErrorMessage(requestMessage, exception);
try {
return doSendAndReceiveMessageReactive(errorChannel, errorMessage, true);
}
catch (Exception errorFlowFailure) {
throw new MessagingException(errorMessage, "failure occurred in error-handling flow", errorFlowFailure);
}
}
else {
// no errorChannel so we'll propagate
throw wrapExceptionIfNecessary(exception, "gateway received checked Exception");
}
}
private long sendTimeout(Message<?> requestMessage) {
Long sendTimeout = headerToLong(requestMessage.getHeaders()
.get(this.messagingTemplate.getSendTimeoutHeader()));
return (sendTimeout != null ? sendTimeout : this.messagingTemplate.getSendTimeout());
}
private long receiveTimeout(Message<?> requestMessage) {
Long receiveTimeout = headerToLong(requestMessage.getHeaders()
.get(this.messagingTemplate.getReceiveTimeoutHeader()));
return (receiveTimeout != null ? receiveTimeout : this.messagingTemplate.getReceiveTimeout());
}
@Nullable
private Long headerToLong(@Nullable Object headerValue) {
if (headerValue instanceof Number) {
return ((Number) headerValue).longValue();
}
else if (headerValue instanceof String) {
return Long.parseLong((String) headerValue);
}
else {
return null;
}
}
/**
* Build an error message for the message and throwable using the configured
* {@link ErrorMessageStrategy}.
@@ -542,9 +677,7 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
* @since 4.3.10
*/
protected final ErrorMessage buildErrorMessage(Message<?> requestMessage, Throwable throwable) {
ErrorMessage errorMessage = this.errorMessageStrategy.buildErrorMessage(throwable,
getErrorMessageAttributes(requestMessage));
return errorMessage;
return this.errorMessageStrategy.buildErrorMessage(throwable, getErrorMessageAttributes(requestMessage));
}
/**
@@ -560,42 +693,60 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
}
private void rethrow(Throwable t, String description) {
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
throw new MessagingException(description, t);
throw wrapExceptionIfNecessary(t, description);
}
private void registerReplyMessageCorrelator() {
synchronized (this.replyMessageCorrelatorMonitor) {
if (this.replyMessageCorrelator != null) {
return;
private RuntimeException wrapExceptionIfNecessary(Throwable t, String description) {
if (t instanceof RuntimeException) {
return (RuntimeException) t;
}
else {
return new MessagingException(description, t);
}
}
protected void registerReplyMessageCorrelatorIfNecessary() {
MessageChannel replyChannel = getReplyChannel();
if (replyChannel != null && this.replyMessageCorrelator == null) {
boolean shouldStartCorrelator;
synchronized (this.replyMessageCorrelatorMonitor) {
if (this.replyMessageCorrelator != null) {
return;
}
AbstractEndpoint correlator;
BridgeHandler handler = new BridgeHandler();
if (getBeanFactory() != null) {
handler.setBeanFactory(getBeanFactory());
}
handler.afterPropertiesSet();
if (replyChannel instanceof SubscribableChannel) {
correlator = new EventDrivenConsumer((SubscribableChannel) replyChannel, handler);
}
else if (replyChannel instanceof PollableChannel) {
PollingConsumer endpoint = new PollingConsumer((PollableChannel) replyChannel, handler);
endpoint.setBeanFactory(getBeanFactory());
endpoint.setReceiveTimeout(this.replyTimeout);
endpoint.afterPropertiesSet();
correlator = endpoint;
}
else if (replyChannel instanceof ReactiveStreamsSubscribableChannel) {
ReactiveStreamsConsumer endpoint =
new ReactiveStreamsConsumer(replyChannel, (Subscriber<Message<?>>) handler);
endpoint.afterPropertiesSet();
correlator = endpoint;
}
else {
throw new MessagingException("Unsupported 'replyChannel' type [" + replyChannel.getClass() + "]."
+ "SubscribableChannel or PollableChannel type are supported.");
}
this.replyMessageCorrelator = correlator;
shouldStartCorrelator = true;
}
AbstractEndpoint correlator = null;
BridgeHandler handler = new BridgeHandler();
if (this.getBeanFactory() != null) {
handler.setBeanFactory(this.getBeanFactory());
if (shouldStartCorrelator && isRunning()) {
if (isRunning()) {
this.replyMessageCorrelator.start();
}
}
handler.afterPropertiesSet();
MessageChannel replyChannel = getReplyChannel();
if (replyChannel instanceof SubscribableChannel) {
correlator = new EventDrivenConsumer((SubscribableChannel) replyChannel, handler);
}
else if (replyChannel instanceof PollableChannel) {
PollingConsumer endpoint = new PollingConsumer((PollableChannel) replyChannel, handler);
endpoint.setBeanFactory(this.getBeanFactory());
endpoint.setReceiveTimeout(this.replyTimeout);
endpoint.afterPropertiesSet();
correlator = endpoint;
}
else {
throw new MessagingException("Unsupported 'replyChannel' type [" + replyChannel.getClass() + "]."
+ "SubscribableChannel or PollableChannel type are supported.");
}
if (this.isRunning()) {
correlator.start();
}
this.replyMessageCorrelator = correlator;
}
}
@@ -641,4 +792,15 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
}
private static class FutureReplyChannel implements MessageChannel {
private final CompletableFuture<Message<?>> messageFuture = new CompletableFuture<>();
@Override
public boolean send(Message<?> message, long timeout) {
return this.messageFuture.complete(message);
}
}
}