Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

INT-2455: HTTP outbound 'encode-uri' flag support #755

Closed
wants to merge 4 commits into from

2 participants

@artembilan
Collaborator

Now <http:outbound-channel-adapter> and <http:outbound-gateway> provide
encode-uri="false" option do not encode request uri.
It can be useful in some scenarios with non standard URL, e.g. RabbitMQ REST API:
'http://foo.RabbitMQ.com/api/queues/%2f/bar.queue'

https://jira.springsource.org/browse/INT-2455

...http/outbound/HttpRequestExecutingMessageHandler.java
@@ -348,7 +362,9 @@ protected Object handleRequestMessage(Message<?> requestMessage) {
Class<?> expectedResponseType = this.determineExpectedResponseType(requestMessage);
HttpEntity<?> httpRequest = this.generateHttpRequest(requestMessage, httpMethod);
- ResponseEntity<?> httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest, expectedResponseType, uriVariables);
+ UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(uri).buildAndExpand(uriVariables);
@garyrussell Owner

Artem, I believe you need to continue to use fromUriString() as is currently used by the RestTemplate. The problem with using fromHttpUrl is that if there's a fragment component (e.g http://......xyz?foo#bar), and encode-url="true" the # will incorrectly be encoded...

junit.framework.ComparisonFailure: null expected:<...xx/si.test.queue?foo[#]bar> but was:<...xx/si.test.queue?foo[%23]bar>
at junit.framework.Assert.assertEquals(Assert.java:81)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@artembilan
Collaborator

Exactly, Gary!
So, pushed with test to cover '#' issue.

@garyrussell
Owner

Hmmm - we have some inconsistency here with uri Vs. url (url, url-expression, encode-uri, uri-variable).

I wonder if we should take this opportunity to make them consistent - or at least, perhaps, provide aliases for url and url-expression (uri, uri-expression).

@artembilan
Collaborator

IMO we may leave it as is. See UriComponentsBuilder: fromHttpUrl, uriVariableValues, encode URI. This is established tradition, that we say about HTTP as URL, but tell about its parts as URI.

@artembilan
Collaborator

BTW, if it's OK I'll add a note to the Reference Manual

@garyrussell
Owner

Yes, I think it's ok

artembilan added some commits
@artembilan artembilan INT-2455: HTTP outbound 'encode-uri' flag support
Now `<http:outbound-channel-adapter>` and `<http:outbound-gateway>` provide
`encode-uri="false"` option do not encode request uri.
It can be useful in some scenarios with non standard URL, e.g. RabbitMQ REST API:
'http://foo.RabbitMQ.com/api/queues/%2f/bar.queue';

https://jira.springsource.org/browse/INT-2455
d3a28ae
@artembilan artembilan INT-2455: Polishing according PR comments 302df71
@artembilan artembilan INT-2455: Add 'encode-uri' to the Manual 9fe8a83
@artembilan artembilan INT-2455: 'encode-uri' in the Reference Manual 3ed098d
@artembilan
Collaborator

Pushed Reference Manual commit.

@garyrussell
Owner

LGTM; merging

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 10, 2013
  1. @artembilan

    INT-2455: HTTP outbound 'encode-uri' flag support

    artembilan authored
    Now `<http:outbound-channel-adapter>` and `<http:outbound-gateway>` provide
    `encode-uri="false"` option do not encode request uri.
    It can be useful in some scenarios with non standard URL, e.g. RabbitMQ REST API:
    'http://foo.RabbitMQ.com/api/queues/%2f/bar.queue';
    
    https://jira.springsource.org/browse/INT-2455
  2. @artembilan
  3. @artembilan
  4. @artembilan
This page is out of date. Refresh to see the latest.
View
5 ...in/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParser.java
@@ -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 @@
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
+ * @author Artem Bilan
* @since 2.0
*/
public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser {
@@ -42,6 +43,8 @@ protected AbstractBeanDefinition parseConsumer(Element element, ParserContext pa
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
5 .../src/main/java/org/springframework/integration/http/config/HttpOutboundGatewayParser.java
@@ -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 @@
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
+ * @author Artem Bilan
*/
public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
@@ -45,6 +46,8 @@ protected BeanDefinitionBuilder parseHandler(Element element, ParserContext pars
HttpAdapterParsingUtils.configureUrlConstructorArg(element, parserContext, builder);
+ IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
+
HttpAdapterParsingUtils.setHttpMethodOrExpression(element, parserContext, builder);
String restTemplate = element.getAttribute("rest-template");
View
20 ...ava/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandler.java
@@ -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.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 @@
private final Expression uriExpression;
+ private volatile boolean encodeUri = true;
+
private volatile Expression httpMethodExpression = new LiteralExpression(HttpMethod.POST.name());
private volatile boolean expectReply = true;
@@ -158,6 +162,16 @@ public HttpRequestExecutingMessageHandler(Expression uriExpression, RestTemplate
}
/**
+ * 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
*
* @param httpMethodExpression
@@ -348,7 +362,9 @@ protected Object handleRequestMessage(Message<?> requestMessage) {
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
18 ...ain/resources/org/springframework/integration/http/config/spring-integration-http-3.0.xsd
@@ -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 send request. It may be useful
+ in some scenarios. It allows to make some partial encoding manually, if needed,
+ e.g. through "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 send request. It may be useful
+ in some scenarios. It allows to make some partial encoding manually, if needed,
+ e.g. through "url-expression". Default is "true".
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
<xsd:attribute name="http-method">
<xsd:annotation>
<xsd:documentation>
View
15 ...va/org/springframework/integration/http/outbound/HttpOutboundWithinChainTests-context.xml
@@ -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
42 ...rg/springframework/integration/http/outbound/HttpRequestExecutingMessageHandlerTests.java
@@ -29,6 +29,7 @@
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 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 void contentTypeIsNotSetForGetRequest() throws Exception {
}
@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 void testUriExpression() {
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);
@@ -708,6 +715,21 @@ public void testUriExpression() {
}
@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 =
new HttpRequestExecutingMessageHandler("http://www.springsource.org/spring-integration");
@@ -857,9 +879,9 @@ public String toString(){
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 String toString(){
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
16 src/reference/docbook/http.xml
@@ -374,6 +374,22 @@ In the case of the Outbound Gateway, the reply message produced by the gateway w
reason, it is recommended that if you wish to generate the URL entirely at runtime, you
use the 'url-expression' attribute.
</para>
+ <para>
+ By default URL string is encoded
+ (<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 request. In some scenarios with non-standard URI (e.g. RabbitMQ Rest API) it is
+ undesirable to do encoding. To achieve this goal <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 value of this attribute should be <code>false</code> (by default it is <code>true</code>).
+ However there is still an opportunity to do partial uri-encoding within <code>&lt;uri-variable/&gt;</code> <code>expression</code>:
+ </para>
+ <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>
+ </para>
</note>
<para>
<emphasis>Mapping URI variables</emphasis>
View
8 src/reference/docbook/whats-new.xml
@@ -71,6 +71,14 @@
payloads to <classname>String</classname>. For more information see <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 <code>encode-uri</code> attribute to allow to disable encoding URI object before sending request
+ by setting <code>false</code> value to this attribute. For more information see <xref linkend="http"/>.
+ </para>
+ </section>
</section>
Something went wrong with that request. Please try again.