INT-4208: Support AsycRest in HTTP Module

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.http.outbound;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import org.junit.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.dsl.channel.MessageChannels;
import org.springframework.integration.http.HttpHeaders;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.AsyncRestTemplate;
/**
* @author Shiliang Li
* @since 5.0
*/
public class AsyncHttpRequestExecutingMessageHandlerTests {
@Test
public void testAsyncReturn() {
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate();
String destinationUri = "http://www.springsource.org/spring-integration";
MockRestServiceServer
.createServer(asyncRestTemplate)
.expect(requestTo(destinationUri))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess());
AsyncHttpRequestExecutingMessageHandler asyncHandler = new AsyncHttpRequestExecutingMessageHandler(
destinationUri,
asyncRestTemplate);
QueueChannel ackChannel = MessageChannels.queue().get();
asyncHandler.setOutputChannel(ackChannel);
asyncHandler.handleMessage(MessageBuilder.withPayload("hello, world").build());
Message<?> ack = ackChannel.receive(1000);
assertNotNull(ack);
assertNotNull(ack.getHeaders());
assertEquals(ack.getHeaders().get(HttpHeaders.STATUS_CODE), HttpStatus.OK);
}
}