GH-2300: Add Flux support in WebFluxRequestExecMH

Fixes: spring-projects/spring-integration#2300

To allow to consume a streaming HTTP response downstream expose
`replyToFlux` option on the `WebFluxRequestExecutingMessageHandler`.
This way the body of the HTTP response can be converted now to the
`Flux` for subsequent output message.
The option is `false` by default; can be changed to `true` in the `5.1`
This commit is contained in:
Artem Bilan
2017-12-14 14:21:01 -05:00
committed by Gary Russell
parent 21bf69b7f8
commit bd872846fb
7 changed files with 144 additions and 28 deletions

View File

@@ -21,6 +21,7 @@ import org.w3c.dom.Element;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.http.config.HttpOutboundGatewayParser;
import org.springframework.integration.webflux.outbound.WebFluxRequestExecutingMessageHandler;
import org.springframework.util.StringUtils;
@@ -46,6 +47,7 @@ public class WebFluxOutboundGatewayParser extends HttpOutboundGatewayParser {
.addIndexedArgumentValue(1, new RuntimeBeanReference(webClientRef));
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-to-flux");
return builder;
}

View File

@@ -29,6 +29,7 @@ import org.springframework.expression.common.LiteralExpression;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.http.ResponseEntity;
import org.springframework.integration.expression.ValueExpression;
import org.springframework.integration.http.outbound.AbstractHttpRequestExecutingMessageHandler;
@@ -36,12 +37,14 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
@@ -59,6 +62,8 @@ public class WebFluxRequestExecutingMessageHandler extends AbstractHttpRequestEx
private final WebClient webClient;
private boolean replyToFlux;
/**
* Create a handler that will send requests to the provided URI.
* @param uri The URI.
@@ -110,6 +115,20 @@ public class WebFluxRequestExecutingMessageHandler extends AbstractHttpRequestEx
this.setAsync(true);
}
/**
* The boolean flag to identify if the reply payload should be as a {@link Flux} from the response body
* or as resolved value from the {@link Mono} of the response body.
* Defaults to {@code false} - simple value is pushed downstream.
* Makes sense when {@code expectedResponseType} is configured.
* @param replyToFlux represent reply payload as a {@link Flux} or as a value from the {@link Mono}.
* @since 5.0.1
* @see #setExpectedResponseType(Class)
* @see #setExpectedResponseTypeExpression(Expression)
*/
public void setReplyToFlux(boolean replyToFlux) {
this.replyToFlux = replyToFlux;
}
@Override
public String getComponentType() {
return (isExpectReply() ? "webflux:outbound-gateway" : "webflux:outbound-channel-adapter");
@@ -169,13 +188,35 @@ public class WebFluxRequestExecutingMessageHandler extends AbstractHttpRequestEx
ResponseEntity.status(response.statusCode())
.headers(response.headers().asHttpHeaders());
Mono<?> bodyMono = Mono.empty();
Mono<?> bodyMono;
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
bodyMono = response.body(BodyExtractors.toMono((ParameterizedTypeReference<?>) expectedResponseType));
if (expectedResponseType != null) {
if (this.replyToFlux) {
BodyExtractor<? extends Flux<?>, ReactiveHttpInputMessage> extractor;
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
extractor = BodyExtractors.toFlux(
(ParameterizedTypeReference<?>) expectedResponseType);
}
else {
extractor = BodyExtractors.toFlux((Class<?>) expectedResponseType);
}
Flux<?> flux = response.body(extractor);
bodyMono = Mono.just(flux);
}
else {
BodyExtractor<? extends Mono<?>, ReactiveHttpInputMessage> extractor;
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
extractor = BodyExtractors.toMono(
(ParameterizedTypeReference<?>) expectedResponseType);
}
else {
extractor = BodyExtractors.toMono((Class<?>) expectedResponseType);
}
bodyMono = response.body(extractor);
}
}
else if (expectedResponseType != null) {
bodyMono = response.body(BodyExtractors.toMono((Class<?>) expectedResponseType));
else {
bodyMono = Mono.empty();
}
return bodyMono

View File

@@ -10,7 +10,7 @@
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:import namespace="http://www.springframework.org/schema/tool"/>
<xsd:import namespace="http://www.springframework.org/schema/integration"
schemaLocation="http://www.springframework.org/schema/integration/spring-integration-5.0.xsd" />
schemaLocation="http://www.springframework.org/schema/integration/spring-integration-5.0.xsd"/>
<xsd:import namespace="http://www.springframework.org/schema/integration/http"
schemaLocation="http://www.springframework.org/schema/integration/http/spring-integration-http-5.0.xsd"/>
@@ -82,7 +82,8 @@
<xsd:complexContent>
<xsd:extension base="int-http:gatewayType">
<xsd:choice minOccurs="0" maxOccurs="3">
<xsd:element name="uri-variable" type="int-http:uriVariableType" minOccurs="0" maxOccurs="unbounded">
<xsd:element name="uri-variable" type="int-http:uriVariableType" minOccurs="0"
maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
Specify an expression for URI variable placeholder within 'url'.
@@ -126,7 +127,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="transfer-cookies" type="xsd:string" default="false">
<xsd:attribute name="transfer-cookies" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
When set to "true", if a response contains a 'Set-Cookie' header, it will be mapped to a 'Cookie' header.
@@ -134,6 +135,9 @@
supplied by the server. Default is "false".
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:boolean xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="reply-timeout" type="xsd:string">
<xsd:annotation>
@@ -173,6 +177,19 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="reply-to-flux" default="false">
<xsd:annotation>
<xsd:documentation>
When set to "true", the response is converted to the Flux which is sent as a reply
message payload.
The downstream flow might have a splitter to walk through that Flux.
Default is "false".
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:boolean xsd:string"/>
</xsd:simpleType>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>