diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpAdapterParsingUtils.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpAdapterParsingUtils.java index d7c8f87340..d8c9a8ec0f 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpAdapterParsingUtils.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpAdapterParsingUtils.java @@ -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"); diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpNamespaceHandler.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpNamespaceHandler.java index bf39895fff..31957121c6 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpNamespaceHandler.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpNamespaceHandler.java @@ -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()); } diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParser.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParser.java index 09184c7100..8230098bd8 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParser.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParser.java @@ -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); + } } } diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundGatewayParser.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundGatewayParser.java index e59176f776..b75f3ff6e5 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundGatewayParser.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundGatewayParser.java @@ -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); + } } } diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/AsyncHttpMessageHandlerSpec.java b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/AsyncHttpMessageHandlerSpec.java new file mode 100644 index 0000000000..9a819a8742 --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/AsyncHttpMessageHandlerSpec.java @@ -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 { + + 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; + } +} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/BaseHttpMessageHandlerSpec.java b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/BaseHttpMessageHandlerSpec.java new file mode 100644 index 0000000000..e6c27afb93 --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/BaseHttpMessageHandlerSpec.java @@ -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 the target {@link BaseHttpMessageHandlerSpec} implementation type. + * @param the target {@link AbstractHttpRequestExecutingMessageHandler} implementation type. + * @author Artem Bilan + * @author Shiliang Li + * @since 5.0 + */ +public abstract class BaseHttpMessageHandlerSpec, E extends AbstractHttpRequestExecutingMessageHandler> + extends MessageHandlerSpec + implements ComponentsRegistration { + + private final Map uriVariableExpressions = new HashMap<>(); + + private HeaderMapper 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 uriVariables + * expanding and before send request via underlying implementation. The default value is true. + * @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

the payload type. + * @return the spec + */ + public

S httpMethodFunction(Function, ?> 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>(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

the payload type. + * @return the spec + */ + public

S expectedResponseTypeFunction( + Function, ?> 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 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 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

the payload type. + * @return the current Spec. + * @see AbstractHttpRequestExecutingMessageHandler#setUriVariableExpressions(Map) + */ + public

S uriVariable(String variable, Function, ?> 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

the payload type. + * @return the current Spec. + * @see AbstractHttpRequestExecutingMessageHandler#setUriVariablesExpression(Expression) + */ + public

S uriVariablesFunction(Function, Map> 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 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(); +} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/Http.java b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/Http.java index 721184a4f9..1725c5e92e 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/Http.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/Http.java @@ -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

the expected payload type. + * @return the AsyncHttpMessageHandlerSpec instance + */ + public static

AsyncHttpMessageHandlerSpec outboundAsyncChannelAdapter(Function, ?> 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

the expected payload type. + * @return the AsyncHttpMessageHandlerSpec instance + */ + public static

AsyncHttpMessageHandlerSpec outboundAsyncChannelAdapter(Function, ?> 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

the expected payload type. + * @return the AsyncHttpMessageHandlerSpec instance + */ + public static

AsyncHttpMessageHandlerSpec outboundAsyncGateway(Function, ?> 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

the expected payload type. + * @return the AsyncHttpMessageHandlerSpec instance + */ + public static

AsyncHttpMessageHandlerSpec outboundAsyncGateway(Function, ?> 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. diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpMessageHandlerSpec.java b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpMessageHandlerSpec.java index 8b82b5cf32..fe910425dd 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpMessageHandlerSpec.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpMessageHandlerSpec.java @@ -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 - implements ComponentsRegistration { + extends BaseHttpMessageHandlerSpec { private final RestTemplate restTemplate; - private final Map uriVariableExpressions = new HashMap<>(); - - private HeaderMapper 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 uriVariables - * expanding and before send request via {@link RestTemplate}. The default value is true. - * @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

the payload type. - * @return the spec - */ - public

HttpMessageHandlerSpec httpMethodFunction(Function, ?> 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>(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

the payload type. - * @return the spec - */ - public

HttpMessageHandlerSpec expectedResponseTypeFunction( - Function, ?> 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 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 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

the payload type. - * @return the current Spec. - * @see HttpRequestExecutingMessageHandler#setUriVariableExpressions(Map) - */ - public

HttpMessageHandlerSpec uriVariable(String variable, Function, ?> 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

the payload type. - * @return the current Spec. - * @see HttpRequestExecutingMessageHandler#setUriVariablesExpression(Expression) - */ - public

HttpMessageHandlerSpec uriVariablesFunction(Function, Map> 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 getComponentsToRegister() { - return Collections.singletonList(this.headerMapper); + protected boolean isRestTemplateSet() { + return this.restTemplate != null; } - } diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/AbstractHttpRequestExecutingMessageHandler.java b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/AbstractHttpRequestExecutingMessageHandler.java new file mode 100644 index 0000000000..01e7a476d0 --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/AbstractHttpRequestExecutingMessageHandler.java @@ -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 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 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 uriVariables + * expanding and before send request via {@link RestTemplate}. The default value is true. + * + * @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); + } + + /** + * 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>(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> messageConverters); + + /** + * Set the {@link HeaderMapper} to use when mapping between HTTP headers and MessageHeaders. + * @param headerMapper The header mapper. + */ + public void setHeaderMapper(HeaderMapper 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 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 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 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 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(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(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(message, httpHeaders); + } + return new HttpEntity(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 is actually valid content for serialization + if (this.isFormData((Map) content)) { + if (this.isMultipart((Map) 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 convertToMultiValueMap(Map simpleMap) { + LinkedMultiValueMap multipartValueMap = new LinkedMultiValueMap(); + 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((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 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 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 determineUriVariables(Message requestMessage) { + Map 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) expressionsObject; + } + else { + expressions = this.uriVariableExpressions; + } + + return ExpressionEvalMap.from(expressions) + .usingEvaluationContext(this.evaluationContext) + .withRoot(requestMessage) + .build(); + + } + +} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/AsyncHttpRequestExecutingMessageHandler.java b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/AsyncHttpRequestExecutingMessageHandler.java new file mode 100644 index 0000000000..2579265e0f --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/AsyncHttpRequestExecutingMessageHandler.java @@ -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> 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 replyMessageFuture = new SettableListenableFuture<>(); + ListenableFuture> 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; + } +} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java index 1cc9f430d9..8286030ca9 100755 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java @@ -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 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 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 uriVariables - * expanding and before send request via {@link RestTemplate}. The default value is true. - * - * @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); - } - - /** - * 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>(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> 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 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 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 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 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 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(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(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(message, httpHeaders); - } - return new HttpEntity(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 is actually valid content for serialization - if (this.isFormData((Map) content)) { - if (this.isMultipart((Map) 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 convertToMultiValueMap(Map simpleMap) { - LinkedMultiValueMap multipartValueMap = new LinkedMultiValueMap(); - 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((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 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 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 determineUriVariables(Message requestMessage) { - Map 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) expressionsObject; - } - else { - expressions = this.uriVariableExpressions; - } - - return ExpressionEvalMap.from(expressions) - .usingEvaluationContext(this.evaluationContext) - .withRoot(requestMessage) - .build(); - - } - } diff --git a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-5.0.xsd b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-5.0.xsd index d636bb27d9..486d963554 100644 --- a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-5.0.xsd +++ b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-5.0.xsd @@ -387,6 +387,44 @@ + + + + + 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'. + + + + + + + + + + Configures a Consumer Endpoint for the + 'org.springframework.integration.http.outbound.AsyncHttpRequestExecutingMessageHandler' + with 'expectReply = false' that sends HTTP requests based on incoming messages. + + + + + + + + Specify an expression for URI variable placeholder within 'url'. + This element is mutually exclusive with 'uri-variables-expression' attribute. + + + + + + + + + @@ -488,6 +526,101 @@ + + + + + + + + + + Configures a Consumer Endpoint for the + 'org.springframework.integration.http.outbound.AsyncHttpRequestExecutingMessageHandler' + that sends HTTP requests based on incoming messages and expects HTTP responses. + + + + + + + + + + Specify an expression for URI variable placeholder within 'url'. + + + + + + + + + + + + + + + + The receiving Message Channel of this endpoint. + + + + + + + + + + + + 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'. + + + + + + + + + + + + + + + @@ -726,6 +859,60 @@ + + + + + + + + + + The reference to org.springframework.web.client.AsyncRestTemplate bean to send the HTTP Request. + + + + + + + + + + + + + + + + + + + + + + + + + The reference to org.springframework.web.client.RestTemplate bean to send the HTTP Request. + + + + + + + + + + + + + + + @@ -773,18 +960,6 @@ - - - - - - - - - The reference to org.springframework.web.client.RestTemplate bean to send the HTTP Request. - - - @@ -849,18 +1024,6 @@ ]]> - - - - - - - - - - + + + + + + + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java index b2c123c31f..5233f420c0 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java @@ -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()) diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml index 6704b2d5e1..d4177c3294 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml @@ -14,6 +14,8 @@ + + @@ -40,6 +42,27 @@ + + + + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java index 23cbb0623d..8548859507 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java @@ -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 uriVariableExpressions = + (Map) 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( diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java index 7e51a3a09a..71c6102bbe 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java @@ -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.>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())); diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/AsyncHttpRequestExecutingMessageHandlerTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/AsyncHttpRequestExecutingMessageHandlerTests.java new file mode 100644 index 0000000000..272dc8b634 --- /dev/null +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/AsyncHttpRequestExecutingMessageHandlerTests.java @@ -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); + } +} diff --git a/src/reference/asciidoc/http.adoc b/src/reference/asciidoc/http.adoc index 5ea2a94eee..ccad36530d 100644 --- a/src/reference/asciidoc/http.adoc +++ b/src/reference/asciidoc/http.adoc @@ -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] +---- + + + + +---- + +You can configure the `AsyncClientHttpRequestFactory` instance to use: + +[source,xml] +---- + + + + + +---- + +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 <> 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] +---- + + + + +---- + [NOTE] ===== To specify the URL; you can use either the 'url' attribute or the 'url-expression' attribute. diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index 81a95d0489..8bd784b1e1 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -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 <> for more information. +==== HTTP Async Outbound Gateway and Channel Adapter + +The new `AsyncHttpRequestExecutingMessageHandler` adds support for `AsyncRestTemplate` for outbound channel adapter and gateway. +See <> for more information. + [[x5.0-general]] === General Changes