INT-4208: Support AsycRest in HTTP Module

https://jira.spring.io/browse/INT-4208
https://jira.spring.io/browse/INT-4076

Add `AsyncRestTemplate` support in `HttpRequestExecutingMessageHandler` and corresponding config/dsl support

* Polishing Docs
This commit is contained in:
Shiliang Li
2017-01-31 23:35:28 +08:00
committed by Artem Bilan
parent e06c0e2927
commit 802061985c
20 changed files with 1947 additions and 841 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -35,16 +35,21 @@ import org.springframework.util.xml.DomUtils;
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
* @author Shiliang Li
* @since 2.0.2
*/
abstract class HttpAdapterParsingUtils {
static final String[] REST_TEMPLATE_REFERENCE_ATTRIBUTES = {
static final String[] SYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES = {
"request-factory", "error-handler", "message-converters"
};
static final String[] ASYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES = {
"async-request-factory", "error-handler", "message-converters"
};
static void verifyNoRestTemplateAttributes(Element element, ParserContext parserContext) {
for (String attributeName : REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
for (String attributeName : SYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
if (element.hasAttribute(attributeName)) {
parserContext.getReaderContext().error("When providing a 'rest-template' reference, the '"
+ attributeName + "' attribute is not allowed.",
@@ -53,6 +58,16 @@ abstract class HttpAdapterParsingUtils {
}
}
static void verifyNoAsyncRestTemplateAttributes(Element element, ParserContext parserContext) {
for (String attributeName : ASYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
if (element.hasAttribute(attributeName)) {
parserContext.getReaderContext().error("When providing an 'async-rest-template' reference, the '"
+ attributeName + "' attribute is not allowed.",
parserContext.extractSource(element));
}
}
}
static void configureUriVariableExpressions(BeanDefinitionBuilder builder, ParserContext parserContext, Element element) {
String uriVariablesExpression = element.getAttribute("uri-variables-expression");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -23,6 +23,7 @@ import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHa
*
* @author Mark Fisher
* @author Artem Bilan
* @author Shiliang Li
*
* @since 1.0.2
*/
@@ -33,6 +34,8 @@ public class HttpNamespaceHandler extends AbstractIntegrationNamespaceHandler {
registerBeanDefinitionParser("inbound-gateway", new HttpInboundEndpointParser(true));
registerBeanDefinitionParser("outbound-channel-adapter", new HttpOutboundChannelAdapterParser());
registerBeanDefinitionParser("outbound-gateway", new HttpOutboundGatewayParser());
registerBeanDefinitionParser("outbound-async-channel-adapter", new HttpOutboundChannelAdapterParser());
registerBeanDefinitionParser("outbound-async-gateway", new HttpOutboundGatewayParser());
registerBeanDefinitionParser("graph-controller", new IntegrationGraphControllerParser());
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2017 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.
@@ -23,6 +23,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.http.outbound.AsyncHttpRequestExecutingMessageHandler;
import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler;
import org.springframework.util.StringUtils;
@@ -33,28 +34,51 @@ import org.springframework.util.StringUtils;
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @author Shiliang Li
* @since 2.0
*/
public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser {
@Override
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(HttpRequestExecutingMessageHandler.class);
builder.addPropertyValue("expectReply", false);
HttpAdapterParsingUtils.configureUrlConstructorArg(element, parserContext, builder);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
HttpAdapterParsingUtils.setHttpMethodOrExpression(element, parserContext, builder);
String restTemplate = element.getAttribute("rest-template");
if (StringUtils.hasText(restTemplate)) {
HttpAdapterParsingUtils.verifyNoRestTemplateAttributes(element, parserContext);
builder.addConstructorArgReference(restTemplate);
BeanDefinitionBuilder builder;
boolean async = element.getLocalName().contains("async");
if (async) {
builder = BeanDefinitionBuilder.genericBeanDefinition(AsyncHttpRequestExecutingMessageHandler.class);
}
else {
for (String referenceAttributeName : HttpAdapterParsingUtils.REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
builder = BeanDefinitionBuilder.genericBeanDefinition(HttpRequestExecutingMessageHandler.class);
}
builder.addPropertyValue("expectReply", false);
HttpAdapterParsingUtils.configureUrlConstructorArg(element, parserContext, builder);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
HttpAdapterParsingUtils.setHttpMethodOrExpression(element, parserContext, builder);
if (async) {
String asyncTemplateRef = element.getAttribute("async-rest-template");
if (StringUtils.hasText(asyncTemplateRef)) {
HttpAdapterParsingUtils.verifyNoAsyncRestTemplateAttributes(element, parserContext);
builder.addConstructorArgReference(asyncTemplateRef);
}
else {
for (String referenceAttributeName : HttpAdapterParsingUtils.ASYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
}
}
}
else {
String restTemplateRef = element.getAttribute("rest-template");
if (StringUtils.hasText(restTemplateRef)) {
HttpAdapterParsingUtils.verifyNoRestTemplateAttributes(element, parserContext);
builder.addConstructorArgReference(restTemplateRef);
}
else {
for (String referenceAttributeName : HttpAdapterParsingUtils.SYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2017 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.
@@ -22,6 +22,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractConsumerEndpointParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.http.outbound.AsyncHttpRequestExecutingMessageHandler;
import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler;
import org.springframework.util.StringUtils;
@@ -32,6 +33,7 @@ import org.springframework.util.StringUtils;
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @author Shiliang Li
*/
public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
@@ -42,22 +44,42 @@ public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(HttpRequestExecutingMessageHandler.class);
HttpAdapterParsingUtils.configureUrlConstructorArg(element, parserContext, builder);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
HttpAdapterParsingUtils.setHttpMethodOrExpression(element, parserContext, builder);
String restTemplate = element.getAttribute("rest-template");
if (StringUtils.hasText(restTemplate)) {
HttpAdapterParsingUtils.verifyNoRestTemplateAttributes(element, parserContext);
builder.addConstructorArgReference(restTemplate);
BeanDefinitionBuilder builder;
boolean async = element.getLocalName().contains("async");
if (async) {
builder = BeanDefinitionBuilder.genericBeanDefinition(AsyncHttpRequestExecutingMessageHandler.class);
}
else {
for (String referenceAttributeName : HttpAdapterParsingUtils.REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
builder = BeanDefinitionBuilder.genericBeanDefinition(HttpRequestExecutingMessageHandler.class);
}
HttpAdapterParsingUtils.configureUrlConstructorArg(element, parserContext, builder);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
HttpAdapterParsingUtils.setHttpMethodOrExpression(element, parserContext, builder);
if (async) {
String asyncTemplateRef = element.getAttribute("async-rest-template");
if (StringUtils.hasText(asyncTemplateRef)) {
HttpAdapterParsingUtils.verifyNoAsyncRestTemplateAttributes(element, parserContext);
builder.addConstructorArgReference(asyncTemplateRef);
}
else {
for (String referenceAttributeName : HttpAdapterParsingUtils.ASYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
}
}
}
else {
String restTemplateRef = element.getAttribute("rest-template");
if (StringUtils.hasText(restTemplateRef)) {
HttpAdapterParsingUtils.verifyNoRestTemplateAttributes(element, parserContext);
builder.addConstructorArgReference(restTemplateRef);
}
else {
for (String referenceAttributeName : HttpAdapterParsingUtils.SYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2017 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.http.dsl;
import java.net.URI;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.http.client.AsyncClientHttpRequestFactory;
import org.springframework.integration.expression.ValueExpression;
import org.springframework.integration.http.outbound.AsyncHttpRequestExecutingMessageHandler;
import org.springframework.util.Assert;
import org.springframework.web.client.AsyncRestTemplate;
/**
* The {@link BaseHttpMessageHandlerSpec} implementation for the {@link AsyncHttpRequestExecutingMessageHandler}.
* @author Shiliang Li
* @since 5.0
* @see AsyncHttpRequestExecutingMessageHandler
*/
public class AsyncHttpMessageHandlerSpec
extends BaseHttpMessageHandlerSpec<AsyncHttpMessageHandlerSpec, AsyncHttpRequestExecutingMessageHandler> {
private final AsyncRestTemplate asyncRestTemplate;
AsyncHttpMessageHandlerSpec(URI uri, AsyncRestTemplate asyncRestTemplate) {
this(new ValueExpression<>(uri), asyncRestTemplate);
}
AsyncHttpMessageHandlerSpec(String uri, AsyncRestTemplate asyncRestTemplate) {
this(new LiteralExpression(uri), asyncRestTemplate);
}
AsyncHttpMessageHandlerSpec(Expression uriExpression, AsyncRestTemplate asyncRestTemplate) {
super(new AsyncHttpRequestExecutingMessageHandler(uriExpression, asyncRestTemplate));
this.asyncRestTemplate = asyncRestTemplate;
}
/**
* Set the {@link AsyncClientHttpRequestFactory} for the underlying {@link AsyncRestTemplate}.
* @param asyncRequestFactory The request factory.
* @return the spec
*/
public AsyncHttpMessageHandlerSpec asyncRequestFactory(AsyncClientHttpRequestFactory asyncRequestFactory) {
Assert.isNull(this.asyncRestTemplate,
"the 'requestFactory' must be specified on the provided 'restTemplate': " + this.asyncRestTemplate);
this.target.setAsyncRequestFactory(asyncRequestFactory);
return this;
}
@Override
protected boolean isRestTemplateSet() {
return this.asyncRestTemplate != null;
}
}

View File

@@ -0,0 +1,343 @@
/*
* Copyright 2017 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.http.dsl;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.expression.Expression;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.integration.dsl.ComponentsRegistration;
import org.springframework.integration.dsl.MessageHandlerSpec;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.expression.ValueExpression;
import org.springframework.integration.http.outbound.AbstractHttpRequestExecutingMessageHandler;
import org.springframework.integration.http.support.DefaultHttpHeaderMapper;
import org.springframework.integration.mapping.HeaderMapper;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
/**
* The base {@link MessageHandlerSpec} for {@link AbstractHttpRequestExecutingMessageHandler}s.
*
* @param <S> the target {@link BaseHttpMessageHandlerSpec} implementation type.
* @param <E> the target {@link AbstractHttpRequestExecutingMessageHandler} implementation type.
* @author Artem Bilan
* @author Shiliang Li
* @since 5.0
*/
public abstract class BaseHttpMessageHandlerSpec<S extends BaseHttpMessageHandlerSpec<S, E>, E extends AbstractHttpRequestExecutingMessageHandler>
extends MessageHandlerSpec<S, E>
implements ComponentsRegistration {
private final Map<String, Expression> uriVariableExpressions = new HashMap<>();
private HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.outboundMapper();
private boolean headerMapperExplicitlySet;
public BaseHttpMessageHandlerSpec(E handler) {
this.target = handler;
this.target.setUriVariableExpressions(this.uriVariableExpressions);
this.target.setHeaderMapper(this.headerMapper);
}
S expectReply(boolean expectReply) {
this.target.setExpectReply(expectReply);
return _this();
}
/**
* Specify whether the real URI should be encoded after <code>uriVariables</code>
* expanding and before send request via underlying implementation. The default value is <code>true</code>.
* @param encodeUri true if the URI should be encoded.
* @return the spec
*/
public S encodeUri(boolean encodeUri) {
this.target.setEncodeUri(encodeUri);
return _this();
}
/**
* Specify the SpEL {@link Expression} to determine {@link HttpMethod} at runtime.
* @param httpMethodExpression The method expression.
* @return the spec
*/
public S httpMethodExpression(Expression httpMethodExpression) {
this.target.setHttpMethodExpression(httpMethodExpression);
return _this();
}
/**
* Specify a {@link Function} to determine {@link HttpMethod} at runtime.
* @param httpMethodFunction The HTTP method {@link Function}.
* @param <P> the payload type.
* @return the spec
*/
public <P> S httpMethodFunction(Function<Message<P>, ?> httpMethodFunction) {
return httpMethodExpression(new FunctionExpression<>(httpMethodFunction));
}
/**
* Specify the {@link HttpMethod} for requests.
* The default method is {@code POST}.
* @param httpMethod the {@link HttpMethod} to use.
* @return the spec
*/
public S httpMethod(HttpMethod httpMethod) {
this.target.setHttpMethod(httpMethod);
return _this();
}
/**
* Specify whether the outbound message's payload should be extracted
* when preparing the request body.
* Otherwise the Message instance itself is serialized.
* The default value is {@code true}.
* @param extractPayload true if the payload should be extracted.
* @return the spec
*/
public S extractPayload(boolean extractPayload) {
this.target.setExtractPayload(extractPayload);
return _this();
}
/**
* Specify the charset name to use for converting String-typed payloads to bytes.
* The default is {@code UTF-8}.
* @param charset The charset.
* @return the spec
*/
public S charset(String charset) {
this.target.setCharset(charset);
return _this();
}
/**
* Specify the expected response type for the REST request.
* @param expectedResponseType The expected type.
* @return the spec
*/
public S expectedResponseType(Class<?> expectedResponseType) {
this.target.setExpectedResponseType(expectedResponseType);
return _this();
}
/**
* Specify a {@link ParameterizedTypeReference} for the expected response type for the REST request.
* @param expectedResponseType The {@link ParameterizedTypeReference} for expected type.
* @return the spec
*/
public S expectedResponseType(ParameterizedTypeReference<?> expectedResponseType) {
return expectedResponseTypeExpression(new ValueExpression<ParameterizedTypeReference<?>>(expectedResponseType));
}
/**
* Specify a SpEL {@link Expression} to determine the type for the expected response
* The returned value of the expression could be an instance of {@link Class} or
* {@link String} representing a fully qualified class name.
* @param expectedResponseTypeExpression The expected response type expression.
* @return the spec
*/
public S expectedResponseTypeExpression(Expression expectedResponseTypeExpression) {
this.target.setExpectedResponseTypeExpression(expectedResponseTypeExpression);
return _this();
}
/**
* Specify a {@link Function} to determine the type for the expected response
* The returned value of the expression could be an instance of {@link Class} or
* {@link String} representing a fully qualified class name.
* @param expectedResponseTypeFunction The expected response type {@link Function}.
* @param <P> the payload type.
* @return the spec
*/
public <P> S expectedResponseTypeFunction(
Function<Message<P>, ?> expectedResponseTypeFunction) {
return expectedResponseTypeExpression(new FunctionExpression<>(expectedResponseTypeFunction));
}
/**
* Set the {@link HeaderMapper} to use when mapping between HTTP headers and {@code MessageHeaders}.
* @param headerMapper The header mapper.
* @return the spec
*/
public S headerMapper(HeaderMapper<HttpHeaders> headerMapper) {
this.headerMapper = headerMapper;
this.target.setHeaderMapper(this.headerMapper);
this.headerMapperExplicitlySet = true;
return _this();
}
/**
* Provide the pattern array for request headers to map.
* @param patterns the patterns for request headers to map.
* @return the spec
* @see DefaultHttpHeaderMapper#setOutboundHeaderNames(String[])
*/
public S mappedRequestHeaders(String... patterns) {
Assert.isTrue(!this.headerMapperExplicitlySet,
"The 'mappedRequestHeaders' must be specified on the provided 'headerMapper': " + this.headerMapper);
((DefaultHttpHeaderMapper) this.headerMapper).setOutboundHeaderNames(patterns);
return _this();
}
/**
* Provide the pattern array for response headers to map.
* @param patterns the patterns for response headers to map.
* @return the current Spec.
* @see DefaultHttpHeaderMapper#setInboundHeaderNames(String[])
*/
public S mappedResponseHeaders(String... patterns) {
Assert.isTrue(!this.headerMapperExplicitlySet,
"The 'mappedResponseHeaders' must be specified on the provided 'headerMapper': " + this.headerMapper);
((DefaultHttpHeaderMapper) this.headerMapper).setInboundHeaderNames(patterns);
return _this();
}
/**
* Set the Map of URI variable expressions to evaluate against the outbound message
* when replacing the variable placeholders in a URI template.
* @param uriVariableExpressions The URI variable expressions.
* @return the current Spec.
* @see AbstractHttpRequestExecutingMessageHandler#setUriVariableExpressions(Map)
*/
public S uriVariableExpressions(Map<String, Expression> uriVariableExpressions) {
this.uriVariableExpressions.clear();
this.uriVariableExpressions.putAll(uriVariableExpressions);
return _this();
}
/**
* Specify a SpEL expression to evaluate a value for the uri template variable.
* @param variable the uri template variable.
* @param value the expression to evaluate value for te uri template variable.
* @return the current Spec.
* @see AbstractHttpRequestExecutingMessageHandler#setUriVariableExpressions(Map)
*/
public S uriVariable(String variable, Expression value) {
this.uriVariableExpressions.put(variable, value);
return _this();
}
/**
* Specify a value for the uri template variable.
* @param variable the uri template variable.
* @param value the expression to evaluate value for te uri template variable.
* @return the current Spec.
* @see AbstractHttpRequestExecutingMessageHandler#setUriVariableExpressions(Map)
*/
public S uriVariable(String variable, String value) {
return uriVariable(variable, PARSER.parseExpression(value));
}
/**
* Specify a {@link Function} to evaluate a value for the uri template variable.
* @param variable the uri template variable.
* @param valueFunction the {@link Function} to evaluate a value for the uri template variable.
* @param <P> the payload type.
* @return the current Spec.
* @see AbstractHttpRequestExecutingMessageHandler#setUriVariableExpressions(Map)
*/
public <P> S uriVariable(String variable, Function<Message<P>, ?> valueFunction) {
return uriVariable(variable, new FunctionExpression<>(valueFunction));
}
/**
* Specify a SpEL expression to evaluate a {@link Map} of URI variables at runtime against request message.
* @param uriVariablesExpression to use.
* @return the current Spec.
* @see AbstractHttpRequestExecutingMessageHandler#setUriVariablesExpression(Expression)
*/
public S uriVariablesExpression(String uriVariablesExpression) {
return uriVariablesExpression(PARSER.parseExpression(uriVariablesExpression));
}
/**
* Specify a SpEL expression to evaluate a {@link Map} of URI variables at runtime against request message.
* @param uriVariablesExpression to use.
* @return the current Spec.
* @see AbstractHttpRequestExecutingMessageHandler#setUriVariablesExpression(Expression)
*/
public S uriVariablesExpression(Expression uriVariablesExpression) {
this.target.setUriVariablesExpression(uriVariablesExpression);
return _this();
}
/**
* Specify a {@link Function} to evaluate a {@link Map} of URI variables at runtime against request message.
* @param uriVariablesFunction the {@link Function} to use.
* @param <P> the payload type.
* @return the current Spec.
* @see AbstractHttpRequestExecutingMessageHandler#setUriVariablesExpression(Expression)
*/
public <P> S uriVariablesFunction(Function<Message<P>, Map<String, ?>> uriVariablesFunction) {
return uriVariablesExpression(new FunctionExpression<>(uriVariablesFunction));
}
/**
* Set to {@code true} if you wish {@code Set-Cookie} header in response to be
* transferred as {@code Cookie} header in subsequent interaction for a message.
* @param transferCookies the transferCookies to set.
* @return the current Spec.
*/
public S transferCookies(boolean transferCookies) {
this.target.setTransferCookies(transferCookies);
return _this();
}
@Override
public Collection<Object> getComponentsToRegister() {
return Collections.singletonList(this.headerMapper);
}
/**
* Set the {@link ResponseErrorHandler} for the underlying {@link RestTemplate}.
* @param errorHandler The error handler.
* @return the spec
*/
public S errorHandler(ResponseErrorHandler errorHandler) {
Assert.isTrue(this.isRestTemplateSet(),
"the 'errorHandler' must be specified on the provided 'restTemplate'");
this.target.setErrorHandler(errorHandler);
return _this();
}
/**
* Set a list of {@link HttpMessageConverter}s to be used by the underlying {@link RestTemplate}.
* Converters configured via this method will override the default converters.
* @param messageConverters The message converters.
* @return the spec
*/
public S messageConverters(HttpMessageConverter<?>... messageConverters) {
Assert.isTrue(!isRestTemplateSet(), "the 'messageConverters' must be specified on the provided restTemplate");
this.target.setMessageConverters(Arrays.asList(messageConverters));
return _this();
}
protected abstract boolean isRestTemplateSet();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -27,12 +27,14 @@ import org.springframework.integration.http.inbound.HttpRequestHandlingMessaging
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.RestTemplate;
/**
* The HTTP components Factory.
*
* @author Artem Bilan
* @author Shiliang Li
*
* @since 5.0
*/
@@ -125,6 +127,93 @@ public final class Http {
return new HttpMessageHandlerSpec(uriExpression, restTemplate).expectReply(false);
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for one-way adapter based on provided {@link URI}.
* @param uri the {@link URI} to send requests.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static AsyncHttpMessageHandlerSpec outboundAsyncChannelAdapter(URI uri) {
return outboundAsyncChannelAdapter(uri, null);
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for one-way adapter based on provided {@code uri}.
* @param uri the {@code uri} to send requests.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static AsyncHttpMessageHandlerSpec outboundAsyncChannelAdapter(String uri) {
return outboundAsyncChannelAdapter(uri, null);
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for one-way adapter based on provided {@code Function}
* to evaluate target {@code uri} against request message.
* @param uriFunction the {@code Function} to evaluate {@code uri} at runtime.
* @param <P> the expected payload type.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static <P> AsyncHttpMessageHandlerSpec outboundAsyncChannelAdapter(Function<Message<P>, ?> uriFunction) {
return outboundAsyncChannelAdapter(new FunctionExpression<>(uriFunction));
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for one-way adapter based on provided SpEL {@link Expression}
* to evaluate target {@code uri} against request message.
* @param uriExpression the SpEL {@link Expression} to evaluate {@code uri} at runtime.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static AsyncHttpMessageHandlerSpec outboundAsyncChannelAdapter(Expression uriExpression) {
return outboundAsyncChannelAdapter(uriExpression, null);
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for one-way adapter
* based on provided {@link URI} and {@link AsyncRestTemplate}.
* @param uri the {@link URI} to send requests.
* @param asyncRestTemplate {@link AsyncRestTemplate} to use.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static AsyncHttpMessageHandlerSpec outboundAsyncChannelAdapter(URI uri, AsyncRestTemplate asyncRestTemplate) {
return new AsyncHttpMessageHandlerSpec(uri, asyncRestTemplate).expectReply(false);
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for one-way adapter
* based on provided {@code uri} and {@link AsyncRestTemplate}.
* @param uri the {@code uri} to send requests.
* @param asyncRestTemplate {@link AsyncRestTemplate} to use.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static AsyncHttpMessageHandlerSpec outboundAsyncChannelAdapter(String uri, AsyncRestTemplate asyncRestTemplate) {
return new AsyncHttpMessageHandlerSpec(uri, asyncRestTemplate).expectReply(false);
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for one-way adapter
* based on provided {@code Function} to evaluate target {@code uri} against request message
* and {@link AsyncRestTemplate} for HTTP exchanges.
* @param uriFunction the {@code Function} to evaluate {@code uri} at runtime.
* @param asyncRestTemplate {@link AsyncRestTemplate} to use.
* @param <P> the expected payload type.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static <P> AsyncHttpMessageHandlerSpec outboundAsyncChannelAdapter(Function<Message<P>, ?> uriFunction,
AsyncRestTemplate asyncRestTemplate) {
return outboundAsyncChannelAdapter(new FunctionExpression<>(uriFunction), asyncRestTemplate);
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for one-way adapter
* based on provided SpEL {@link Expression} to evaluate target {@code uri}
* against request message and {@link AsyncRestTemplate} for HTTP exchanges.
* @param uriExpression the SpEL {@link Expression} to evaluate {@code uri} at runtime.
* @param asyncRestTemplate {@link AsyncRestTemplate} to use.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static AsyncHttpMessageHandlerSpec outboundAsyncChannelAdapter(Expression uriExpression, AsyncRestTemplate asyncRestTemplate) {
return new AsyncHttpMessageHandlerSpec(uriExpression, asyncRestTemplate).expectReply(false);
}
/**
* Create an {@link HttpMessageHandlerSpec} builder for request-reply gateway based on provided {@link URI}.
* @param uri the {@link URI} to send requests.
@@ -212,6 +301,93 @@ public final class Http {
return new HttpMessageHandlerSpec(uriExpression, restTemplate);
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for request-reply gateway based on provided {@link URI}.
* @param uri the {@link URI} to send requests.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static AsyncHttpMessageHandlerSpec outboundAsyncGateway(URI uri) {
return outboundAsyncGateway(uri, null);
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for request-reply gateway based on provided {@code uri}.
* @param uri the {@code uri} to send requests.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static AsyncHttpMessageHandlerSpec outboundAsyncGateway(String uri) {
return outboundAsyncGateway(uri, null);
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for request-reply gateway
* based on provided {@code Function} to evaluate target {@code uri} against request message.
* @param uriFunction the {@code Function} to evaluate {@code uri} at runtime.
* @param <P> the expected payload type.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static <P> AsyncHttpMessageHandlerSpec outboundAsyncGateway(Function<Message<P>, ?> uriFunction) {
return outboundAsyncGateway(new FunctionExpression<>(uriFunction));
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for request-reply gateway
* based on provided SpEL {@link Expression} to evaluate target {@code uri} against request message.
* @param uriExpression the SpEL {@link Expression} to evaluate {@code uri} at runtime.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static AsyncHttpMessageHandlerSpec outboundAsyncGateway(Expression uriExpression) {
return outboundAsyncGateway(uriExpression, null);
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for request-reply gateway
* based on provided {@link URI} and {@link RestTemplate}.
* @param uri the {@link URI} to send requests.
* @param asyncRestTemplate {@link AsyncRestTemplate} to use.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static AsyncHttpMessageHandlerSpec outboundAsyncGateway(URI uri, AsyncRestTemplate asyncRestTemplate) {
return new AsyncHttpMessageHandlerSpec(uri, asyncRestTemplate);
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for request-reply gateway
* based on provided {@code uri} and {@link RestTemplate}.
* @param uri the {@code uri} to send requests.
* @param asyncRestTemplate {@link AsyncRestTemplate} to use.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static AsyncHttpMessageHandlerSpec outboundAsyncGateway(String uri, AsyncRestTemplate asyncRestTemplate) {
return new AsyncHttpMessageHandlerSpec(uri, asyncRestTemplate);
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for request-reply gateway
* based on provided {@code Function} to evaluate target {@code uri} against request message
* and {@link RestTemplate} for HTTP exchanges.
* @param uriFunction the {@code Function} to evaluate {@code uri} at runtime.
* @param asyncRestTemplate {@link AsyncRestTemplate} to use.
* @param <P> the expected payload type.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static <P> AsyncHttpMessageHandlerSpec outboundAsyncGateway(Function<Message<P>, ?> uriFunction,
AsyncRestTemplate asyncRestTemplate) {
return outboundAsyncGateway(new FunctionExpression<>(uriFunction), asyncRestTemplate);
}
/**
* Create an {@link AsyncHttpMessageHandlerSpec} builder for request-reply gateway
* based on provided SpEL {@link Expression} to evaluate target {@code uri}
* against request message and {@link AsyncRestTemplate} for HTTP exchanges.
* @param uriExpression the SpEL {@link Expression} to evaluate {@code uri} at runtime.
* @param asyncRestTemplate {@link AsyncRestTemplate} to use.
* @return the AsyncHttpMessageHandlerSpec instance
*/
public static AsyncHttpMessageHandlerSpec outboundAsyncGateway(Expression uriExpression, AsyncRestTemplate asyncRestTemplate) {
return new AsyncHttpMessageHandlerSpec(uriExpression, asyncRestTemplate);
}
/**
* Create an {@link HttpControllerEndpointSpec} builder for one-way adapter
* based on the provided MVC {@code viewName} and {@code path} array for mapping.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 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.
@@ -17,53 +17,30 @@
package org.springframework.integration.http.dsl;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.integration.dsl.ComponentsRegistration;
import org.springframework.integration.dsl.MessageHandlerSpec;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.expression.ValueExpression;
import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler;
import org.springframework.integration.http.support.DefaultHttpHeaderMapper;
import org.springframework.integration.mapping.HeaderMapper;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
/**
* The {@link MessageHandlerSpec} implementation for the {@link HttpRequestExecutingMessageHandler}.
* The {@link BaseHttpMessageHandlerSpec} implementation for the {@link HttpRequestExecutingMessageHandler}.
*
* @author Artem Bilan
* @author Shiliang Li
*
* @since 5.0
*
* @see HttpRequestExecutingMessageHandler
*/
public class HttpMessageHandlerSpec
extends MessageHandlerSpec<HttpMessageHandlerSpec, HttpRequestExecutingMessageHandler>
implements ComponentsRegistration {
extends BaseHttpMessageHandlerSpec<HttpMessageHandlerSpec, HttpRequestExecutingMessageHandler> {
private final RestTemplate restTemplate;
private final Map<String, Expression> uriVariableExpressions = new HashMap<>();
private HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.outboundMapper();
private boolean headerMapperExplicitlySet;
HttpMessageHandlerSpec(URI uri, RestTemplate restTemplate) {
this(new ValueExpression<>(uri), restTemplate);
}
@@ -73,152 +50,10 @@ public class HttpMessageHandlerSpec
}
HttpMessageHandlerSpec(Expression uriExpression, RestTemplate restTemplate) {
this.target = new HttpRequestExecutingMessageHandler(uriExpression, restTemplate);
this.target.setUriVariableExpressions(this.uriVariableExpressions);
this.target.setHeaderMapper(this.headerMapper);
super(new HttpRequestExecutingMessageHandler(uriExpression, restTemplate));
this.restTemplate = restTemplate;
}
HttpMessageHandlerSpec expectReply(boolean expectReply) {
this.target.setExpectReply(expectReply);
return this;
}
/**
* Specify whether the real URI should be encoded after <code>uriVariables</code>
* expanding and before send request via {@link RestTemplate}. The default value is <code>true</code>.
* @param encodeUri true if the URI should be encoded.
* @return the spec
*/
public HttpMessageHandlerSpec encodeUri(boolean encodeUri) {
this.target.setEncodeUri(encodeUri);
return this;
}
/**
* Specify the SpEL {@link Expression} to determine {@link HttpMethod} at runtime.
* @param httpMethodExpression The method expression.
* @return the spec
*/
public HttpMessageHandlerSpec httpMethodExpression(Expression httpMethodExpression) {
this.target.setHttpMethodExpression(httpMethodExpression);
return this;
}
/**
* Specify a {@link Function} to determine {@link HttpMethod} at runtime.
* @param httpMethodFunction The HTTP method {@link Function}.
* @param <P> the payload type.
* @return the spec
*/
public <P> HttpMessageHandlerSpec httpMethodFunction(Function<Message<P>, ?> httpMethodFunction) {
return httpMethodExpression(new FunctionExpression<>(httpMethodFunction));
}
/**
* Specify the {@link HttpMethod} for requests.
* The default method is {@code POST}.
* @param httpMethod the {@link HttpMethod} to use.
* @return the spec
*/
public HttpMessageHandlerSpec httpMethod(HttpMethod httpMethod) {
this.target.setHttpMethod(httpMethod);
return this;
}
/**
* Specify whether the outbound message's payload should be extracted
* when preparing the request body.
* Otherwise the Message instance itself is serialized.
* The default value is {@code true}.
* @param extractPayload true if the payload should be extracted.
* @return the spec
*/
public HttpMessageHandlerSpec extractPayload(boolean extractPayload) {
this.target.setExtractPayload(extractPayload);
return this;
}
/**
* Specify the charset name to use for converting String-typed payloads to bytes.
* The default is {@code UTF-8}.
* @param charset The charset.
* @return the spec
*/
public HttpMessageHandlerSpec charset(String charset) {
this.target.setCharset(charset);
return this;
}
/**
* Specify the expected response type for the REST request.
* @param expectedResponseType The expected type.
* @return the spec
*/
public HttpMessageHandlerSpec expectedResponseType(Class<?> expectedResponseType) {
this.target.setExpectedResponseType(expectedResponseType);
return this;
}
/**
* Specify a {@link ParameterizedTypeReference} for the expected response type for the REST request.
* @param expectedResponseType The {@link ParameterizedTypeReference} for expected type.
* @return the spec
*/
public HttpMessageHandlerSpec expectedResponseType(ParameterizedTypeReference<?> expectedResponseType) {
return expectedResponseTypeExpression(new ValueExpression<ParameterizedTypeReference<?>>(expectedResponseType));
}
/**
* Specify a SpEL {@link Expression} to determine the type for the expected response
* The returned value of the expression could be an instance of {@link Class} or
* {@link String} representing a fully qualified class name.
* @param expectedResponseTypeExpression The expected response type expression.
* @return the spec
*/
public HttpMessageHandlerSpec expectedResponseTypeExpression(Expression expectedResponseTypeExpression) {
this.target.setExpectedResponseTypeExpression(expectedResponseTypeExpression);
return this;
}
/**
* Specify a {@link Function} to determine the type for the expected response
* The returned value of the expression could be an instance of {@link Class} or
* {@link String} representing a fully qualified class name.
* @param expectedResponseTypeFunction The expected response type {@link Function}.
* @param <P> the payload type.
* @return the spec
*/
public <P> HttpMessageHandlerSpec expectedResponseTypeFunction(
Function<Message<P>, ?> expectedResponseTypeFunction) {
return expectedResponseTypeExpression(new FunctionExpression<>(expectedResponseTypeFunction));
}
/**
* Set the {@link ResponseErrorHandler} for the underlying {@link RestTemplate}.
* @param errorHandler The error handler.
* @return the spec
*/
public HttpMessageHandlerSpec errorHandler(ResponseErrorHandler errorHandler) {
Assert.isNull(this.restTemplate,
"the 'errorHandler' must be specified on the provided 'restTemplate': " + this.restTemplate);
this.target.setErrorHandler(errorHandler);
return this;
}
/**
* Set a list of {@link HttpMessageConverter}s to be used by the underlying {@link RestTemplate}.
* Converters configured via this method will override the default converters.
* @param messageConverters The message converters.
* @return the spec
*/
public HttpMessageHandlerSpec messageConverters(HttpMessageConverter<?>... messageConverters) {
Assert.isNull(this.restTemplate,
"the 'messageConverters' must be specified on the provided 'restTemplate': " + this.restTemplate);
this.target.setMessageConverters(Arrays.asList(messageConverters));
return this;
}
/**
* Set the {@link ClientHttpRequestFactory} for the underlying {@link RestTemplate}.
* @param requestFactory The request factory.
@@ -231,138 +66,8 @@ public class HttpMessageHandlerSpec
return this;
}
/**
* Set the {@link HeaderMapper} to use when mapping between HTTP headers and {@code MessageHeaders}.
* @param headerMapper The header mapper.
* @return the spec
*/
public HttpMessageHandlerSpec headerMapper(HeaderMapper<HttpHeaders> headerMapper) {
this.headerMapper = headerMapper;
this.target.setHeaderMapper(this.headerMapper);
this.headerMapperExplicitlySet = true;
return this;
}
/**
* Provide the pattern array for request headers to map.
* @param patterns the patterns for request headers to map.
* @return the spec
* @see DefaultHttpHeaderMapper#setOutboundHeaderNames(String[])
*/
public HttpMessageHandlerSpec mappedRequestHeaders(String... patterns) {
Assert.isTrue(!this.headerMapperExplicitlySet,
"The 'mappedRequestHeaders' must be specified on the provided 'headerMapper': " + this.headerMapper);
((DefaultHttpHeaderMapper) this.headerMapper).setOutboundHeaderNames(patterns);
return this;
}
/**
* Provide the pattern array for response headers to map.
* @param patterns the patterns for response headers to map.
* @return the current Spec.
* @see DefaultHttpHeaderMapper#setInboundHeaderNames(String[])
*/
public HttpMessageHandlerSpec mappedResponseHeaders(String... patterns) {
Assert.isTrue(!this.headerMapperExplicitlySet,
"The 'mappedResponseHeaders' must be specified on the provided 'headerMapper': " + this.headerMapper);
((DefaultHttpHeaderMapper) this.headerMapper).setInboundHeaderNames(patterns);
return this;
}
/**
* Set the Map of URI variable expressions to evaluate against the outbound message
* when replacing the variable placeholders in a URI template.
* @param uriVariableExpressions The URI variable expressions.
* @return the current Spec.
* @see HttpRequestExecutingMessageHandler#setUriVariableExpressions(Map)
*/
public HttpMessageHandlerSpec uriVariableExpressions(Map<String, Expression> uriVariableExpressions) {
this.uriVariableExpressions.clear();
this.uriVariableExpressions.putAll(uriVariableExpressions);
return this;
}
/**
* Specify a SpEL expression to evaluate a value for the uri template variable.
* @param variable the uri template variable.
* @param value the expression to evaluate value for te uri template variable.
* @return the current Spec.
* @see HttpRequestExecutingMessageHandler#setUriVariableExpressions(Map)
*/
public HttpMessageHandlerSpec uriVariable(String variable, Expression value) {
this.uriVariableExpressions.put(variable, value);
return this;
}
/**
* Specify a value for the uri template variable.
* @param variable the uri template variable.
* @param value the expression to evaluate value for te uri template variable.
* @return the current Spec.
* @see HttpRequestExecutingMessageHandler#setUriVariableExpressions(Map)
*/
public HttpMessageHandlerSpec uriVariable(String variable, String value) {
return uriVariable(variable, PARSER.parseExpression(value));
}
/**
* Specify a {@link Function} to evaluate a value for the uri template variable.
* @param variable the uri template variable.
* @param valueFunction the {@link Function} to evaluate a value for the uri template variable.
* @param <P> the payload type.
* @return the current Spec.
* @see HttpRequestExecutingMessageHandler#setUriVariableExpressions(Map)
*/
public <P> HttpMessageHandlerSpec uriVariable(String variable, Function<Message<P>, ?> valueFunction) {
return uriVariable(variable, new FunctionExpression<>(valueFunction));
}
/**
* Specify a SpEL expression to evaluate a {@link Map} of URI variables at runtime against request message.
* @param uriVariablesExpression to use.
* @return the current Spec.
* @see HttpRequestExecutingMessageHandler#setUriVariablesExpression(Expression)
*/
public HttpMessageHandlerSpec uriVariablesExpression(String uriVariablesExpression) {
return uriVariablesExpression(PARSER.parseExpression(uriVariablesExpression));
}
/**
* Specify a SpEL expression to evaluate a {@link Map} of URI variables at runtime against request message.
* @param uriVariablesExpression to use.
* @return the current Spec.
* @see HttpRequestExecutingMessageHandler#setUriVariablesExpression(Expression)
*/
public HttpMessageHandlerSpec uriVariablesExpression(Expression uriVariablesExpression) {
this.target.setUriVariablesExpression(uriVariablesExpression);
return this;
}
/**
* Specify a {@link Function} to evaluate a {@link Map} of URI variables at runtime against request message.
* @param uriVariablesFunction the {@link Function} to use.
* @param <P> the payload type.
* @return the current Spec.
* @see HttpRequestExecutingMessageHandler#setUriVariablesExpression(Expression)
*/
public <P> HttpMessageHandlerSpec uriVariablesFunction(Function<Message<P>, Map<String, ?>> uriVariablesFunction) {
return uriVariablesExpression(new FunctionExpression<>(uriVariablesFunction));
}
/**
* Set to {@code true} if you wish {@code Set-Cookie} header in response to be
* transferred as {@code Cookie} header in subsequent interaction for a message.
* @param transferCookies the transferCookies to set.
* @return the current Spec.
*/
public HttpMessageHandlerSpec transferCookies(boolean transferCookies) {
this.target.setTransferCookies(transferCookies);
return this;
}
@Override
public Collection<Object> getComponentsToRegister() {
return Collections.singletonList(this.headerMapper);
protected boolean isRestTemplateSet() {
return this.restTemplate != null;
}
}

View File

@@ -0,0 +1,551 @@
/*
* Copyright 2017 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.http.outbound;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.transform.Source;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.integration.expression.ExpressionEvalMap;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.expression.ValueExpression;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.http.support.DefaultHttpHeaderMapper;
import org.springframework.integration.mapping.HeaderMapper;
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Base class for http outbound adapter/gateway.
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Gunnar Hillert
* @author Artem Bilan
* @author Wallace Wadge
* @author Shiliang Li
* @since 5.0
*/
public abstract class AbstractHttpRequestExecutingMessageHandler extends AbstractReplyProducingMessageHandler {
private final Map<String, Expression> uriVariableExpressions = new HashMap<>();
private volatile StandardEvaluationContext evaluationContext;
private final Expression uriExpression;
private volatile boolean encodeUri = true;
private volatile Expression httpMethodExpression = new ValueExpression<>(HttpMethod.POST);
private volatile boolean expectReply = true;
private volatile Expression expectedResponseTypeExpression;
private volatile boolean extractPayload = true;
private volatile boolean extractPayloadExplicitlySet = false;
private volatile Charset charset = Charset.forName("UTF-8");
private volatile boolean transferCookies = false;
private volatile HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.outboundMapper();
private volatile Expression uriVariablesExpression;
public AbstractHttpRequestExecutingMessageHandler(Expression uriExpression) {
Assert.notNull(uriExpression, "URI Expression is required");
this.uriExpression = uriExpression;
}
/**
* Specify whether the real URI should be encoded after <code>uriVariables</code>
* expanding and before send request via {@link RestTemplate}. The default value is <code>true</code>.
*
* @param encodeUri true if the URI should be encoded.
*
* @see UriComponentsBuilder
*/
public void setEncodeUri(boolean encodeUri) {
this.encodeUri = encodeUri;
}
/**
* Specify the SpEL {@link Expression} to determine {@link HttpMethod} at runtime.
* @param httpMethodExpression The method expression.
*/
public void setHttpMethodExpression(Expression httpMethodExpression) {
Assert.notNull(httpMethodExpression, "'httpMethodExpression' must not be null");
this.httpMethodExpression = httpMethodExpression;
}
/**
* Specify the {@link HttpMethod} for requests.
* The default method is {@code POST}.
* @param httpMethod The method.
*/
public void setHttpMethod(HttpMethod httpMethod) {
Assert.notNull(httpMethod, "'httpMethod' must not be null");
this.httpMethodExpression = new ValueExpression<HttpMethod>(httpMethod);
}
/**
* Specify whether the outbound message's payload should be extracted
* when preparing the request body.
* Otherwise the Message instance itself is serialized.
* The default value is {@code true}.
* @param extractPayload true if the payload should be extracted.
*/
public void setExtractPayload(boolean extractPayload) {
this.extractPayload = extractPayload;
this.extractPayloadExplicitlySet = true;
}
/**
* Specify the charset name to use for converting String-typed payloads to bytes.
* The default is {@code UTF-8}.
* @param charset The charset.
*/
public void setCharset(String charset) {
Assert.isTrue(Charset.isSupported(charset), "unsupported charset '" + charset + "'");
this.charset = Charset.forName(charset);
}
/**
* @return whether a reply Message is expected.
* @see AbstractHttpRequestExecutingMessageHandler#setExpectReply(boolean)
*/
public boolean getExpectReply() {
return this.expectReply;
}
/**
* Specify whether a reply Message is expected. If not, this handler will simply return null for a
* successful response or throw an Exception for a non-successful response. The default is true.
*
* @param expectReply true if a reply is expected.
*/
public void setExpectReply(boolean expectReply) {
this.expectReply = expectReply;
}
/**
* Specify the expected response type for the REST request
* otherwise the default response type is {@link ResponseEntity} and will
* be returned as a payload of the reply Message.
* To take advantage of the HttpMessageConverters
* registered on this adapter, provide a different type).
*
* @param expectedResponseType The expected type.
*
* Also see {@link #setExpectedResponseTypeExpression(Expression)}
*/
public void setExpectedResponseType(Class<?> expectedResponseType) {
Assert.notNull(expectedResponseType, "'expectedResponseType' must not be null");
this.expectedResponseTypeExpression = new ValueExpression<Class<?>>(expectedResponseType);
}
/**
* Specify the {@link Expression} to determine the type for the expected response
* The returned value of the expression could be an instance of {@link Class} or
* {@link String} representing a fully qualified class name.
* @param expectedResponseTypeExpression The expected response type expression.
* Also see {@link #setExpectedResponseType}
*/
public void setExpectedResponseTypeExpression(Expression expectedResponseTypeExpression) {
this.expectedResponseTypeExpression = expectedResponseTypeExpression;
}
/**
* Set the {@link ResponseErrorHandler} for the underlying implementation.
*
* @param errorHandler The error handler.
*/
public abstract void setErrorHandler(ResponseErrorHandler errorHandler);
/**
* Set a list of {@link HttpMessageConverter}s to be used by the underlying implementation.
* Converters configured via this method will override the default converters.
*
* @param messageConverters The message converters.
*/
public abstract void setMessageConverters(List<HttpMessageConverter<?>> messageConverters);
/**
* Set the {@link HeaderMapper} to use when mapping between HTTP headers and MessageHeaders.
* @param headerMapper The header mapper.
*/
public void setHeaderMapper(HeaderMapper<HttpHeaders> headerMapper) {
Assert.notNull(headerMapper, "headerMapper must not be null");
this.headerMapper = headerMapper;
}
/**
* Set the Map of URI variable expressions to evaluate against the outbound message
* when replacing the variable placeholders in a URI template.
*
* @param uriVariableExpressions The URI variable expressions.
*/
public void setUriVariableExpressions(Map<String, Expression> uriVariableExpressions) {
synchronized (this.uriVariableExpressions) {
this.uriVariableExpressions.clear();
this.uriVariableExpressions.putAll(uriVariableExpressions);
}
}
/**
* Set the {@link Expression} to evaluate against the outbound message; the expression
* must evaluate to a Map of URI variable expressions to evaluate against the outbound message
* when replacing the variable placeholders in a URI template.
*
* @param uriVariablesExpression The URI variables expression.
*/
public void setUriVariablesExpression(Expression uriVariablesExpression) {
this.uriVariablesExpression = uriVariablesExpression;
}
/**
* Set to true if you wish 'Set-Cookie' headers in responses to be
* transferred as 'Cookie' headers in subsequent interactions for
* a message.
*
* @param transferCookies the transferCookies to set.
*/
public void setTransferCookies(boolean transferCookies) {
this.transferCookies = transferCookies;
}
@Override
protected void doInit() {
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
}
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
Object uri = this.uriExpression.getValue(this.evaluationContext, requestMessage);
Assert.state(uri instanceof String || uri instanceof URI,
"'uriExpression' evaluation must result in a 'String' or 'URI' instance, not: "
+ (uri == null ? "null" : uri.getClass()));
URI realUri = null;
try {
HttpMethod httpMethod = this.determineHttpMethod(requestMessage);
if (!this.shouldIncludeRequestBody(httpMethod) && this.extractPayloadExplicitlySet) {
if (logger.isWarnEnabled()) {
logger.warn("The 'extractPayload' attribute has no relevance for the current request since the HTTP Method is '" +
httpMethod + "', and no request body will be sent for that method.");
}
}
Object expectedResponseType = this.determineExpectedResponseType(requestMessage);
HttpEntity<?> httpRequest = this.generateHttpRequest(requestMessage, httpMethod);
Map<String, ?> uriVariables = this.determineUriVariables(requestMessage);
UriComponentsBuilder uriComponentsBuilder = uri instanceof String
? UriComponentsBuilder.fromUriString((String) uri)
: UriComponentsBuilder.fromUri((URI) uri);
UriComponents uriComponents = uriComponentsBuilder.buildAndExpand(uriVariables);
realUri = this.encodeUri ? uriComponents.toUri() : new URI(uriComponents.toUriString());
return this.exchange(realUri, httpMethod, httpRequest, expectedResponseType);
}
catch (MessagingException e) {
throw e;
}
catch (Exception e) {
throw new MessageHandlingException(requestMessage, "HTTP request execution failed for URI ["
+ (realUri == null ? uri : realUri) + "]", e);
}
}
abstract protected Object exchange(URI realUri, HttpMethod httpMethod, HttpEntity<?> httpRequest, Object expectedResponseType);
protected Object getReply(ResponseEntity<?> httpResponse) {
if (this.expectReply) {
HttpHeaders httpHeaders = httpResponse.getHeaders();
Map<String, Object> headers = this.headerMapper.toHeaders(httpHeaders);
if (this.transferCookies) {
this.doConvertSetCookie(headers);
}
AbstractIntegrationMessageBuilder<?> replyBuilder = null;
if (httpResponse.hasBody()) {
Object responseBody = httpResponse.getBody();
replyBuilder = (responseBody instanceof Message<?>) ?
this.getMessageBuilderFactory().fromMessage((Message<?>) responseBody) : this.getMessageBuilderFactory().withPayload(responseBody);
}
else {
replyBuilder = this.getMessageBuilderFactory().withPayload(httpResponse);
}
replyBuilder.setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE, httpResponse.getStatusCode());
return replyBuilder.copyHeaders(headers);
}
return null;
}
/**
* Convert Set-Cookie to Cookie
*/
private void doConvertSetCookie(Map<String, Object> headers) {
String keyName = null;
for (String key : headers.keySet()) {
if (key.equalsIgnoreCase(DefaultHttpHeaderMapper.SET_COOKIE)) {
keyName = key;
break;
}
}
if (keyName != null) {
Object cookies = headers.remove(keyName);
headers.put(DefaultHttpHeaderMapper.COOKIE, cookies);
if (logger.isDebugEnabled()) {
logger.debug("Converted Set-Cookie header to Cookie for: "
+ cookies);
}
}
}
private HttpEntity<?> generateHttpRequest(Message<?> message, HttpMethod httpMethod) throws Exception {
Assert.notNull(message, "message must not be null");
return (this.extractPayload) ? this.createHttpEntityFromPayload(message, httpMethod)
: this.createHttpEntityFromMessage(message, httpMethod);
}
private HttpEntity<?> createHttpEntityFromPayload(Message<?> message, HttpMethod httpMethod) {
Object payload = message.getPayload();
if (payload instanceof HttpEntity<?>) {
// payload is already an HttpEntity, just return it as-is
return (HttpEntity<?>) payload;
}
HttpHeaders httpHeaders = this.mapHeaders(message);
if (!shouldIncludeRequestBody(httpMethod)) {
return new HttpEntity<Object>(httpHeaders);
}
// otherwise, we are creating a request with a body and need to deal with the content-type header as well
if (httpHeaders.getContentType() == null) {
MediaType contentType = (payload instanceof String)
? resolveContentType((String) payload, this.charset)
: resolveContentType(payload);
httpHeaders.setContentType(contentType);
}
if (MediaType.APPLICATION_FORM_URLENCODED.equals(httpHeaders.getContentType()) ||
MediaType.MULTIPART_FORM_DATA.equals(httpHeaders.getContentType())) {
if (!(payload instanceof MultiValueMap)) {
payload = this.convertToMultiValueMap((Map<?, ?>) payload);
}
}
return new HttpEntity<Object>(payload, httpHeaders);
}
private HttpEntity<?> createHttpEntityFromMessage(Message<?> message, HttpMethod httpMethod) {
HttpHeaders httpHeaders = mapHeaders(message);
if (shouldIncludeRequestBody(httpMethod)) {
httpHeaders.setContentType(new MediaType("application", "x-java-serialized-object"));
return new HttpEntity<Object>(message, httpHeaders);
}
return new HttpEntity<Object>(httpHeaders);
}
protected HttpHeaders mapHeaders(Message<?> message) {
HttpHeaders httpHeaders = new HttpHeaders();
this.headerMapper.fromHeaders(message.getHeaders(), httpHeaders);
return httpHeaders;
}
@SuppressWarnings("unchecked")
private MediaType resolveContentType(Object content) {
MediaType contentType = null;
if (content instanceof byte[]) {
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
else if (content instanceof Source) {
contentType = MediaType.TEXT_XML;
}
else if (content instanceof Map) {
// We need to check separately for MULTIPART as well as URLENCODED simply because
// MultiValueMap<Object, Object> is actually valid content for serialization
if (this.isFormData((Map<Object, ?>) content)) {
if (this.isMultipart((Map<String, ?>) content)) {
contentType = MediaType.MULTIPART_FORM_DATA;
}
else {
contentType = MediaType.APPLICATION_FORM_URLENCODED;
}
}
}
if (contentType == null) {
contentType = new MediaType("application", "x-java-serialized-object");
}
return contentType;
}
private boolean shouldIncludeRequestBody(HttpMethod httpMethod) {
return !HttpMethod.GET.equals(httpMethod);
}
private MediaType resolveContentType(String content, Charset charset) {
return new MediaType("text", "plain", charset);
}
private MultiValueMap<Object, Object> convertToMultiValueMap(Map<?, ?> simpleMap) {
LinkedMultiValueMap<Object, Object> multipartValueMap = new LinkedMultiValueMap<Object, Object>();
for (Entry<?, ?> entry : simpleMap.entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue();
if (value instanceof Object[]) {
value = Arrays.asList((Object[]) value);
}
if (value instanceof Collection) {
multipartValueMap.put(key, new ArrayList<Object>((Collection<?>) value));
}
else {
multipartValueMap.add(key, value);
}
}
return multipartValueMap;
}
/**
* If all keys are Strings, and some values are not Strings we'll consider
* the Map to be multipart/form-data
*/
private boolean isMultipart(Map<String, ?> map) {
for (Object value : map.values()) {
if (value != null) {
if (value.getClass().isArray()) {
value = CollectionUtils.arrayToList(value);
}
if (value instanceof Collection) {
Collection<?> cValues = (Collection<?>) value;
for (Object cValue : cValues) {
if (cValue != null && !(cValue instanceof String)) {
return true;
}
}
}
else if (!(value instanceof String)) {
return true;
}
}
}
return false;
}
/**
* If all keys and values are Strings, we'll consider the Map to be form data.
*/
private boolean isFormData(Map<Object, ?> map) {
for (Object key : map.keySet()) {
if (!(key instanceof String)) {
return false;
}
}
return true;
}
private HttpMethod determineHttpMethod(Message<?> requestMessage) {
Object httpMethod = this.httpMethodExpression.getValue(this.evaluationContext, requestMessage);
Assert.state(httpMethod != null && (httpMethod instanceof String || httpMethod instanceof HttpMethod),
"'httpMethodExpression' evaluation must result in an 'HttpMethod' enum or its String representation, " +
"not: " + (httpMethod == null ? "null" : httpMethod.getClass()));
if (httpMethod instanceof HttpMethod) {
return (HttpMethod) httpMethod;
}
else {
try {
return HttpMethod.valueOf((String) httpMethod);
}
catch (Exception e) {
throw new IllegalStateException("The 'httpMethodExpression' returned an invalid HTTP Method value: "
+ httpMethod);
}
}
}
private Object determineExpectedResponseType(Message<?> requestMessage) throws Exception {
Object expectedResponseType = null;
if (this.expectedResponseTypeExpression != null) {
expectedResponseType = this.expectedResponseTypeExpression.getValue(this.evaluationContext, requestMessage);
}
if (expectedResponseType != null) {
Assert.state(expectedResponseType instanceof Class<?>
|| expectedResponseType instanceof String
|| expectedResponseType instanceof ParameterizedTypeReference,
"'expectedResponseType' can be an instance of 'Class<?>', 'String' or 'ParameterizedTypeReference<?>'; "
+ "evaluation resulted in a" + expectedResponseType.getClass() + ".");
if (expectedResponseType instanceof String && StringUtils.hasText((String) expectedResponseType)) {
expectedResponseType = ClassUtils.forName((String) expectedResponseType,
getApplicationContext().getClassLoader());
}
}
return expectedResponseType;
}
@SuppressWarnings("unchecked")
private Map<String, ?> determineUriVariables(Message<?> requestMessage) {
Map<String, ?> expressions;
if (this.uriVariablesExpression != null) {
Object expressionsObject = this.uriVariablesExpression.getValue(this.evaluationContext, requestMessage);
Assert.state(expressionsObject instanceof Map,
"The 'uriVariablesExpression' evaluation must result in a 'Map'.");
expressions = (Map<String, ?>) expressionsObject;
}
else {
expressions = this.uriVariableExpressions;
}
return ExpressionEvalMap.from(expressions)
.usingEvaluationContext(this.evaluationContext)
.withRoot(requestMessage)
.build();
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2017 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.http.outbound;
import java.net.URI;
import java.util.List;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.AsyncClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.integration.expression.ValueExpression;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.SettableListenableFuture;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.ResponseErrorHandler;
/**
* A {@link MessageHandler} implementation that executes HTTP requests by delegating
* to an {@link AsyncRestTemplate} instance.
* @see HttpRequestExecutingMessageHandler
* @author Shiliang Li
* @since 5.0
*/
public class AsyncHttpRequestExecutingMessageHandler extends AbstractHttpRequestExecutingMessageHandler {
private final AsyncRestTemplate asyncRestTemplate;
/**
* Create a handler that will send requests to the provided URI.
*
* @param uri The URI.
*/
public AsyncHttpRequestExecutingMessageHandler(URI uri) {
this(new ValueExpression<>(uri));
}
/**
* Create a handler that will send requests to the provided URI.
*
* @param uri The URI.
*/
public AsyncHttpRequestExecutingMessageHandler(String uri) {
this(uri, null);
}
/**
* Create a handler that will send requests to the provided URI Expression.
*
* @param uriExpression The URI expression.
*/
public AsyncHttpRequestExecutingMessageHandler(Expression uriExpression) {
this(uriExpression, null);
}
/**
* Create a handler that will send requests to the provided URI using a provided AsyncRestTemplate
* @param uri The URI.
* @param asyncRestTemplate The rest template.
*/
public AsyncHttpRequestExecutingMessageHandler(String uri, AsyncRestTemplate asyncRestTemplate) {
this(new LiteralExpression(uri), asyncRestTemplate);
/*
* We'd prefer to do this assertion first, but the compiler doesn't allow it. However,
* it's safe because the literal expression simply wraps the String variable, even
* when null.
*/
Assert.hasText(uri, "URI is required");
}
/**
* Create a handler that will send requests to the provided URI using a provided AsyncRestTemplate
* @param uriExpression A SpEL Expression that can be resolved against the message object and
* {@link BeanFactory}.
* @param asyncRestTemplate The rest template.
*/
public AsyncHttpRequestExecutingMessageHandler(Expression uriExpression, AsyncRestTemplate asyncRestTemplate) {
super(uriExpression);
this.asyncRestTemplate = (asyncRestTemplate == null ? new AsyncRestTemplate() : asyncRestTemplate);
this.setAsync(true);
}
@Override
public String getComponentType() {
return (this.getExpectReply() ? "http:outbound-async-gateway" : "http:outbound-async-channel-adapter");
}
/**
* Set the {@link ResponseErrorHandler} for the underlying {@link AsyncRestTemplate}.
*
* @param errorHandler The error handler.
*
* @see AsyncRestTemplate#setErrorHandler(ResponseErrorHandler)
*/
@Override
public void setErrorHandler(ResponseErrorHandler errorHandler) {
this.asyncRestTemplate.setErrorHandler(errorHandler);
}
/**
* Set a list of {@link HttpMessageConverter}s to be used by the underlying {@link AsyncRestTemplate}.
* Converters configured via this method will override the default converters.
*
* @param messageConverters The message converters.
*
* @see AsyncRestTemplate#setMessageConverters(java.util.List)
*/
@Override
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.asyncRestTemplate.setMessageConverters(messageConverters);
}
/**
* Set the {@link AsyncClientHttpRequestFactory} for the underlying {@link AsyncRestTemplate}.
*
* @param asyncRequestFactory The request factory.
*
* @see AsyncRestTemplate#setAsyncRequestFactory(AsyncClientHttpRequestFactory)
*/
public void setAsyncRequestFactory(AsyncClientHttpRequestFactory asyncRequestFactory) {
this.asyncRestTemplate.setAsyncRequestFactory(asyncRequestFactory);
}
@Override
protected Object exchange(URI uri, HttpMethod httpMethod, HttpEntity<?> httpRequest, Object expectedResponseType) {
SettableListenableFuture<Object> replyMessageFuture = new SettableListenableFuture<>();
ListenableFuture<? extends ResponseEntity<?>> responseFuture;
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
responseFuture = this.asyncRestTemplate.exchange(uri, httpMethod, httpRequest, (ParameterizedTypeReference<?>) expectedResponseType);
}
else {
responseFuture = this.asyncRestTemplate.exchange(uri, httpMethod, httpRequest, (Class<?>) expectedResponseType);
}
responseFuture.addCallback(
result -> replyMessageFuture.set(getReply(result)),
replyMessageFuture::setException);
return replyMessageFuture;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -17,51 +17,24 @@
package org.springframework.integration.http.outbound;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.transform.Source;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.integration.expression.ExpressionEvalMap;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.expression.ValueExpression;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.http.support.DefaultHttpHeaderMapper;
import org.springframework.integration.mapping.HeaderMapper;
import org.springframework.integration.support.AbstractIntegrationMessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
* A {@link MessageHandler} implementation that executes HTTP requests by delegating
@@ -79,40 +52,12 @@ import org.springframework.web.util.UriComponentsBuilder;
* @author Gunnar Hillert
* @author Artem Bilan
* @author Wallace Wadge
* @author Shiliang Li
* @since 2.0
*/
public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMessageHandler {
private final Map<String, Expression> uriVariableExpressions = new HashMap<>();
public class HttpRequestExecutingMessageHandler extends AbstractHttpRequestExecutingMessageHandler {
private final RestTemplate restTemplate;
private volatile StandardEvaluationContext evaluationContext;
private final Expression uriExpression;
private volatile boolean encodeUri = true;
private volatile Expression httpMethodExpression = new ValueExpression<>(HttpMethod.POST);
private volatile boolean expectReply = true;
private volatile Expression expectedResponseTypeExpression;
private volatile boolean extractPayload = true;
private volatile boolean extractPayloadExplicitlySet = false;
private volatile Charset charset = Charset.forName("UTF-8");
private volatile boolean transferCookies = false;
private volatile HeaderMapper<HttpHeaders> headerMapper = DefaultHttpHeaderMapper.outboundMapper();
private volatile Expression uriVariablesExpression;
/**
* Create a handler that will send requests to the provided URI.
*
@@ -162,99 +107,13 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
* @param restTemplate The rest template.
*/
public HttpRequestExecutingMessageHandler(Expression uriExpression, RestTemplate restTemplate) {
Assert.notNull(uriExpression, "URI Expression is required");
super(uriExpression);
this.restTemplate = (restTemplate == null ? new RestTemplate() : restTemplate);
this.uriExpression = uriExpression;
}
/**
* Specify whether the real URI should be encoded after <code>uriVariables</code>
* expanding and before send request via {@link RestTemplate}. The default value is <code>true</code>.
*
* @param encodeUri true if the URI should be encoded.
*
* @see UriComponentsBuilder
*/
public void setEncodeUri(boolean encodeUri) {
this.encodeUri = encodeUri;
}
/**
* Specify the SpEL {@link Expression} to determine {@link HttpMethod} at runtime.
* @param httpMethodExpression The method expression.
*/
public void setHttpMethodExpression(Expression httpMethodExpression) {
Assert.notNull(httpMethodExpression, "'httpMethodExpression' must not be null");
this.httpMethodExpression = httpMethodExpression;
}
/**
* Specify the {@link HttpMethod} for requests.
* The default method is {@code POST}.
* @param httpMethod The method.
*/
public void setHttpMethod(HttpMethod httpMethod) {
Assert.notNull(httpMethod, "'httpMethod' must not be null");
this.httpMethodExpression = new ValueExpression<HttpMethod>(httpMethod);
}
/**
* Specify whether the outbound message's payload should be extracted
* when preparing the request body.
* Otherwise the Message instance itself is serialized.
* The default value is {@code true}.
* @param extractPayload true if the payload should be extracted.
*/
public void setExtractPayload(boolean extractPayload) {
this.extractPayload = extractPayload;
this.extractPayloadExplicitlySet = true;
}
/**
* Specify the charset name to use for converting String-typed payloads to bytes.
* The default is {@code UTF-8}.
* @param charset The charset.
*/
public void setCharset(String charset) {
Assert.isTrue(Charset.isSupported(charset), "unsupported charset '" + charset + "'");
this.charset = Charset.forName(charset);
}
/**
* Specify whether a reply Message is expected. If not, this handler will simply return null for a
* successful response or throw an Exception for a non-successful response. The default is true.
*
* @param expectReply true if a reply is expected.
*/
public void setExpectReply(boolean expectReply) {
this.expectReply = expectReply;
}
/**
* Specify the expected response type for the REST request
* otherwise the default response type is {@link ResponseEntity} and will
* be returned as a payload of the reply Message.
* To take advantage of the HttpMessageConverters
* registered on this adapter, provide a different type).
*
* @param expectedResponseType The expected type.
*
* Also see {@link #setExpectedResponseTypeExpression(Expression)}
*/
public void setExpectedResponseType(Class<?> expectedResponseType) {
Assert.notNull(expectedResponseType, "'expectedResponseType' must not be null");
this.expectedResponseTypeExpression = new ValueExpression<Class<?>>(expectedResponseType);
}
/**
* Specify the {@link Expression} to determine the type for the expected response
* The returned value of the expression could be an instance of {@link Class} or
* {@link String} representing a fully qualified class name.
* @param expectedResponseTypeExpression The expected response type expression.
* Also see {@link #setExpectedResponseType}
*/
public void setExpectedResponseTypeExpression(Expression expectedResponseTypeExpression) {
this.expectedResponseTypeExpression = expectedResponseTypeExpression;
@Override
public String getComponentType() {
return (this.getExpectReply() ? "http:outbound-gateway" : "http:outbound-channel-adapter");
}
/**
@@ -264,6 +123,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
*
* @see RestTemplate#setErrorHandler(ResponseErrorHandler)
*/
@Override
public void setErrorHandler(ResponseErrorHandler errorHandler) {
this.restTemplate.setErrorHandler(errorHandler);
}
@@ -276,19 +136,11 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
*
* @see RestTemplate#setMessageConverters(java.util.List)
*/
@Override
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.restTemplate.setMessageConverters(messageConverters);
}
/**
* Set the {@link HeaderMapper} to use when mapping between HTTP headers and MessageHeaders.
* @param headerMapper The header mapper.
*/
public void setHeaderMapper(HeaderMapper<HttpHeaders> headerMapper) {
Assert.notNull(headerMapper, "headerMapper must not be null");
this.headerMapper = headerMapper;
}
/**
* Set the {@link ClientHttpRequestFactory} for the underlying {@link RestTemplate}.
*
@@ -300,330 +152,15 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
this.restTemplate.setRequestFactory(requestFactory);
}
/**
* Set the Map of URI variable expressions to evaluate against the outbound message
* when replacing the variable placeholders in a URI template.
*
* @param uriVariableExpressions The URI variable expressions.
*/
public void setUriVariableExpressions(Map<String, Expression> uriVariableExpressions) {
synchronized (this.uriVariableExpressions) {
this.uriVariableExpressions.clear();
this.uriVariableExpressions.putAll(uriVariableExpressions);
}
}
/**
* Set the {@link Expression} to evaluate against the outbound message; the expression
* must evaluate to a Map of URI variable expressions to evaluate against the outbound message
* when replacing the variable placeholders in a URI template.
*
* @param uriVariablesExpression The URI variables expression.
*/
public void setUriVariablesExpression(Expression uriVariablesExpression) {
this.uriVariablesExpression = uriVariablesExpression;
}
/**
* Set to true if you wish 'Set-Cookie' headers in responses to be
* transferred as 'Cookie' headers in subsequent interactions for
* a message.
*
* @param transferCookies the transferCookies to set.
*/
public void setTransferCookies(boolean transferCookies) {
this.transferCookies = transferCookies;
}
@Override
public String getComponentType() {
return (this.expectReply ? "http:outbound-gateway" : "http:outbound-channel-adapter");
}
@Override
protected void doInit() {
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
}
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
Object uri = this.uriExpression.getValue(this.evaluationContext, requestMessage);
Assert.state(uri instanceof String || uri instanceof URI,
"'uriExpression' evaluation must result in a 'String' or 'URI' instance, not: "
+ (uri == null ? "null" : uri.getClass()));
URI realUri = null;
try {
HttpMethod httpMethod = this.determineHttpMethod(requestMessage);
if (!this.shouldIncludeRequestBody(httpMethod) && this.extractPayloadExplicitlySet) {
if (logger.isWarnEnabled()) {
logger.warn("The 'extractPayload' attribute has no relevance for the current request since the HTTP Method is '" +
httpMethod + "', and no request body will be sent for that method.");
}
}
Object expectedResponseType = this.determineExpectedResponseType(requestMessage);
HttpEntity<?> httpRequest = this.generateHttpRequest(requestMessage, httpMethod);
Map<String, ?> uriVariables = this.determineUriVariables(requestMessage);
UriComponentsBuilder uriComponentsBuilder = uri instanceof String
? UriComponentsBuilder.fromUriString((String) uri)
: UriComponentsBuilder.fromUri((URI) uri);
UriComponents uriComponents = uriComponentsBuilder.buildAndExpand(uriVariables);
realUri = this.encodeUri ? uriComponents.toUri() : new URI(uriComponents.toUriString());
ResponseEntity<?> httpResponse;
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
httpResponse = this.restTemplate.exchange(realUri, httpMethod, httpRequest, (ParameterizedTypeReference<?>) expectedResponseType);
}
else {
httpResponse = this.restTemplate.exchange(realUri, httpMethod, httpRequest, (Class<?>) expectedResponseType);
}
if (this.expectReply) {
HttpHeaders httpHeaders = httpResponse.getHeaders();
Map<String, Object> headers = this.headerMapper.toHeaders(httpHeaders);
if (this.transferCookies) {
this.doConvertSetCookie(headers);
}
AbstractIntegrationMessageBuilder<?> replyBuilder = null;
if (httpResponse.hasBody()) {
Object responseBody = httpResponse.getBody();
replyBuilder = (responseBody instanceof Message<?>) ?
this.getMessageBuilderFactory().fromMessage((Message<?>) responseBody) : this.getMessageBuilderFactory().withPayload(responseBody);
}
else {
replyBuilder = this.getMessageBuilderFactory().withPayload(httpResponse);
}
replyBuilder.setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE, httpResponse.getStatusCode());
return replyBuilder.copyHeaders(headers).build();
}
return null;
}
catch (MessagingException e) {
throw e;
}
catch (Exception e) {
throw new MessageHandlingException(requestMessage, "HTTP request execution failed for URI ["
+ (realUri == null ? uri : realUri) + "]", e);
}
}
/**
* Convert Set-Cookie to Cookie
*/
private void doConvertSetCookie(Map<String, Object> headers) {
String keyName = null;
for (String key : headers.keySet()) {
if (key.equalsIgnoreCase(DefaultHttpHeaderMapper.SET_COOKIE)) {
keyName = key;
break;
}
}
if (keyName != null) {
Object cookies = headers.remove(keyName);
headers.put(DefaultHttpHeaderMapper.COOKIE, cookies);
if (logger.isDebugEnabled()) {
logger.debug("Converted Set-Cookie header to Cookie for: "
+ cookies);
}
}
}
private HttpEntity<?> generateHttpRequest(Message<?> message, HttpMethod httpMethod) throws Exception {
Assert.notNull(message, "message must not be null");
return (this.extractPayload) ? this.createHttpEntityFromPayload(message, httpMethod)
: this.createHttpEntityFromMessage(message, httpMethod);
}
private HttpEntity<?> createHttpEntityFromPayload(Message<?> message, HttpMethod httpMethod) {
Object payload = message.getPayload();
if (payload instanceof HttpEntity<?>) {
// payload is already an HttpEntity, just return it as-is
return (HttpEntity<?>) payload;
}
HttpHeaders httpHeaders = this.mapHeaders(message);
if (!shouldIncludeRequestBody(httpMethod)) {
return new HttpEntity<Object>(httpHeaders);
}
// otherwise, we are creating a request with a body and need to deal with the content-type header as well
if (httpHeaders.getContentType() == null) {
MediaType contentType = (payload instanceof String)
? resolveContentType((String) payload, this.charset)
: resolveContentType(payload);
httpHeaders.setContentType(contentType);
}
if (MediaType.APPLICATION_FORM_URLENCODED.equals(httpHeaders.getContentType()) ||
MediaType.MULTIPART_FORM_DATA.equals(httpHeaders.getContentType())) {
if (!(payload instanceof MultiValueMap)) {
payload = this.convertToMultiValueMap((Map<?, ?>) payload);
}
}
return new HttpEntity<Object>(payload, httpHeaders);
}
private HttpEntity<?> createHttpEntityFromMessage(Message<?> message, HttpMethod httpMethod) {
HttpHeaders httpHeaders = mapHeaders(message);
if (shouldIncludeRequestBody(httpMethod)) {
httpHeaders.setContentType(new MediaType("application", "x-java-serialized-object"));
return new HttpEntity<Object>(message, httpHeaders);
}
return new HttpEntity<Object>(httpHeaders);
}
protected HttpHeaders mapHeaders(Message<?> message) {
HttpHeaders httpHeaders = new HttpHeaders();
this.headerMapper.fromHeaders(message.getHeaders(), httpHeaders);
return httpHeaders;
}
@SuppressWarnings("unchecked")
private MediaType resolveContentType(Object content) {
MediaType contentType = null;
if (content instanceof byte[]) {
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
else if (content instanceof Source) {
contentType = MediaType.TEXT_XML;
}
else if (content instanceof Map) {
// We need to check separately for MULTIPART as well as URLENCODED simply because
// MultiValueMap<Object, Object> is actually valid content for serialization
if (this.isFormData((Map<Object, ?>) content)) {
if (this.isMultipart((Map<String, ?>) content)) {
contentType = MediaType.MULTIPART_FORM_DATA;
}
else {
contentType = MediaType.APPLICATION_FORM_URLENCODED;
}
}
}
if (contentType == null) {
contentType = new MediaType("application", "x-java-serialized-object");
}
return contentType;
}
private boolean shouldIncludeRequestBody(HttpMethod httpMethod) {
return !HttpMethod.GET.equals(httpMethod);
}
private MediaType resolveContentType(String content, Charset charset) {
return new MediaType("text", "plain", charset);
}
private MultiValueMap<Object, Object> convertToMultiValueMap(Map<?, ?> simpleMap) {
LinkedMultiValueMap<Object, Object> multipartValueMap = new LinkedMultiValueMap<Object, Object>();
for (Entry<?, ?> entry : simpleMap.entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue();
if (value instanceof Object[]) {
value = Arrays.asList((Object[]) value);
}
if (value instanceof Collection) {
multipartValueMap.put(key, new ArrayList<Object>((Collection<?>) value));
}
else {
multipartValueMap.add(key, value);
}
}
return multipartValueMap;
}
/**
* If all keys are Strings, and some values are not Strings we'll consider
* the Map to be multipart/form-data
*/
private boolean isMultipart(Map<String, ?> map) {
for (Object value : map.values()) {
if (value != null) {
if (value.getClass().isArray()) {
value = CollectionUtils.arrayToList(value);
}
if (value instanceof Collection) {
Collection<?> cValues = (Collection<?>) value;
for (Object cValue : cValues) {
if (cValue != null && !(cValue instanceof String)) {
return true;
}
}
}
else if (!(value instanceof String)) {
return true;
}
}
}
return false;
}
/**
* If all keys and values are Strings, we'll consider the Map to be form data.
*/
private boolean isFormData(Map<Object, ?> map) {
for (Object key : map.keySet()) {
if (!(key instanceof String)) {
return false;
}
}
return true;
}
private HttpMethod determineHttpMethod(Message<?> requestMessage) {
Object httpMethod = this.httpMethodExpression.getValue(this.evaluationContext, requestMessage);
Assert.state(httpMethod != null && (httpMethod instanceof String || httpMethod instanceof HttpMethod),
"'httpMethodExpression' evaluation must result in an 'HttpMethod' enum or its String representation, " +
"not: " + (httpMethod == null ? "null" : httpMethod.getClass()));
if (httpMethod instanceof HttpMethod) {
return (HttpMethod) httpMethod;
protected Object exchange(URI uri, HttpMethod httpMethod, HttpEntity<?> httpRequest, Object expectedResponseType) {
ResponseEntity<?> httpResponse;
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest, (ParameterizedTypeReference<?>) expectedResponseType);
}
else {
try {
return HttpMethod.valueOf((String) httpMethod);
}
catch (Exception e) {
throw new IllegalStateException("The 'httpMethodExpression' returned an invalid HTTP Method value: "
+ httpMethod);
}
httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest, (Class<?>) expectedResponseType);
}
return getReply(httpResponse);
}
private Object determineExpectedResponseType(Message<?> requestMessage) throws Exception {
Object expectedResponseType = null;
if (this.expectedResponseTypeExpression != null) {
expectedResponseType = this.expectedResponseTypeExpression.getValue(this.evaluationContext, requestMessage);
}
if (expectedResponseType != null) {
Assert.state(expectedResponseType instanceof Class<?>
|| expectedResponseType instanceof String
|| expectedResponseType instanceof ParameterizedTypeReference,
"'expectedResponseType' can be an instance of 'Class<?>', 'String' or 'ParameterizedTypeReference<?>'; "
+ "evaluation resulted in a" + expectedResponseType.getClass() + ".");
if (expectedResponseType instanceof String && StringUtils.hasText((String) expectedResponseType)) {
expectedResponseType = ClassUtils.forName((String) expectedResponseType,
getApplicationContext().getClassLoader());
}
}
return expectedResponseType;
}
@SuppressWarnings("unchecked")
private Map<String, ?> determineUriVariables(Message<?> requestMessage) {
Map<String, ?> expressions;
if (this.uriVariablesExpression != null) {
Object expressionsObject = this.uriVariablesExpression.getValue(this.evaluationContext, requestMessage);
Assert.state(expressionsObject instanceof Map,
"The 'uriVariablesExpression' evaluation must result in a 'Map'.");
expressions = (Map<String, ?>) expressionsObject;
}
else {
expressions = this.uriVariableExpressions;
}
return ExpressionEvalMap.from(expressions)
.usingEvaluationContext(this.evaluationContext)
.withRoot(requestMessage)
.build();
}
}

View File

@@ -387,6 +387,44 @@
</xsd:choice>
<xsd:attributeGroup ref="integration:channelAdapterAttributes"/>
<xsd:attributeGroup ref="httpOutboundCommonAttributes"/>
<xsd:attributeGroup ref="syncHttpOutboundCommonAttributes"/>
<xsd:attribute name="extract-payload" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation>
Specify whether the outbound message's payload should be extracted
when preparing the request body. Otherwise the Message instance itself
will be serialized.
The default value is 'true'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="outbound-async-channel-adapter">
<xsd:annotation>
<xsd:documentation>
Configures a Consumer Endpoint for the
'org.springframework.integration.http.outbound.AsyncHttpRequestExecutingMessageHandler'
with 'expectReply = false' that sends HTTP requests based on incoming messages.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:choice minOccurs="0" maxOccurs="3">
<xsd:element name="uri-variable" type="uriVariableType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
Specify an expression for URI variable placeholder within 'url'.
This element is mutually exclusive with 'uri-variables-expression' attribute.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType" minOccurs="0" maxOccurs="1" />
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
</xsd:choice>
<xsd:attributeGroup ref="integration:channelAdapterAttributes"/>
<xsd:attributeGroup ref="httpOutboundCommonAttributes"/>
<xsd:attributeGroup ref="asyncHttpOutboundCommonAttributes"/>
<xsd:attribute name="extract-payload" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation>
@@ -488,6 +526,101 @@
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="httpOutboundCommonAttributes"/>
<xsd:attributeGroup ref="syncHttpOutboundCommonAttributes"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="outbound-async-gateway">
<xsd:annotation>
<xsd:documentation>
Configures a Consumer Endpoint for the
'org.springframework.integration.http.outbound.AsyncHttpRequestExecutingMessageHandler'
that sends HTTP requests based on incoming messages and expects HTTP responses.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="gatewayType">
<xsd:choice minOccurs="0" maxOccurs="3">
<xsd:element name="uri-variable" type="uriVariableType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
Specify an expression for URI variable placeholder within 'url'.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="transactional" type="integration:transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="integration:handlerAdviceChainType" minOccurs="0" maxOccurs="1" />
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
</xsd:choice>
<xsd:attribute name="request-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 receiving Message Channel of this endpoint.
</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 HttpHeaders to be mapped from the HTTP response 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_RESPONSE_HEADERS" will match against any of the standard HTTP Response headers.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="extract-request-payload" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specifies whether the outbound message's payload should be extracted
when preparing the request body. Otherwise the Message instance itself
will be serialized.
The default value is 'true'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="transfer-cookies" type="xsd:string" 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. This enables simple
cookie handling where subsequent HTTP interactions in the same message flow can use a cookie
supplied by the server. Default is "false".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="reply-timeout" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Allows you to specify how long this gateway will wait for
the reply message to be sent successfully to the reply channel
before throwing an exception. This attribute only applies when the
channel might block, for example when using a bounded queue channel that
is currently full.
Also, keep in mind that when sending to a DirectChannel, the
invocation will occur in the sender's thread. Therefore,
the failing of the send operation may be caused by other
components further downstream.
The "reply-timeout" attribute maps to the "sendTimeout" property of the
underlying 'MessagingTemplate' instance (org.springframework.integration.core.MessagingTemplate).
The attribute will default, if not specified, to '-1', meaning that
by default, the Gateway will wait indefinitely. The value is
specified in milliseconds.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="httpOutboundCommonAttributes"/>
<xsd:attributeGroup ref="asyncHttpOutboundCommonAttributes"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
@@ -726,6 +859,60 @@
</xsd:attribute>
</xsd:complexType>
<xsd:attributeGroup name="asyncHttpOutboundCommonAttributes">
<xsd:attribute name="async-rest-template" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.web.client.AsyncRestTemplate" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
The reference to org.springframework.web.client.AsyncRestTemplate bean to send the HTTP Request.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="async-request-factory" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a AsyncClientHttpRequestFactory to be used by the underlying RestTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.http.client.AsyncClientHttpRequestFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:attributeGroup name="syncHttpOutboundCommonAttributes">
<xsd:attribute name="rest-template" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.web.client.RestTemplate" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
The reference to org.springframework.web.client.RestTemplate bean to send the HTTP Request.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="request-factory" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a ClientHttpRequestFactory to be used by the underlying RestTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.http.client.ClientHttpRequestFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:attributeGroup name="httpOutboundCommonAttributes">
<xsd:attribute name="url" type="xsd:string" use="optional">
<xsd:annotation>
@@ -773,18 +960,6 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="rest-template" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.web.client.RestTemplate" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
The reference to org.springframework.web.client.RestTemplate bean to send the HTTP Request.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="charset" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
@@ -849,18 +1024,6 @@
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="request-factory" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a ClientHttpRequestFactory to be used by the underlying RestTemplate.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.http.client.ClientHttpRequestFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="error-handler" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[

View File

@@ -16,8 +16,14 @@
<outbound-channel-adapter id="restTemplateConfig" url="http://localhost/test1" channel="requests" rest-template="customRestTemplate"/>
<outbound-async-channel-adapter id="asyncMinimalConfig" url="http://localhost/test1" channel="requests" />
<outbound-async-channel-adapter id="asyncRestTemplateConfig" url="http://localhost/test1" channel="requests" async-rest-template="asyncRestTemplate" />
<beans:bean id="customRestTemplate" class="org.springframework.web.client.RestTemplate"/>
<beans:bean id="asyncRestTemplate" class="org.springframework.web.client.AsyncRestTemplate"/>
<outbound-channel-adapter id="fullConfig"
url="http://localhost/test2/{foo}"
http-method="GET"
@@ -94,6 +100,8 @@
<beans:bean id="testRequestFactory" class="org.springframework.http.client.SimpleClientHttpRequestFactory"/>
<beans:bean id="testAsyncRequestFactory" class="org.springframework.http.client.SimpleClientHttpRequestFactory"/>
<beans:bean id="testErrorHandler" class="org.springframework.integration.http.config.HttpOutboundChannelAdapterParserTests$StubErrorHandler"/>
<util:list id="converterList">

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -41,12 +41,14 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.AsyncClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.integration.http.outbound.AsyncHttpRequestExecutingMessageHandler;
import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
@@ -56,6 +58,7 @@ import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
@@ -65,6 +68,7 @@ import org.springframework.web.client.RestTemplate;
* @author Gunnar Hillert
* @author Artem Bilan
* @author Biju Kunjummen
* @author Shiliang Li
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@@ -80,9 +84,18 @@ public class HttpOutboundChannelAdapterParserTests {
@Autowired @Qualifier("restTemplateConfig")
private AbstractEndpoint restTemplateConfig;
@Autowired @Qualifier("asyncMinimalConfig")
private AbstractEndpoint asyncMinimalConfig;
@Autowired @Qualifier("asyncRestTemplateConfig")
private AbstractEndpoint asyncRestTemplateConfig;
@Autowired @Qualifier("customRestTemplate")
private RestTemplate customRestTemplate;
@Autowired @Qualifier("asyncRestTemplate")
private AsyncRestTemplate asyncRestTemplate;
@Autowired @Qualifier("withUrlAndTemplate")
private AbstractEndpoint withUrlAndTemplate;
@@ -165,6 +178,28 @@ public class HttpOutboundChannelAdapterParserTests {
assertTrue(ObjectUtils.containsElement(mappedRequestHeaders, "requestHeader2"));
}
@Test
public void asyncMinimalConfig() {
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(this.asyncMinimalConfig);
AsyncRestTemplate asyncRestTemplate =
TestUtils.getPropertyValue(this.asyncMinimalConfig, "handler.asyncRestTemplate", AsyncRestTemplate.class);
assertNotSame(this.asyncRestTemplate, asyncRestTemplate);
AsyncHttpRequestExecutingMessageHandler handler = (AsyncHttpRequestExecutingMessageHandler) endpointAccessor.getPropertyValue("handler");
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
assertEquals(false, handlerAccessor.getPropertyValue("expectReply"));
assertEquals(this.applicationContext.getBean("requests"), endpointAccessor.getPropertyValue("inputChannel"));
assertNull(handlerAccessor.getPropertyValue("outputChannel"));
DirectFieldAccessor templateAccessor = new DirectFieldAccessor(handlerAccessor.getPropertyValue("asyncRestTemplate"));
AsyncClientHttpRequestFactory asyncRequestFactory = (AsyncClientHttpRequestFactory)
templateAccessor.getPropertyValue("asyncRequestFactory");
assertTrue(asyncRequestFactory instanceof SimpleClientHttpRequestFactory);
Expression uriExpression = (Expression) handlerAccessor.getPropertyValue("uriExpression");
assertEquals("http://localhost/test1", uriExpression.getValue());
assertEquals(HttpMethod.POST.name(), TestUtils.getPropertyValue(handler, "httpMethodExpression", Expression.class).getExpressionString());
assertEquals(Charset.forName("UTF-8"), handlerAccessor.getPropertyValue("charset"));
assertEquals(true, handlerAccessor.getPropertyValue("extractPayload"));
}
@Test
public void restTemplateConfig() {
RestTemplate restTemplate =
@@ -172,6 +207,14 @@ public class HttpOutboundChannelAdapterParserTests {
assertEquals(customRestTemplate, restTemplate);
}
@Test
public void asyncRestTemplateConfig() {
AsyncRestTemplate asyncRestTemplate = TestUtils.getPropertyValue(
this.asyncRestTemplateConfig,
"handler.asyncRestTemplate", AsyncRestTemplate.class);
assertSame(this.asyncRestTemplate, asyncRestTemplate);
}
@Test(expected = BeanDefinitionParsingException.class)
public void failWithRestTemplateAndRestAttributes() {
new ClassPathXmlApplicationContext("HttpOutboundChannelAdapterParserTests-fail-context.xml", this.getClass())

View File

@@ -14,6 +14,8 @@
<si:channel id="requests"/>
<beans:bean id="asyncRestTemplate" class="org.springframework.web.client.AsyncRestTemplate"/>
<outbound-gateway id="minimalConfig" url="http://localhost/test1" request-channel="requests"/>
<si:channel id="replies">
@@ -40,6 +42,27 @@
<uri-variable name="foo" expression="headers.bar"/>
</outbound-gateway>
<outbound-async-gateway id="asyncMinimalConfig" url="http://localhost/test1" request-channel="requests" async-rest-template="asyncRestTemplate"/>
<outbound-async-gateway id="asyncFullConfig"
url="http://localhost/test2"
http-method="PUT"
request-channel="requests"
async-request-factory="testRequestFactory"
reply-timeout="1234"
message-converters="converterList"
extract-request-payload="false"
expected-response-type="java.lang.String"
mapped-request-headers="requestHeader1, requestHeader2"
mapped-response-headers="responseHeader"
error-handler="testErrorHandler"
reply-channel="replies"
charset="UTF-8"
order="77"
auto-startup="false"
transfer-cookies="true">
<uri-variable name="foo" expression="headers.bar"/>
</outbound-async-gateway>
<util:map id="uriVariables">
<beans:entry key="foo1" value="bar1"/>

View File

@@ -41,12 +41,14 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.AsyncClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.integration.http.outbound.AsyncHttpRequestExecutingMessageHandler;
import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
@@ -56,6 +58,7 @@ import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.ResponseErrorHandler;
/**
@@ -75,6 +78,12 @@ public class HttpOutboundGatewayParserTests {
@Autowired @Qualifier("fullConfig")
private AbstractEndpoint fullConfigEndpoint;
@Autowired @Qualifier("asyncMinimalConfig")
private AbstractEndpoint asyncMinimalConfigEndpoint;
@Autowired @Qualifier("asyncFullConfig")
private AbstractEndpoint asyncFullConfigEndpoint;
@Autowired @Qualifier("withUrlExpression")
private AbstractEndpoint withUrlExpressionEndpoint;
@@ -84,6 +93,9 @@ public class HttpOutboundGatewayParserTests {
@Autowired @Qualifier("withPoller1")
private AbstractEndpoint withPoller1;
@Autowired @Qualifier("asyncRestTemplate")
private AsyncRestTemplate asyncRestTemplate;
@Autowired
private ApplicationContext applicationContext;
@@ -160,6 +172,78 @@ public class HttpOutboundGatewayParserTests {
assertEquals(true, handlerAccessor.getPropertyValue("transferCookies"));
}
@Test
public void asyncMinimalConfig() {
AsyncHttpRequestExecutingMessageHandler handler = (AsyncHttpRequestExecutingMessageHandler) new DirectFieldAccessor(
this.asyncMinimalConfigEndpoint).getPropertyValue("handler");
MessageChannel requestChannel = (MessageChannel) new DirectFieldAccessor(
this.minimalConfigEndpoint).getPropertyValue("inputChannel");
assertEquals(this.applicationContext.getBean("requests"), requestChannel);
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
Object replyChannel = handlerAccessor.getPropertyValue("outputChannel");
assertNull(replyChannel);
DirectFieldAccessor templateAccessor = new DirectFieldAccessor(handlerAccessor.getPropertyValue("asyncRestTemplate"));
AsyncClientHttpRequestFactory requestFactory = (AsyncClientHttpRequestFactory)
templateAccessor.getPropertyValue("asyncRequestFactory");
assertTrue(requestFactory instanceof SimpleClientHttpRequestFactory);
Expression uriExpression = (Expression) handlerAccessor.getPropertyValue("uriExpression");
assertEquals("http://localhost/test1", uriExpression.getValue());
assertEquals(HttpMethod.POST.name(), TestUtils.getPropertyValue(handler, "httpMethodExpression", Expression.class).getExpressionString());
assertEquals(Charset.forName("UTF-8"), handlerAccessor.getPropertyValue("charset"));
assertEquals(true, handlerAccessor.getPropertyValue("extractPayload"));
assertEquals(false, handlerAccessor.getPropertyValue("transferCookies"));
}
@Test
@SuppressWarnings("unchecked")
public void asyncFullConfig() {
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(this.asyncFullConfigEndpoint);
AsyncHttpRequestExecutingMessageHandler handler = (AsyncHttpRequestExecutingMessageHandler) endpointAccessor.getPropertyValue("handler");
MessageChannel requestChannel = (MessageChannel) new DirectFieldAccessor(
this.asyncFullConfigEndpoint).getPropertyValue("inputChannel");
assertEquals(this.applicationContext.getBean("requests"), requestChannel);
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
assertEquals(77, handlerAccessor.getPropertyValue("order"));
assertEquals(Boolean.FALSE, endpointAccessor.getPropertyValue("autoStartup"));
Object replyChannel = handlerAccessor.getPropertyValue("outputChannel");
assertNotNull(replyChannel);
assertEquals(this.applicationContext.getBean("replies"), replyChannel);
DirectFieldAccessor asyncTemplateAccessor = new DirectFieldAccessor(handlerAccessor.getPropertyValue("asyncRestTemplate"));
DirectFieldAccessor syncTemplateAccessor = new DirectFieldAccessor(asyncTemplateAccessor.getPropertyValue("syncTemplate"));
AsyncClientHttpRequestFactory requestFactory = (AsyncClientHttpRequestFactory)
asyncTemplateAccessor.getPropertyValue("asyncRequestFactory");
assertTrue(requestFactory instanceof SimpleClientHttpRequestFactory);
Object converterListBean = this.applicationContext.getBean("converterList");
assertEquals(converterListBean, syncTemplateAccessor.getPropertyValue("messageConverters"));
assertEquals(String.class.getName(), TestUtils.getPropertyValue(handler, "expectedResponseTypeExpression", Expression.class).getValue());
Expression uriExpression = (Expression) handlerAccessor.getPropertyValue("uriExpression");
assertEquals("http://localhost/test2", uriExpression.getValue());
assertEquals(HttpMethod.PUT.name(), TestUtils.getPropertyValue(handler, "httpMethodExpression", Expression.class).getExpressionString());
assertEquals(Charset.forName("UTF-8"), handlerAccessor.getPropertyValue("charset"));
assertEquals(false, handlerAccessor.getPropertyValue("extractPayload"));
Object requestFactoryBean = this.applicationContext.getBean("testRequestFactory");
assertEquals(requestFactoryBean, requestFactory);
Object errorHandlerBean = this.applicationContext.getBean("testErrorHandler");
assertEquals(errorHandlerBean, syncTemplateAccessor.getPropertyValue("errorHandler"));
Object sendTimeout = new DirectFieldAccessor(
handlerAccessor.getPropertyValue("messagingTemplate")).getPropertyValue("sendTimeout");
assertEquals(new Long("1234"), sendTimeout);
Map<String, Expression> uriVariableExpressions =
(Map<String, Expression>) handlerAccessor.getPropertyValue("uriVariableExpressions");
assertEquals(1, uriVariableExpressions.size());
assertEquals("headers.bar", uriVariableExpressions.get("foo").getExpressionString());
DirectFieldAccessor mapperAccessor = new DirectFieldAccessor(handlerAccessor.getPropertyValue("headerMapper"));
String[] mappedRequestHeaders = (String[]) mapperAccessor.getPropertyValue("outboundHeaderNames");
String[] mappedResponseHeaders = (String[]) mapperAccessor.getPropertyValue("inboundHeaderNames");
assertEquals(2, mappedRequestHeaders.length);
assertEquals(1, mappedResponseHeaders.length);
assertTrue(ObjectUtils.containsElement(mappedRequestHeaders, "requestHeader1"));
assertTrue(ObjectUtils.containsElement(mappedRequestHeaders, "requestHeader2"));
assertEquals("responseHeader", mappedResponseHeaders[0]);
assertEquals(true, handlerAccessor.getPropertyValue("transferCookies"));
}
@Test
public void withUrlExpression() {
HttpRequestExecutingMessageHandler handler = (HttpRequestExecutingMessageHandler) new DirectFieldAccessor(

View File

@@ -18,12 +18,16 @@ package org.springframework.integration.http.dsl;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import java.util.Collections;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,10 +36,13 @@ import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.http.outbound.AsyncHttpRequestExecutingMessageHandler;
import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler;
import org.springframework.integration.security.channel.ChannelSecurityInterceptor;
import org.springframework.integration.security.channel.SecuredChannel;
@@ -51,9 +58,11 @@ import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.client.MockMvcClientHttpRequestFactory;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@@ -61,6 +70,7 @@ import org.springframework.web.util.UriComponentsBuilder;
/**
* @author Artem Bilan
* @author Shiliang Li
*
* @since 5.0
*/
@@ -75,6 +85,9 @@ public class HttpDslTests {
@Autowired
private HttpRequestExecutingMessageHandler serviceInternalGatewayHandler;
@Autowired
private AsyncHttpRequestExecutingMessageHandler serviceInternalAsyncGatewayHandler;
private MockMvc mockMvc;
@Before
@@ -101,6 +114,27 @@ public class HttpDslTests {
.string("FOO"));
}
@Test
public void testHttpAsyncProxyFlow() throws Exception {
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate();
String destinationUri = "http://www.springsource.org/spring-integration";
MockRestServiceServer
.createServer(asyncRestTemplate)
.expect(requestTo(Matchers.startsWith(destinationUri)))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess("FOO", MediaType.TEXT_PLAIN));
new DirectFieldAccessor(this.serviceInternalAsyncGatewayHandler)
.setPropertyValue("asyncRestTemplate", asyncRestTemplate);
this.mockMvc.perform(
get("/service2")
.with(httpBasic("guest", "guest"))
.param("name", "foo"))
.andExpect(
content()
.string("FOO"));
}
@Configuration
@EnableWebMvc
@@ -159,6 +193,21 @@ public class HttpDslTests {
.get();
}
@Bean
public IntegrationFlow httpAsyncProxyFlow() {
return IntegrationFlows
.from(Http.inboundGateway("/service2")
.requestMapping(r -> r.params("name")))
.handle(Http.<MultiValueMap<String, String>>outboundAsyncGateway(m ->
UriComponentsBuilder.fromUriString("http://www.springsource.org/spring-integration")
.queryParams(m.getPayload())
.build()
.toUri())
.expectedResponseType(String.class),
e -> e.id("serviceInternalAsyncGateway"))
.get();
}
@Bean
public AccessDecisionManager accessDecisionManager() {
return new AffirmativeBased(Collections.singletonList(new RoleVoter()));

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2017 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.http.outbound;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import org.junit.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.dsl.channel.MessageChannels;
import org.springframework.integration.http.HttpHeaders;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.AsyncRestTemplate;
/**
* @author Shiliang Li
* @since 5.0
*/
public class AsyncHttpRequestExecutingMessageHandlerTests {
@Test
public void testAsyncReturn() {
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate();
String destinationUri = "http://www.springsource.org/spring-integration";
MockRestServiceServer
.createServer(asyncRestTemplate)
.expect(requestTo(destinationUri))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess());
AsyncHttpRequestExecutingMessageHandler asyncHandler = new AsyncHttpRequestExecutingMessageHandler(
destinationUri,
asyncRestTemplate);
QueueChannel ackChannel = MessageChannels.queue().get();
asyncHandler.setOutputChannel(ackChannel);
asyncHandler.handleMessage(MessageBuilder.withPayload("hello, world").build());
Message<?> ack = ackChannel.receive(1000);
assertNotNull(ack);
assertNotNull(ack.getHeaders());
assertEquals(ack.getHeaders().get(HttpHeaders.STATUS_CODE), HttpStatus.OK);
}
}

View File

@@ -5,7 +5,7 @@
=== Introduction
The HTTP support allows for the execution of HTTP requests and the processing of inbound HTTP requests.
Because interaction over HTTP is always synchronous, even if all that is returned is a 200 status code, the HTTP support consists of two gateway implementations: `HttpInboundEndpoint` and `HttpRequestExecutingMessageHandler`.
the HTTP support consists of the following gateway implementations: `HttpInboundEndpoint`, `HttpRequestExecutingMessageHandler` and `AsyncHttpRequestExecutingMessageHandler`
[[http-inbound]]
=== Http Inbound Components
@@ -125,6 +125,7 @@ The key that is used for that map entry by default is 'reply', but this can be o
[[http-outbound]]
=== Http Outbound Components
==== HttpRequestExecutingMessageHandler
To configure the `HttpRequestExecutingMessageHandler` write a bean definition like this:
@@ -164,6 +165,7 @@ When set to true (default is false), a _Set-Cookie_ header received from the ser
This header will then be used on subsequent sends.
This enables simple stateful interactions, such as...
`...->logonGateway->...->doWorkGateway->...->logoffGateway->...`
If _transfer-cookies_ is false, any _Set-Cookie_ header received will remain as _Set-Cookie_ in the reply message, and will be dropped on subsequent sends.
@@ -190,6 +192,37 @@ The `expected-response-type` must be compatible with the (configured or default)
Of course, this can be an abstract class, or even an interface (such as `java.io.Serializable` when using java serialization and `Content-Type: application/x-java-serialized-object`).
=====
==== AsyncHttpRequestExecutingMessageHandler
The `AsyncHttpRequestExecutingMessageHandler` implementation is very similar to `HttpRequestExecutingMessageHandler` instead of delegating to a `AsyncRestTemplate`.
To configure it, write a bean like this:
[source,xml]
----
<bean id="httpAsyncOutbound"
class="org.springframework.integration.http.outbound.AsyncHttpRequestExecutingMessageHandler">
<constructor-arg value="http://localhost:8080/example" />
<property name="outputChannel" ref="responseChannel" />
</bean>
----
You can configure the `AsyncClientHttpRequestFactory` instance to use:
[source,xml]
----
<bean id="httpOutbound"
class="org.springframework.integration.http.outbound.AsyncHttpRequestExecutingMessageHandler">
<constructor-arg value="http://localhost:8080/example" />
<property name="outputChannel" ref="responseChannel" />
<property name="asyncRequestFactory" ref="customRequestFactory" />
</bean>
----
By default the HTTP request will be generated using an instance of `SimpleClientHttpRequestFactory`.
Use of the Apache Commons HTTP Async Client is also supported through the provided `HttpComponentsAsyncClientHttpRequestFactory` which can be injected as shown above.
For other settings like cookie, converters, etc, please see <<HttpRequestExecutingMessageHandler>> above.
[[http-namespace]]
=== HTTP Namespace Support
@@ -512,6 +545,34 @@ The configuration looks very similar to the gateway:
auto-startup="false"/>
----
If you want to execute the http request in an asynchronous way, you can use the `outbound-async-gateway` or `outbound-async-channel-adapter`.
[source,xml]
----
<int-http:outbound-async-gateway id="asyncExample1"
request-channel="requests"
url="http://localhost/test"
http-method-expression="headers.httpMethod"
extract-request-payload="false"
expected-response-type-expression="payload"
charset="UTF-8"
async-request-factory="requestFactory"
reply-timeout="1234"
reply-channel="replies"/>
<int-http:outbound-async-channel-adapter id="asyncExample2"
url="http://localhost/example"
http-method="GET"
channel="requests"
charset="UTF-8"
extract-payload="false"
expected-response-type="java.lang.String"
async-request-factory="someRequestFactory"
order="3"
auto-startup="false"/>
----
[NOTE]
=====
To specify the URL; you can use either the 'url' attribute or the 'url-expression' attribute.

View File

@@ -14,6 +14,11 @@ development process.
The new `MongoDbOutboundGateway` allows you to make queries to the database on demand by sending a message to its request channel.
See <<mongodb-outbound-gateway>> for more information.
==== HTTP Async Outbound Gateway and Channel Adapter
The new `AsyncHttpRequestExecutingMessageHandler` adds support for `AsyncRestTemplate` for outbound channel adapter and gateway.
See <<AsyncHttpRequestExecutingMessageHandler>> for more information.
[[x5.0-general]]
=== General Changes