WebFlux improvements
* add `BodyExtractor` support for Outbound part * add `ClientHttpResponseBodyExtractor` as identity function * add XML configuration for the Inbound part * document `BodyExtractor` and Inbound XML support Rename `replyToFlux` property to the `replyPayloadToFlux` Doc Polishing
This commit is contained in:
committed by
Gary Russell
parent
1e1346dc94
commit
b5ec98f025
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.webflux.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
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.HttpInboundEndpointParser;
|
||||
import org.springframework.integration.webflux.inbound.WebFluxInboundEndpoint;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0.1
|
||||
*/
|
||||
public class WebFluxInboundEndpointParser extends HttpInboundEndpointParser {
|
||||
|
||||
public WebFluxInboundEndpointParser(boolean expectReply) {
|
||||
super(expectReply);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> getBeanClass(Element element) {
|
||||
return WebFluxInboundEndpoint.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
|
||||
super.doParse(element, parserContext, builder);
|
||||
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "codec-configurer");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "requested-content-type-resolver");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reactive-adapter-registry");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2017-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,6 +28,8 @@ import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHa
|
||||
public class WebFluxNamespaceHandler extends AbstractIntegrationNamespaceHandler {
|
||||
|
||||
public void init() {
|
||||
registerBeanDefinitionParser("inbound-channel-adapter", new WebFluxInboundEndpointParser(false));
|
||||
registerBeanDefinitionParser("inbound-gateway", new WebFluxInboundEndpointParser(true));
|
||||
registerBeanDefinitionParser("outbound-channel-adapter", new WebFluxOutboundChannelAdapterParser());
|
||||
registerBeanDefinitionParser("outbound-gateway", new WebFluxOutboundGatewayParser());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2017-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -47,7 +47,8 @@ public class WebFluxOutboundGatewayParser extends HttpOutboundGatewayParser {
|
||||
.addIndexedArgumentValue(1, new RuntimeBeanReference(webClientRef));
|
||||
}
|
||||
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-to-flux");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-payload-to-flux");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "body-extractor");
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
* Copyright 2017-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,11 +20,16 @@ import java.net.URI;
|
||||
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
import org.springframework.integration.expression.ValueExpression;
|
||||
import org.springframework.integration.http.dsl.BaseHttpMessageHandlerSpec;
|
||||
import org.springframework.integration.webflux.outbound.WebFluxRequestExecutingMessageHandler;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* The {@link BaseHttpMessageHandlerSpec} implementation for the {@link WebFluxRequestExecutingMessageHandler}.
|
||||
*
|
||||
@@ -53,6 +58,30 @@ public class WebFluxMessageHandlerSpec
|
||||
this.webClient = webClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 replyPayloadToFlux represent reply payload as a {@link Flux} or as a value from the {@link Mono}.
|
||||
* @since 5.0.1
|
||||
* @see WebFluxRequestExecutingMessageHandler#setReplyPayloadToFlux(boolean)
|
||||
*/
|
||||
public void replyPayloadToFlux(boolean replyPayloadToFlux) {
|
||||
this.target.setReplyPayloadToFlux(replyPayloadToFlux);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a {@link BodyExtractor} as an alternative to the {@code expectedResponseType}
|
||||
* to allow to get low-level access to the received {@link ClientHttpResponse}.
|
||||
* @param bodyExtractor the {@link BodyExtractor} to use.
|
||||
* @since 5.0.1
|
||||
* @see WebFluxRequestExecutingMessageHandler#setBodyExtractor(BodyExtractor)
|
||||
*/
|
||||
public void bodyExtractor(BodyExtractor<?, ClientHttpResponse> bodyExtractor) {
|
||||
this.target.setBodyExtractor(bodyExtractor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isClientSet() {
|
||||
return this.webClient != null;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
* Copyright 2017-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -126,7 +126,7 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W
|
||||
|
||||
@Override
|
||||
public String getComponentType() {
|
||||
return super.getComponentType().replaceFirst("(http:)", "$1webflux-");
|
||||
return super.getComponentType().replaceFirst("http", "webflux");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
* Copyright 2017-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,6 +31,7 @@ import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ReactiveHttpInputMessage;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
import org.springframework.integration.expression.ValueExpression;
|
||||
import org.springframework.integration.http.outbound.AbstractHttpRequestExecutingMessageHandler;
|
||||
import org.springframework.messaging.Message;
|
||||
@@ -62,7 +63,9 @@ public class WebFluxRequestExecutingMessageHandler extends AbstractHttpRequestEx
|
||||
|
||||
private final WebClient webClient;
|
||||
|
||||
private boolean replyToFlux;
|
||||
private boolean replyPayloadToFlux;
|
||||
|
||||
private BodyExtractor<?, ClientHttpResponse> bodyExtractor;
|
||||
|
||||
/**
|
||||
* Create a handler that will send requests to the provided URI.
|
||||
@@ -120,13 +123,25 @@ public class WebFluxRequestExecutingMessageHandler extends AbstractHttpRequestEx
|
||||
* 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}.
|
||||
* @param replyPayloadToFlux 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;
|
||||
public void setReplyPayloadToFlux(boolean replyPayloadToFlux) {
|
||||
this.replyPayloadToFlux = replyPayloadToFlux;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a {@link BodyExtractor} as an alternative to the {@code expectedResponseType}
|
||||
* to allow to get low-level access to the received {@link ClientHttpResponse}.
|
||||
* @param bodyExtractor the {@link BodyExtractor} to use.
|
||||
* @since 5.0.1
|
||||
* @see #setExpectedResponseType(Class)
|
||||
* @see #setExpectedResponseTypeExpression(Expression)
|
||||
*/
|
||||
public void setBodyExtractor(BodyExtractor<?, ClientHttpResponse> bodyExtractor) {
|
||||
this.bodyExtractor = bodyExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -191,7 +206,7 @@ public class WebFluxRequestExecutingMessageHandler extends AbstractHttpRequestEx
|
||||
Mono<?> bodyMono;
|
||||
|
||||
if (expectedResponseType != null) {
|
||||
if (this.replyToFlux) {
|
||||
if (this.replyPayloadToFlux) {
|
||||
BodyExtractor<? extends Flux<?>, ReactiveHttpInputMessage> extractor;
|
||||
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
|
||||
extractor = BodyExtractors.toFlux(
|
||||
@@ -215,6 +230,15 @@ public class WebFluxRequestExecutingMessageHandler extends AbstractHttpRequestEx
|
||||
bodyMono = response.body(extractor);
|
||||
}
|
||||
}
|
||||
else if (this.bodyExtractor != null) {
|
||||
Object body = response.body(this.bodyExtractor);
|
||||
if (body instanceof Mono) {
|
||||
bodyMono = (Mono<?>) body;
|
||||
}
|
||||
else {
|
||||
bodyMono = Mono.just(body);
|
||||
}
|
||||
}
|
||||
else {
|
||||
bodyMono = Mono.empty();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.webflux.support;
|
||||
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
|
||||
/**
|
||||
* The {@link BodyExtractor} identity function implementation
|
||||
* which just returns the provided {@link ClientHttpResponse}.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0.1
|
||||
*/
|
||||
public class ClientHttpResponseBodyExtractor implements BodyExtractor<ClientHttpResponse, ClientHttpResponse> {
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse extract(ClientHttpResponse response, Context context) {
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,315 @@
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
|
||||
|
||||
<xsd:element name="inbound-channel-adapter">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Configures a Message Producing Endpoint for the
|
||||
'org.springframework.integration.webflux.inbound.WebFluxInboundEndpoint' that
|
||||
receives HTTP requests reactive manner and doesn't produce responses.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="request-mapping" type="int-http:requestMappingType" minOccurs="0">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Defines configuration for org.springframework.integration.http.inbound.RequestMapping
|
||||
as RESTFul attributes for Spring Integration HTTP Inbound Endpoints.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="cross-origin" type="int-http:crossOriginType" minOccurs="0">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Marks this endpoint as permitting cross origin requests (CORS).
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="header" type="int-http:headerType" minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specifies a Message header as a result of expression evaluation
|
||||
against ServletRequest and URI Variables.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="integration:channelAdapterAttributes"/>
|
||||
<xsd:attribute name="send-timeout" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Maximum amount of time in milliseconds to wait when sending
|
||||
a message to the channel if such channel may block.
|
||||
For example, a Queue Channel can block until space
|
||||
is available if its maximum capacity has been reached.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="status-code-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
A SpEL expression that resolves to an 'HttpStatus' code when rendering a response.
|
||||
The expression must return an object which can be converted to a
|
||||
'org.springframework.http.HttpStatus' enum value.
|
||||
The 'evaluationContext' has a 'BeanResolver' but no variables, so the usage of this attribute
|
||||
is somewhat limited.
|
||||
An example might be to resolve, at runtime, some scoped Bean that returns an
|
||||
'HttpStatus' value, or use a literal expression e.g. "201".
|
||||
By default 'status-code-expression' is null, meaning that the default '200 OK' response status
|
||||
will be returned.
|
||||
The 'http:inbound-gateway' resolves the 'status code' from the 'http_statusCode' header of the reply
|
||||
Message.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="inboundCommonAttributes" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="inbound-gateway">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Configures a Messaging Gateway Endpoint for the
|
||||
'org.springframework.integration.webflux.inbound.WebFluxInboundEndpoint' that
|
||||
receives HTTP requests and produces responses reactive manner.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="int-http:gatewayType">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="request-mapping" minOccurs="0">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Defines configuration for org.springframework.integration.http.inbound.RequestMapping
|
||||
as RESTFul attributes for Spring Integration HTTP Inbound Endpoints.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:complexType>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="int-http:requestMappingType">
|
||||
<xsd:attribute name="produces" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The producible media types of the mapped request, narrowing the primary mapping.
|
||||
The format is a sequence of media types ("text/plain", "application/*),
|
||||
with a request only mapped if the Accept matches one of these media types.
|
||||
Expressions can be negated by using the "!" operator, as in "!text/plain", which matches
|
||||
all requests with a Accept other than "text/plain".
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="cross-origin" type="int-http:crossOriginType" minOccurs="0">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Marks this endpoint as permitting cross origin requests (CORS).
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
<xsd:element name="header" type="int-http:headerType" minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specifies a Message header as a result of expression evaluation
|
||||
against ServletRequest and URI Variables.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:element>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="request-channel" type="xsd:string" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.messaging.MessageChannel" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
The receiving Message Channel of this endpoint.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="inboundCommonAttributes" />
|
||||
<xsd:attribute name="extract-reply-payload" type="xsd:string" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specify whether only the reply Message's payload should be passed in the response.
|
||||
If this is set to 'false', the entire Message will be used to generate the response.
|
||||
The default is 'true'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="mapped-response-headers" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Comma-separated list of names of MessageHeaders to be mapped into the HttpHeaders of the HTTP response.
|
||||
This can only be provided if the 'header-mapper' reference is not being set directly. The values in
|
||||
this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo").
|
||||
The String "HTTP_RESPONSE_HEADERS" will match against any of the standard HTTP Response headers.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="request-timeout" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Used to set the sendTimeout on the underlying MessagingTemplate instance
|
||||
(org.springframework.integration.core.MessagingTemplate) for sending messages
|
||||
to the request channel. If not specified this property will default to "1000"
|
||||
(1 second).
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="reply-timeout" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Used to set the receiveTimeout on the underlying MessagingTemplate instance
|
||||
(org.springframework.integration.core.MessagingTemplate) for receiving messages
|
||||
from the reply channel. If not specified this property will default to "1000"
|
||||
(1 second).
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="reply-timeout-status-code-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
A SpEL expression that resolves to an 'HttpStatus' code when rendering a response after
|
||||
a 'reply-timeout'.
|
||||
The expression must return an object which can be converted to a
|
||||
'org.springframework.http.HttpStatus' enum value.
|
||||
The 'evaluationContext' has a 'BeanResolver' but no variables, so the usage of this attribute
|
||||
is somewhat limited.
|
||||
An example might be to resolve, at runtime, some scoped Bean that returns an
|
||||
'HttpStatus' value, or use a literal expression e.g. "504".
|
||||
By default 'status-code-expression' is null, meaning that the default
|
||||
'500 Internal Server Error' response status will be returned after a timeout.
|
||||
When a timeout is not encountered,
|
||||
the 'http:inbound-gateway' resolves the 'status code' from the 'http_statusCode' header of the reply
|
||||
Message.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:attributeGroup name="inboundCommonAttributes">
|
||||
<xsd:attribute name="path" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Comma-separated URI paths (e.g., /orderId/{order}).
|
||||
Ant-style path patterns are also supported (e.g. /myPath/*.do).
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="supported-methods">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Comma-separated HTTP Method names. Determines which types of Request are
|
||||
allowed with this Endpoint.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:union memberTypes="int-http:httpMethodEnumeration xsd:string" />
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="request-payload-type" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Target type for payload that is the conversion result of the request.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="payload-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Allows you to specify SpEL expression to construct a Message payload
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="header-mapper" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.mapping.HeaderMapper" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Specifies a reference to org.springframework.integration.mapping.HeaderMapper
|
||||
implementation bean. Only one of 'header-mapper' or 'mapped-request-headers' attributes
|
||||
can be provided.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="mapped-request-headers" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
Comma-separated list of names of HttpHeaders to be mapped from the HTTP request into the MessageHeaders.
|
||||
This can only be provided if the 'header-mapper' reference is not being set directly. The values in
|
||||
this list can also be simple patterns to be matched against the header names (e.g. "foo*" or "*foo").
|
||||
The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Request headers.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="error-channel" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.messaging.MessageChannel" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
The MessagingGateway's 'error-channel' where to send an ErrorMessage in case
|
||||
of Exception is caused from original message flow.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="codec-configurer" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.http.codec.ServerCodecConfigurer" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
A 'ServerCodecConfigurer' for the request readers and response writers.
|
||||
By default the 'ServerCodecConfigurer#create()' factory is used.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="requested-content-type-resolver" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.web.reactive.accept.RequestedContentTypeResolver" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
A 'RequestedContentTypeResolver' strategy to resolve the requested media types
|
||||
for a 'ServerWebExchange'.
|
||||
A 'HeaderContentTypeResolver' is used by default.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="reactive-adapter-registry" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.core.ReactiveAdapterRegistry" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
A 'ReactiveAdapterRegistry' registry of adapters to adapt a
|
||||
Reactive Streams 'Publisher' to/from.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:attributeGroup>
|
||||
|
||||
<xsd:element name="outbound-channel-adapter">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
@@ -177,7 +486,7 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="reply-to-flux" default="false">
|
||||
<xsd:attribute name="reply-payload-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
|
||||
@@ -190,6 +499,20 @@
|
||||
<xsd:union memberTypes="xsd:boolean xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="body-extractor" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.web.reactive.function.BodyExtractor"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
A reference to an org.springframework.web.reactive.function.BodyExtractor bean
|
||||
which is used to to extract the content from the response.
|
||||
Must be used instead of 'expected-response-type(-expression)'
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans
|
||||
xmlns="http://www.springframework.org/schema/integration/webflux"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:si="http://www.springframework.org/schema/integration"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/integration/webflux http://www.springframework.org/schema/integration/webflux/spring-integration-webflux.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<si:channel id="requests"/>
|
||||
|
||||
<inbound-channel-adapter id="reactiveMinimalConfig" channel="requests"/>
|
||||
|
||||
<inbound-channel-adapter id="reactiveFullConfig" channel="requests"
|
||||
path="test1"
|
||||
auto-startup="false"
|
||||
phase="101"
|
||||
request-payload-type="byte[]"
|
||||
error-channel="errorChannel"
|
||||
payload-expression="payload"
|
||||
supported-methods="PUT"
|
||||
status-code-expression="'202'"
|
||||
header-mapper="headerMapper"
|
||||
codec-configurer="codecConfigurer"
|
||||
reactive-adapter-registry="reactiveAdapterRegistry"
|
||||
requested-content-type-resolver="requestedContentTypeResolver">
|
||||
<request-mapping headers="foo"/>
|
||||
<cross-origin origin="foo"
|
||||
method="PUT"/>
|
||||
<header name="foo" expression="'foo'"/>
|
||||
</inbound-channel-adapter>
|
||||
|
||||
<beans:bean id="headerMapper" class="org.mockito.Mockito" factory-method="mock">
|
||||
<beans:constructor-arg value="org.springframework.integration.mapping.HeaderMapper"/>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="codecConfigurer" class="org.mockito.Mockito" factory-method="mock">
|
||||
<beans:constructor-arg value="org.springframework.http.codec.ServerCodecConfigurer"/>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="requestedContentTypeResolver" class="org.mockito.Mockito" factory-method="mock">
|
||||
<beans:constructor-arg value="org.springframework.web.reactive.accept.RequestedContentTypeResolver"/>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="reactiveAdapterRegistry" class="org.springframework.core.ReactiveAdapterRegistry"/>
|
||||
|
||||
</beans:beans>
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.webflux.config;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.integration.http.inbound.CrossOrigin;
|
||||
import org.springframework.integration.mapping.HeaderMapper;
|
||||
import org.springframework.integration.webflux.inbound.WebFluxInboundEndpoint;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0.1
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@DirtiesContext
|
||||
public class WebFluxInboundChannelAdapterParserTests {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("reactiveMinimalConfig")
|
||||
private WebFluxInboundEndpoint reactiveMinimalConfig;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("reactiveFullConfig")
|
||||
private WebFluxInboundEndpoint reactiveFullConfig;
|
||||
|
||||
@Autowired
|
||||
private MessageChannel requests;
|
||||
|
||||
@Autowired
|
||||
private HeaderMapper<?> headerMapper;
|
||||
|
||||
@Autowired
|
||||
private ServerCodecConfigurer serverCodecConfigurer;
|
||||
|
||||
@Autowired
|
||||
private RequestedContentTypeResolver requestedContentTypeResolver;
|
||||
|
||||
@Autowired
|
||||
private ReactiveAdapterRegistry reactiveAdapterRegistry;
|
||||
|
||||
@Test
|
||||
public void reactiveMinimalConfig() {
|
||||
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(this.reactiveMinimalConfig);
|
||||
assertSame(this.requests, endpointAccessor.getPropertyValue("requestChannel"));
|
||||
assertTrue((boolean) endpointAccessor.getPropertyValue("autoStartup"));
|
||||
assertFalse((boolean) endpointAccessor.getPropertyValue("expectReply"));
|
||||
assertNull(endpointAccessor.getPropertyValue("statusCodeExpression"));
|
||||
assertNull(endpointAccessor.getPropertyValue("payloadExpression"));
|
||||
assertNull(endpointAccessor.getPropertyValue("headerExpressions"));
|
||||
assertNull(endpointAccessor.getPropertyValue("crossOrigin"));
|
||||
assertNull(endpointAccessor.getPropertyValue("requestPayloadType"));
|
||||
|
||||
assertNotSame(this.headerMapper, endpointAccessor.getPropertyValue("headerMapper"));
|
||||
assertNotSame(this.serverCodecConfigurer, endpointAccessor.getPropertyValue("codecConfigurer"));
|
||||
assertNotSame(this.requestedContentTypeResolver, endpointAccessor.getPropertyValue("requestedContentTypeResolver"));
|
||||
assertNotSame(this.reactiveAdapterRegistry, endpointAccessor.getPropertyValue("adapterRegistry"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void reactiveFullConfig() {
|
||||
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(this.reactiveFullConfig);
|
||||
assertSame(this.requests, endpointAccessor.getPropertyValue("requestChannel"));
|
||||
assertNotNull(endpointAccessor.getPropertyValue("errorChannel"));
|
||||
assertFalse((boolean) endpointAccessor.getPropertyValue("autoStartup"));
|
||||
assertEquals(101, endpointAccessor.getPropertyValue("phase"));
|
||||
assertFalse((boolean) endpointAccessor.getPropertyValue("expectReply"));
|
||||
|
||||
assertEquals("'202'",
|
||||
((SpelExpression) endpointAccessor.getPropertyValue("statusCodeExpression")).getExpressionString());
|
||||
|
||||
assertEquals("payload",
|
||||
((SpelExpression) endpointAccessor.getPropertyValue("payloadExpression")).getExpressionString());
|
||||
|
||||
Map<String, Expression> headerExpressions =
|
||||
(Map<String, Expression>) endpointAccessor.getPropertyValue("headerExpressions");
|
||||
|
||||
assertTrue(headerExpressions.containsKey("foo"));
|
||||
|
||||
assertEquals("foo", headerExpressions.get("foo").getValue());
|
||||
|
||||
CrossOrigin crossOrigin = (CrossOrigin) endpointAccessor.getPropertyValue("crossOrigin");
|
||||
assertNotNull(crossOrigin);
|
||||
assertArrayEquals(new String[] { "foo" }, crossOrigin.getOrigin());
|
||||
|
||||
assertEquals(ResolvableType.forClass(byte[].class), endpointAccessor.getPropertyValue("requestPayloadType"));
|
||||
|
||||
assertSame(this.headerMapper, endpointAccessor.getPropertyValue("headerMapper"));
|
||||
assertSame(this.serverCodecConfigurer, endpointAccessor.getPropertyValue("codecConfigurer"));
|
||||
assertSame(this.requestedContentTypeResolver, endpointAccessor.getPropertyValue("requestedContentTypeResolver"));
|
||||
assertSame(this.reactiveAdapterRegistry, endpointAccessor.getPropertyValue("adapterRegistry"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans
|
||||
xmlns="http://www.springframework.org/schema/integration/webflux"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:si="http://www.springframework.org/schema/integration"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/integration/webflux http://www.springframework.org/schema/integration/webflux/spring-integration-webflux.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<si:channel id="requests"/>
|
||||
|
||||
<inbound-gateway id="reactiveMinimalConfig" request-channel="requests"/>
|
||||
|
||||
<inbound-gateway id="reactiveFullConfig" request-channel="requests"
|
||||
path="test1"
|
||||
auto-startup="false"
|
||||
phase="101"
|
||||
request-payload-type="byte[]"
|
||||
error-channel="errorChannel"
|
||||
payload-expression="payload"
|
||||
supported-methods="PUT"
|
||||
reply-timeout-status-code-expression="'504'"
|
||||
header-mapper="headerMapper"
|
||||
codec-configurer="codecConfigurer"
|
||||
reactive-adapter-registry="reactiveAdapterRegistry"
|
||||
requested-content-type-resolver="requestedContentTypeResolver">
|
||||
<request-mapping headers="foo"/>
|
||||
<cross-origin origin="foo"
|
||||
method="PUT"/>
|
||||
<header name="foo" expression="'foo'"/>
|
||||
</inbound-gateway>
|
||||
|
||||
<beans:bean id="headerMapper" class="org.mockito.Mockito" factory-method="mock">
|
||||
<beans:constructor-arg value="org.springframework.integration.mapping.HeaderMapper"/>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="codecConfigurer" class="org.mockito.Mockito" factory-method="mock">
|
||||
<beans:constructor-arg value="org.springframework.http.codec.ServerCodecConfigurer"/>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="requestedContentTypeResolver" class="org.mockito.Mockito" factory-method="mock">
|
||||
<beans:constructor-arg value="org.springframework.web.reactive.accept.RequestedContentTypeResolver"/>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="reactiveAdapterRegistry" class="org.springframework.core.ReactiveAdapterRegistry"/>
|
||||
|
||||
</beans:beans>
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.webflux.config;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.integration.http.inbound.CrossOrigin;
|
||||
import org.springframework.integration.mapping.HeaderMapper;
|
||||
import org.springframework.integration.webflux.inbound.WebFluxInboundEndpoint;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0.1
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@DirtiesContext
|
||||
public class WebFluxInboundGatewayParserTests {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("reactiveMinimalConfig")
|
||||
private WebFluxInboundEndpoint reactiveMinimalConfig;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("reactiveFullConfig")
|
||||
private WebFluxInboundEndpoint reactiveFullConfig;
|
||||
|
||||
@Autowired
|
||||
private MessageChannel requests;
|
||||
|
||||
@Autowired
|
||||
private HeaderMapper<?> headerMapper;
|
||||
|
||||
@Autowired
|
||||
private ServerCodecConfigurer serverCodecConfigurer;
|
||||
|
||||
@Autowired
|
||||
private RequestedContentTypeResolver requestedContentTypeResolver;
|
||||
|
||||
@Autowired
|
||||
private ReactiveAdapterRegistry reactiveAdapterRegistry;
|
||||
|
||||
@Test
|
||||
public void reactiveMinimalConfig() {
|
||||
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(this.reactiveMinimalConfig);
|
||||
assertSame(this.requests, endpointAccessor.getPropertyValue("requestChannel"));
|
||||
assertTrue((boolean) endpointAccessor.getPropertyValue("autoStartup"));
|
||||
assertTrue((boolean) endpointAccessor.getPropertyValue("expectReply"));
|
||||
assertNull(endpointAccessor.getPropertyValue("statusCodeExpression"));
|
||||
assertNull(endpointAccessor.getPropertyValue("payloadExpression"));
|
||||
assertNull(endpointAccessor.getPropertyValue("headerExpressions"));
|
||||
assertNull(endpointAccessor.getPropertyValue("crossOrigin"));
|
||||
assertNull(endpointAccessor.getPropertyValue("requestPayloadType"));
|
||||
|
||||
assertNotSame(this.headerMapper, endpointAccessor.getPropertyValue("headerMapper"));
|
||||
assertNotSame(this.serverCodecConfigurer, endpointAccessor.getPropertyValue("codecConfigurer"));
|
||||
assertNotSame(this.requestedContentTypeResolver, endpointAccessor.getPropertyValue("requestedContentTypeResolver"));
|
||||
assertNotSame(this.reactiveAdapterRegistry, endpointAccessor.getPropertyValue("adapterRegistry"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void reactiveFullConfig() {
|
||||
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(this.reactiveFullConfig);
|
||||
assertSame(this.requests, endpointAccessor.getPropertyValue("requestChannel"));
|
||||
assertNotNull(endpointAccessor.getPropertyValue("errorChannel"));
|
||||
assertFalse((boolean) endpointAccessor.getPropertyValue("autoStartup"));
|
||||
assertEquals(101, endpointAccessor.getPropertyValue("phase"));
|
||||
assertTrue((boolean) endpointAccessor.getPropertyValue("expectReply"));
|
||||
|
||||
assertEquals("'504'",
|
||||
((SpelExpression) endpointAccessor.getPropertyValue("statusCodeExpression")).getExpressionString());
|
||||
|
||||
assertEquals("payload",
|
||||
((SpelExpression) endpointAccessor.getPropertyValue("payloadExpression")).getExpressionString());
|
||||
|
||||
Map<String, Expression> headerExpressions =
|
||||
(Map<String, Expression>) endpointAccessor.getPropertyValue("headerExpressions");
|
||||
|
||||
assertTrue(headerExpressions.containsKey("foo"));
|
||||
|
||||
assertEquals("foo", headerExpressions.get("foo").getValue());
|
||||
|
||||
CrossOrigin crossOrigin = (CrossOrigin) endpointAccessor.getPropertyValue("crossOrigin");
|
||||
assertNotNull(crossOrigin);
|
||||
assertArrayEquals(new String[] { "foo" }, crossOrigin.getOrigin());
|
||||
|
||||
assertEquals(ResolvableType.forClass(byte[].class), endpointAccessor.getPropertyValue("requestPayloadType"));
|
||||
|
||||
assertSame(this.headerMapper, endpointAccessor.getPropertyValue("headerMapper"));
|
||||
assertSame(this.serverCodecConfigurer, endpointAccessor.getPropertyValue("codecConfigurer"));
|
||||
assertSame(this.requestedContentTypeResolver, endpointAccessor.getPropertyValue("requestedContentTypeResolver"));
|
||||
assertSame(this.reactiveAdapterRegistry, endpointAccessor.getPropertyValue("adapterRegistry"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,8 +36,12 @@
|
||||
order="77"
|
||||
auto-startup="false"
|
||||
transfer-cookies="true"
|
||||
reply-to-flux="true">
|
||||
reply-payload-to-flux="true"
|
||||
body-extractor="bodyExtractor">
|
||||
<uri-variable name="foo" expression="headers.bar"/>
|
||||
</outbound-gateway>
|
||||
|
||||
<beans:bean id="bodyExtractor"
|
||||
class="org.springframework.integration.webflux.support.ClientHttpResponseBodyExtractor"/>
|
||||
|
||||
</beans:beans>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
* Copyright 2017-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -40,6 +40,7 @@ import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.reactive.function.BodyExtractor;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
@@ -62,6 +63,9 @@ public class WebFluxOutboundGatewayParserTests {
|
||||
@Autowired
|
||||
private WebClient webClient;
|
||||
|
||||
@Autowired
|
||||
private BodyExtractor<?, ?> bodyExtractor;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@@ -82,7 +86,7 @@ public class WebFluxOutboundGatewayParserTests {
|
||||
assertEquals(Charset.forName("UTF-8"), handlerAccessor.getPropertyValue("charset"));
|
||||
assertEquals(true, handlerAccessor.getPropertyValue("extractPayload"));
|
||||
assertEquals(false, handlerAccessor.getPropertyValue("transferCookies"));
|
||||
assertEquals(false, handlerAccessor.getPropertyValue("replyToFlux"));
|
||||
assertEquals(false, handlerAccessor.getPropertyValue("replyPayloadToFlux"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -124,7 +128,8 @@ public class WebFluxOutboundGatewayParserTests {
|
||||
assertTrue(ObjectUtils.containsElement(mappedRequestHeaders, "requestHeader2"));
|
||||
assertEquals("responseHeader", mappedResponseHeaders[0]);
|
||||
assertEquals(true, handlerAccessor.getPropertyValue("transferCookies"));
|
||||
assertEquals(true, handlerAccessor.getPropertyValue("replyToFlux"));
|
||||
assertEquals(true, handlerAccessor.getPropertyValue("replyPayloadToFlux"));
|
||||
assertSame(this.bodyExtractor, handlerAccessor.getPropertyValue("bodyExtractor"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.integration.webflux.outbound;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
@@ -33,11 +34,13 @@ import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
import org.springframework.integration.channel.FluxMessageChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.http.HttpHeaders;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.integration.webflux.support.ClientHttpResponseBodyExtractor;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandlingException;
|
||||
import org.springframework.messaging.support.ErrorMessage;
|
||||
@@ -215,7 +218,7 @@ public class WebFluxRequestExecutingMessageHandlerTests {
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
reactiveHandler.setOutputChannel(replyChannel);
|
||||
reactiveHandler.setExpectedResponseType(String.class);
|
||||
reactiveHandler.setReplyToFlux(true);
|
||||
reactiveHandler.setReplyPayloadToFlux(true);
|
||||
|
||||
reactiveHandler.handleMessage(MessageBuilder.withPayload("hello, world").build());
|
||||
|
||||
@@ -232,4 +235,55 @@ public class WebFluxRequestExecutingMessageHandlerTests {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientHttpResponseAsReply() {
|
||||
ClientHttpConnector httpConnector = new HttpHandlerConnector((request, response) -> {
|
||||
response.setStatusCode(HttpStatus.OK);
|
||||
response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
|
||||
|
||||
DataBufferFactory bufferFactory = response.bufferFactory();
|
||||
|
||||
Flux<DataBuffer> data =
|
||||
Flux.just(bufferFactory.wrap("foo".getBytes()),
|
||||
bufferFactory.wrap("bar".getBytes()),
|
||||
bufferFactory.wrap("baz".getBytes()));
|
||||
|
||||
return response.writeWith(data)
|
||||
.then(Mono.defer(response::setComplete));
|
||||
});
|
||||
|
||||
WebClient webClient = WebClient.builder()
|
||||
.clientConnector(httpConnector)
|
||||
.build();
|
||||
|
||||
String destinationUri = "http://www.springsource.org/spring-integration";
|
||||
WebFluxRequestExecutingMessageHandler reactiveHandler =
|
||||
new WebFluxRequestExecutingMessageHandler(destinationUri, webClient);
|
||||
|
||||
QueueChannel replyChannel = new QueueChannel();
|
||||
reactiveHandler.setOutputChannel(replyChannel);
|
||||
reactiveHandler.setBodyExtractor(new ClientHttpResponseBodyExtractor());
|
||||
|
||||
reactiveHandler.handleMessage(MessageBuilder.withPayload("hello, world").build());
|
||||
|
||||
Message<?> receive = replyChannel.receive(10_000);
|
||||
|
||||
assertNotNull(receive);
|
||||
|
||||
assertThat(receive.getPayload(), instanceOf(ClientHttpResponse.class));
|
||||
|
||||
ClientHttpResponse response = (ClientHttpResponse) receive.getPayload();
|
||||
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertEquals(MediaType.TEXT_PLAIN, response.getHeaders().getContentType());
|
||||
|
||||
StepVerifier.create(
|
||||
response.getBody()
|
||||
.map(dataBuffer -> new String(dataBuffer.asByteBuffer().array())))
|
||||
.expectNext("foo", "bar", "baz")
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -100,9 +100,13 @@ Together with the `ReactiveChannel` as an `outputChannel`, the `Mono<ClientRespo
|
||||
Otherwise, it is treated as an `async` mode and the `Mono` response is adapted to an `SettableListenableFuture` for an asynchronous reply from the `WebFluxRequestExecutingMessageHandler`.
|
||||
The target payload of the output message depends on the `WebFluxRequestExecutingMessageHandler` configuration.
|
||||
The `setExpectedResponseType(Class<?>)` or `setExpectedResponseTypeExpression(Expression)` identifies the target type of the response body element conversion.
|
||||
If `replyToFlux` is set to `true`, the response body is converted to a `Flux` with the provided `expectedResponseType` for each element and this `Flux` is sent as the payload downstream.
|
||||
If the `replyPayloadToFlux` is set to `true`, the response body is converted to a `Flux` with the provided `expectedResponseType` for each element and this `Flux` is sent as the payload downstream.
|
||||
A <<splitter,splitter>> afterwards can be used to iterate over this `Flux` in a reactive manner.
|
||||
|
||||
In addition a `BodyExtractor<?, ClientHttpResponse>` can be injected into the `WebFluxRequestExecutingMessageHandler` instead of `expectedResponseType` and `replyPayloadToFlux` properties.
|
||||
It can be used for low-level access to the `ClientHttpResponse` and more control over body and HTTP headers conversion.
|
||||
The `ClientHttpResponseBodyExtractor` is provided out-of-the-box as identity function to produce downstream the whole `ClientHttpResponse` and any other possible custom logic.
|
||||
|
||||
Also see <<http-outbound>> for more possible configuration options.
|
||||
|
||||
[[webflux-namespace]]
|
||||
@@ -133,6 +137,48 @@ To include it in your configuration, simply provide the following namespace decl
|
||||
|
||||
==== Inbound
|
||||
|
||||
To configure Spring Integration WebFlux via XML you may use appropriate components from the mentioned `int-webflux` namespace - `inbound-channel-adapter` or `inbound-gateway` according request/response requirements respectively:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<inbound-channel-adapter id="reactiveFullConfig" channel="requests"
|
||||
path="test1"
|
||||
auto-startup="false"
|
||||
phase="101"
|
||||
request-payload-type="byte[]"
|
||||
error-channel="errorChannel"
|
||||
payload-expression="payload"
|
||||
supported-methods="PUT"
|
||||
status-code-expression="'202'"
|
||||
header-mapper="headerMapper"
|
||||
codec-configurer="codecConfigurer"
|
||||
reactive-adapter-registry="reactiveAdapterRegistry"
|
||||
requested-content-type-resolver="requestedContentTypeResolver">
|
||||
<request-mapping headers="foo"/>
|
||||
<cross-origin origin="foo"
|
||||
method="PUT"/>
|
||||
<header name="foo" expression="'foo'"/>
|
||||
</inbound-channel-adapter>
|
||||
|
||||
<inbound-gateway id="reactiveFullConfig" request-channel="requests"
|
||||
path="test1"
|
||||
auto-startup="false"
|
||||
phase="101"
|
||||
request-payload-type="byte[]"
|
||||
error-channel="errorChannel"
|
||||
payload-expression="payload"
|
||||
supported-methods="PUT"
|
||||
reply-timeout-status-code-expression="'504'"
|
||||
header-mapper="headerMapper"
|
||||
codec-configurer="codecConfigurer"
|
||||
reactive-adapter-registry="reactiveAdapterRegistry"
|
||||
requested-content-type-resolver="requestedContentTypeResolver">
|
||||
<request-mapping headers="foo"/>
|
||||
<cross-origin origin="foo"
|
||||
method="PUT"/>
|
||||
<header name="foo" expression="'foo'"/>
|
||||
</inbound-gateway>
|
||||
----
|
||||
|
||||
==== Outbound
|
||||
|
||||
|
||||
Reference in New Issue
Block a user