From bb77f508ec028067fc965ba04b8c5cab98eede79 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Tue, 26 Feb 2013 13:34:32 +0200 Subject: [PATCH] INT-2455: Support HTTP Outbound 'encode-uri' Flag The `` and `` now provide an `encode-uri` option that controls whether the request uri is encoded (default `true`). It can be useful in some scenarios with a non standard URL, e.g. RabbitMQ REST API: 'http://foo.RabbitMQ.com/api/queues/%2f/bar.queue' where we must not encode the '%'. https://jira.springsource.org/browse/INT-2455 INT-2455: Polishing based on PR comments INT-2455: 'encode-uri' in the Reference Manual --- .../HttpOutboundChannelAdapterParser.java | 5 ++- .../config/HttpOutboundGatewayParser.java | 5 ++- .../HttpRequestExecutingMessageHandler.java | 20 ++++++++- .../config/spring-integration-http-3.0.xsd | 18 ++++++++ .../HttpOutboundWithinChainTests-context.xml | 15 +++++-- ...tpRequestExecutingMessageHandlerTests.java | 42 ++++++++++++++----- src/reference/docbook/http.xml | 20 ++++++++- src/reference/docbook/whats-new.xml | 9 ++++ 8 files changed, 115 insertions(+), 19 deletions(-) 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 df81521391..d1a6c0ee44 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-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"); 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 0d13e685ec..df2ed76567 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-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"); 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 d76929d3e6..db2f808ac1 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-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 uriVariables + * expanding and before send request via {@link RestTemplate}. The default value is true. + * + * @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 headers = this.headerMapper.toHeaders(httpHeaders); diff --git a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-3.0.xsd b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-3.0.xsd index b6bddc7ff9..6f2203a573 100644 --- a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-3.0.xsd +++ b/spring-integration-http/src/main/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 ]]> + + + + 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". + + + @@ -560,6 +569,15 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re ]]> + + + + 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". + + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/HttpOutboundWithinChainTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/HttpOutboundWithinChainTests-context.xml index d77d40fdc2..a4ca379b94 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/HttpOutboundWithinChainTests-context.xml +++ b/spring-integration-http/src/test/java/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"> - + - + + + + + - + encode-uri="false" + expected-response-type="java.lang.String"> + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandlerTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandlerTests.java index c30a6bc0b5..145978948e 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandlerTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/outbound/HttpRequestExecutingMessageHandlerTests.java @@ -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 actualUrl = new AtomicReference(); @Override - public ResponseEntity exchange(String url, HttpMethod method, HttpEntity requestEntity, - Class responseType, Map uriVariables) throws RestClientException { - this.actualUrl.set(url); + public ResponseEntity exchange(URI uri, HttpMethod method, HttpEntity requestEntity, + Class 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 ResponseEntity exchange(String url, HttpMethod method, HttpEntity requestEntity, - Class responseType, Map uriVariables) throws RestClientException { + public ResponseEntity exchange(URI uri, HttpMethod method, HttpEntity requestEntity, + Class responseType) throws RestClientException { return new ResponseEntity(HttpStatus.OK); } } diff --git a/src/reference/docbook/http.xml b/src/reference/docbook/http.xml index 76ac32de22..e1ebde4e95 100644 --- a/src/reference/docbook/http.xml +++ b/src/reference/docbook/http.xml @@ -376,7 +376,7 @@ In the case of the Outbound Gateway, the reply message produced by the gateway w - Mapping URI variables + Mapping URI Variables 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'. + + Controlling URI Encoding + + + By default, the URL string is encoded (see + UriComponentsBuilder) + 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 <http:outbound-gateway/> + and <http:outbound-channel-adapter/> provide an encode-uri attribute. + To disable encoding the URL, this attribute should be set to false (by default it is true). + If you wish to partially encode some of the URL, this can be achieved using an expression within a + <uri-variable/>: + + + +]]> diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index cbef2d2e10..f1e9672cbe 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -136,5 +136,14 @@ see 'JSON Transformers' in . +
+ HTTP Outbound Endpoint 'encode-uri' property + + <http:outbound-gateway/> and <http:outbound-channel-adapter/> now + provide an encode-uri attribute to allow disabling the encoding of the URI object + before sending the request. For more information see . + +
+