Merge pull request #755 from artembilan/INT-2455

* INT-2455:
  INT-2455: Support HTTP Outbound 'encode-uri' Flag
This commit is contained in:
Gary Russell
2013-04-30 11:53:01 -04:00
8 changed files with 115 additions and 19 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -32,6 +32,7 @@ import org.springframework.util.StringUtils;
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser {
@@ -42,6 +43,8 @@ public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAda
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");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ import org.springframework.util.StringUtils;
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
*/
public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
@@ -45,6 +46,8 @@ public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
HttpAdapterParsingUtils.configureUrlConstructorArg(element, parserContext, builder);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
HttpAdapterParsingUtils.setHttpMethodOrExpression(element, parserContext, builder);
String restTemplate = element.getAttribute("rest-template");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -62,6 +62,8 @@ 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
@@ -90,6 +92,8 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
private final Expression uriExpression;
private volatile boolean encodeUri = true;
private volatile Expression httpMethodExpression = new LiteralExpression(HttpMethod.POST.name());
private volatile boolean expectReply = true;
@@ -157,6 +161,16 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
this.evaluationContext = sec;
}
/**
* Specify whether the real URI should be encoded after <code>uriVariables</code>
* expanding and before send request via {@link RestTemplate}. The default value is <code>true</code>.
*
* @see UriComponentsBuilder
*/
public void setEncodeUri(boolean encodeUri) {
this.encodeUri = encodeUri;
}
/**
* Specify the SpEL {@link Expression} to determine {@link HttpMethod} dynamically
*
@@ -348,7 +362,9 @@ public class HttpRequestExecutingMessageHandler extends AbstractReplyProducingMe
Class<?> expectedResponseType = this.determineExpectedResponseType(requestMessage);
HttpEntity<?> httpRequest = this.generateHttpRequest(requestMessage, httpMethod);
ResponseEntity<?> httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest, expectedResponseType, uriVariables);
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uri).buildAndExpand(uriVariables);
URI realUri = this.encodeUri ? uriComponents.toUri() : new URI(uriComponents.toUriString());
ResponseEntity<?> httpResponse = this.restTemplate.exchange(realUri, httpMethod, httpRequest, expectedResponseType);
if (this.expectReply) {
HttpHeaders httpHeaders = httpResponse.getHeaders();
Map<String, Object> headers = this.headerMapper.toHeaders(httpHeaders);

View File

@@ -390,6 +390,15 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="encode-uri" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation>
When set to "false", the real URI won't be encoded before the request is sent. This may be useful
in some scenarios as it allows user control over the encoding, if needed,
for example by using the "url-expression". Default is "true".
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="http-method">
<xsd:annotation>
<xsd:documentation>
@@ -560,6 +569,15 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="encode-uri" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation>
When set to "false", the real URI won't be encoded before the request is sent. This may be useful
in some scenarios as it allows user control over the encoding, if needed,
for example by using the "url-expression". Default is "true".
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="http-method">
<xsd:annotation>
<xsd:documentation>

View File

@@ -9,15 +9,22 @@
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<si:chain input-channel="httpOutboundChannelAdapterWithinChain">
<outbound-channel-adapter url="http://localhost/test1" rest-template="restTemplate"/>
<outbound-channel-adapter url="http://localhost/test1/%2f" encode-uri="false" rest-template="restTemplate"/>
</si:chain>
<beans:bean id="restTemplate" class="org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandlerTests$MockRestTemplate2"/>
<beans:bean id="restTemplate" class="org.mockito.Mockito" factory-method="spy">
<beans:constructor-arg>
<beans:bean class="org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandlerTests$MockRestTemplate2"/>
</beans:constructor-arg>
</beans:bean>
<si:chain input-channel="httpOutboundGatewayWithinChain" output-channel="replyChannel">
<outbound-gateway url="http://localhost:51235/testApps/httpOutboundGatewayWithinChain"
<outbound-gateway url="http://localhost:51235/%2f/testApps?param={param}"
rest-template="restTemplate"
expected-response-type="java.lang.String"/>
encode-uri="false"
expected-response-type="java.lang.String">
<uri-variable name="param" expression="T(org.apache.commons.httpclient.util.URIUtil).encodeWithinQuery('http Outbound Gateway Within Chain')"/>
</outbound-gateway>
</si:chain>
<si:channel id="replyChannel">

View File

@@ -29,6 +29,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -42,6 +43,7 @@ import javax.xml.transform.Source;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -673,23 +675,28 @@ public class HttpRequestExecutingMessageHandlerTests {
}
@Test //INT-2275
public void testOutboundChannelAdapterWithinChain() {
public void testOutboundChannelAdapterWithinChain() throws URISyntaxException {
ApplicationContext ctx = new ClassPathXmlApplicationContext("HttpOutboundWithinChainTests-context.xml", this.getClass());
MessageChannel channel = ctx.getBean("httpOutboundChannelAdapterWithinChain", MessageChannel.class);
RestTemplate restTemplate = ctx.getBean("restTemplate", RestTemplate.class);
channel.send(MessageBuilder.withPayload("test").build());
// It's just enough if it was sent successfully from chain without any failures
Mockito.verify(restTemplate).exchange(Mockito.eq(new URI("http://localhost/test1/%2f")), Mockito.eq(HttpMethod.POST),
Mockito.any(HttpEntity.class), Mockito.eq((Class<?>) null));
}
@Test //INT-1029
public void testHttpOutboundGatewayWithinChain() throws IOException {
public void testHttpOutboundGatewayWithinChain() throws IOException, URISyntaxException {
ApplicationContext ctx = new ClassPathXmlApplicationContext("HttpOutboundWithinChainTests-context.xml", this.getClass());
MessageChannel channel = ctx.getBean("httpOutboundGatewayWithinChain", MessageChannel.class);
RestTemplate restTemplate = ctx.getBean("restTemplate", RestTemplate.class);
channel.send(MessageBuilder.withPayload("test").build());
PollableChannel output = ctx.getBean("replyChannel", PollableChannel.class);
Message<?> receive = output.receive();
assertEquals(HttpStatus.OK, ((ResponseEntity<?>)receive.getPayload()).getStatusCode());
Mockito.verify(restTemplate)
.exchange(Mockito.eq(new URI("http://localhost:51235/%2f/testApps?param=http%20Outbound%20Gateway%20Within%20Chain")),
Mockito.eq(HttpMethod.POST), Mockito.any(HttpEntity.class), Mockito.eq(String.class));
}
@Test
@@ -698,7 +705,7 @@ public class HttpRequestExecutingMessageHandlerTests {
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler(
new SpelExpressionParser().parseExpression("headers['foo']"),
restTemplate);
String theURL = "http://bar/baz";
String theURL = "http://bar/baz?foo#bar";
Message<?> message = MessageBuilder.withPayload("").setHeader("foo", theURL).build();
try {
handler.handleRequestMessage(message);
@@ -707,6 +714,21 @@ public class HttpRequestExecutingMessageHandlerTests {
assertEquals(theURL, restTemplate.actualUrl.get());
}
@Test
public void testInt2455UriNotEncoded() {
MockRestTemplate restTemplate = new MockRestTemplate();
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler(
new SpelExpressionParser().parseExpression("'http://my.RabbitMQ.com/api/' + payload"),
restTemplate);
handler.setEncodeUri(false);
Message<?> message = MessageBuilder.withPayload("queues/%2f/si.test.queue?foo#bar").build();
try {
handler.handleRequestMessage(message);
}
catch (Exception e) {}
assertEquals("http://my.RabbitMQ.com/api/queues/%2f/si.test.queue?foo#bar", restTemplate.actualUrl.get());
}
@Test
public void nonCompatibleConversionService() throws Exception {
HttpRequestExecutingMessageHandler handler =
@@ -857,9 +879,9 @@ public class HttpRequestExecutingMessageHandlerTests {
private final AtomicReference<String> actualUrl = new AtomicReference<String>();
@Override
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity,
Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
this.actualUrl.set(url);
public <T> ResponseEntity<T> exchange(URI uri, HttpMethod method, HttpEntity<?> requestEntity,
Class<T> responseType) throws RestClientException {
this.actualUrl.set(uri.toString());
this.lastRequestEntity.set(requestEntity);
throw new RuntimeException("intentional");
}
@@ -869,8 +891,8 @@ public class HttpRequestExecutingMessageHandlerTests {
private static class MockRestTemplate2 extends RestTemplate {
@Override
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity,
Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
public <T> ResponseEntity<T> exchange(URI uri, HttpMethod method, HttpEntity<?> requestEntity,
Class<T> responseType) throws RestClientException {
return new ResponseEntity<T>(HttpStatus.OK);
}
}

View File

@@ -376,7 +376,7 @@ In the case of the Outbound Gateway, the reply message produced by the gateway w
</para>
</note>
<para>
<emphasis>Mapping URI variables</emphasis>
<emphasis>Mapping URI Variables</emphasis>
</para>
<para>
If your URL contains URI variables, you can map them using the
@@ -402,6 +402,24 @@ In the case of the Outbound Gateway, the reply message produced by the gateway w
method will be invoked on the payload object of the Message and the result
of that method will be used as the value for the URI variable named 'zipCode'.
</para>
<para>
<emphasis>Controlling URI Encoding</emphasis>
</para>
<para>
By default, the URL string is encoded (see
<ulink url="http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/web/util/UriComponentsBuilder.html">UriComponentsBuilder</ulink>)
to the URI object before sending the request. In some scenarios with a non-standard URI (e.g. the RabbitMQ Rest API) it is
undesirable to perform the encoding. The <code>&lt;http:outbound-gateway/&gt;</code>
and <code>&lt;http:outbound-channel-adapter/&gt;</code> provide an <code>encode-uri</code> attribute.
To disable encoding the URL, this attribute should be set to <code>false</code> (by default it is <code>true</code>).
If you wish to partially encode some of the URL, this can be achieved using an <code>expression</code> within a
<code>&lt;uri-variable/&gt;</code>:
</para>
<programlisting language="xml"><![CDATA[<http:outbound-gateway url="http://somehost/%2f/fooApps?bar={param}" encode-uri="false">
<http:uri-variable name="param"
expression="T(org.apache.commons.httpclient.util.URIUtil)
.encodeWithinQuery('Hellow World!')"/>
</http:outbound-gateway>]]></programlisting>
</section>

View File

@@ -136,5 +136,14 @@
see 'JSON Transformers' in <xref linkend="transformer"/>.
</para>
</section>
<section id="3.0-http-encode-uri">
<title>HTTP Outbound Endpoint 'encode-uri' property</title>
<para>
<code>&lt;http:outbound-gateway/&gt;</code> and <code>&lt;http:outbound-channel-adapter/&gt;</code> now
provide an <code>encode-uri</code> attribute to allow disabling the encoding of the URI object
before sending the request. For more information see <xref linkend="http"/>.
</para>
</section>
</section>
</chapter>