GH-3154: Support UriBuilderFactory.EncodingMode (#3162)
* GH-3154: Support `UriBuilderFactory.EncodingMode` Fixes https://github.com/spring-projects/spring-integration/issues/3154 Spring Framework now provides a `DefaultUriBuilderFactory.EncodingMode` for encoding URIs in the `RestTemplate` before and after uri template enrichment with uri variables. Therefore `encodeUri` and manual uri variables substitution is not necessary in Spring Integration HTTP components * Deprecate `AbstractHttpRequestExecutingMessageHandler.encodeUri` in favor of `DefaultUriBuilderFactory.EncodingMode` and respective configuration on the `RestTemplate` in HTTP module and `WebClient` in WebFlux module * * Really populate `uriFactory` into an internal `RestTemplate` * Ensure in tests that `encoding-mode` is populated properly into an internal `RestTemplate` * Clean up affected HTTP tests for AssertJ and JUnit 5 * * Clean up formatting * * Apply fix for WebFlux module * Add docs for new `encoding-mode` option * * Remove unused import in the test
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -25,6 +25,7 @@ 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.HttpRequestExecutingMessageHandler;
|
||||
import org.springframework.integration.http.support.DefaultHttpHeaderMapper;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -46,7 +47,6 @@ 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 headerMapper = element.getAttribute("header-mapper");
|
||||
@@ -60,8 +60,8 @@ public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAda
|
||||
builder.addPropertyReference("headerMapper", headerMapper);
|
||||
}
|
||||
else if (StringUtils.hasText(mappedRequestHeaders)) {
|
||||
BeanDefinitionBuilder headerMapperBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
"org.springframework.integration.http.support.DefaultHttpHeaderMapper");
|
||||
BeanDefinitionBuilder headerMapperBuilder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(DefaultHttpHeaderMapper.class);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(headerMapperBuilder, element,
|
||||
"mapped-request-headers", "outboundHeaderNames");
|
||||
builder.addPropertyValue("headerMapper", headerMapperBuilder.getBeanDefinition());
|
||||
@@ -90,6 +90,8 @@ public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAda
|
||||
for (String referenceAttributeName : HttpAdapterParsingUtils.SYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
|
||||
}
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encoding-mode");
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -47,7 +47,6 @@ public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
|
||||
BeanDefinitionBuilder builder = getBuilder(element, parserContext);
|
||||
|
||||
HttpAdapterParsingUtils.configureUrlConstructorArg(element, parserContext, builder);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
|
||||
HttpAdapterParsingUtils.setHttpMethodOrExpression(element, parserContext, builder);
|
||||
|
||||
String headerMapper = element.getAttribute("header-mapper");
|
||||
@@ -103,6 +102,8 @@ public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
|
||||
for (String referenceAttributeName : HttpAdapterParsingUtils.SYNC_REST_TEMPLATE_REFERENCE_ATTRIBUTES) {
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, referenceAttributeName);
|
||||
}
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encode-uri");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "encoding-mode");
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
* Copyright 2017-2020 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.
|
||||
@@ -34,6 +34,7 @@ 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.util.DefaultUriBuilderFactory;
|
||||
|
||||
/**
|
||||
* The base {@link MessageHandlerSpec} for {@link AbstractHttpRequestExecutingMessageHandler}s.
|
||||
@@ -71,12 +72,25 @@ public abstract class BaseHttpMessageHandlerSpec<S extends BaseHttpMessageHandle
|
||||
* expanding and before send request via underlying implementation. The default value is <code>true</code>.
|
||||
* @param encodeUri true if the URI should be encoded.
|
||||
* @return the spec
|
||||
* @deprecated since 5.3 in favor of {@link #encodingMode}
|
||||
*/
|
||||
@Deprecated
|
||||
public S encodeUri(boolean encodeUri) {
|
||||
this.target.setEncodeUri(encodeUri);
|
||||
return _this();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a {@link DefaultUriBuilderFactory.EncodingMode} for uri construction.
|
||||
* @param encodingMode to use for uri construction.
|
||||
* @return the spec
|
||||
* @since 5.3
|
||||
*/
|
||||
public S encodingMode(DefaultUriBuilderFactory.EncodingMode encodingMode) {
|
||||
this.target.setEncodingMode(encodingMode);
|
||||
return _this();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the SpEL {@link Expression} to determine {@link HttpMethod} at runtime.
|
||||
* @param httpMethodExpression The method expression.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
* Copyright 2017-2020 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,7 +17,6 @@
|
||||
package org.springframework.integration.http.outbound;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
@@ -27,7 +26,6 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.xml.transform.Source;
|
||||
|
||||
@@ -56,14 +54,13 @@ import org.springframework.integration.support.AbstractIntegrationMessageBuilder
|
||||
import org.springframework.integration.support.MessageBuilderFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandlingException;
|
||||
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.util.UriComponents;
|
||||
import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
@@ -85,6 +82,8 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
private static final List<HttpMethod> NO_BODY_HTTP_METHODS =
|
||||
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.TRACE);
|
||||
|
||||
protected final DefaultUriBuilderFactory uriFactory = new DefaultUriBuilderFactory(); // NOSONAR - final
|
||||
|
||||
private final Map<String, Expression> uriVariableExpressions = new HashMap<>();
|
||||
|
||||
private final Expression uriExpression;
|
||||
@@ -95,8 +94,6 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
|
||||
private boolean trustedSpel;
|
||||
|
||||
private boolean encodeUri = true;
|
||||
|
||||
private Expression httpMethodExpression = new ValueExpression<>(HttpMethod.POST);
|
||||
|
||||
private boolean expectReply = true;
|
||||
@@ -127,9 +124,27 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
* <code>true</code>.
|
||||
* @param encodeUri true if the URI should be encoded.
|
||||
* @see UriComponentsBuilder
|
||||
* @deprecated since 5.3 in favor of {@link #setEncodingMode}
|
||||
*/
|
||||
@Deprecated
|
||||
public void setEncodeUri(boolean encodeUri) {
|
||||
this.encodeUri = encodeUri;
|
||||
setEncodingMode(
|
||||
encodeUri
|
||||
? DefaultUriBuilderFactory.EncodingMode.TEMPLATE_AND_VALUES
|
||||
: DefaultUriBuilderFactory.EncodingMode.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the encoding mode to use.
|
||||
* By default this is set to {@link DefaultUriBuilderFactory.EncodingMode#TEMPLATE_AND_VALUES}.
|
||||
* For more complicated scenarios consider to configure an {@link org.springframework.web.util.UriTemplateHandler}
|
||||
* on an externally provided {@link org.springframework.web.client.RestTemplate}.
|
||||
* @param encodingMode the mode to use for uri encoding
|
||||
* @since 5.3
|
||||
*/
|
||||
public void setEncodingMode(DefaultUriBuilderFactory.EncodingMode encodingMode) {
|
||||
Assert.notNull(encodingMode, "'encodingMode' must not be null");
|
||||
this.uriFactory.setEncodingMode(encodingMode);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,32 +306,23 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
Object expectedResponseType = determineExpectedResponseType(requestMessage);
|
||||
|
||||
HttpEntity<?> httpRequest = generateHttpRequest(requestMessage, httpMethod);
|
||||
return exchange(() -> generateUri(requestMessage), httpMethod, httpRequest, expectedResponseType,
|
||||
requestMessage);
|
||||
}
|
||||
|
||||
protected abstract Object exchange(Supplier<URI> uriSupplier, HttpMethod httpMethod, HttpEntity<?> httpRequest,
|
||||
Object expectedResponseType, Message<?> requestMessage);
|
||||
|
||||
private URI generateUri(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()));
|
||||
Map<String, ?> uriVariables = determineUriVariables(requestMessage);
|
||||
UriComponentsBuilder uriComponentsBuilder =
|
||||
uri instanceof String
|
||||
? UriComponentsBuilder.fromUriString((String) uri)
|
||||
: UriComponentsBuilder.fromUri((URI) uri);
|
||||
UriComponents uriComponents = uriComponentsBuilder.buildAndExpand(uriVariables);
|
||||
try {
|
||||
return this.encodeUri ? uriComponents.encode().toUri() : new URI(uriComponents.toUriString());
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
throw new MessageHandlingException(requestMessage, "Invalid URI [" + uri + "] in the [" + this + ']', e);
|
||||
|
||||
Map<String, ?> uriVariables = null;
|
||||
|
||||
if (uri instanceof String) {
|
||||
uriVariables = determineUriVariables(requestMessage);
|
||||
}
|
||||
|
||||
return exchange(uri, httpMethod, httpRequest, expectedResponseType, requestMessage, uriVariables);
|
||||
}
|
||||
|
||||
protected abstract Object exchange(Object uri, HttpMethod httpMethod, HttpEntity<?> httpRequest,
|
||||
Object expectedResponseType, Message<?> requestMessage, Map<String, ?> uriVariables);
|
||||
|
||||
protected Object getReply(ResponseEntity<?> httpResponse) {
|
||||
if (this.expectReply) {
|
||||
HttpHeaders httpHeaders = httpResponse.getHeaders();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -18,7 +18,7 @@ package org.springframework.integration.http.outbound;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.expression.Expression;
|
||||
@@ -29,12 +29,14 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.integration.expression.ValueExpression;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandlingException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.messaging.MessageHandler}
|
||||
@@ -64,6 +66,8 @@ public class HttpRequestExecutingMessageHandler extends AbstractHttpRequestExecu
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
private final boolean restTemplateExplicitlySet;
|
||||
|
||||
/**
|
||||
* Create a handler that will send requests to the provided URI.
|
||||
* @param uri The URI.
|
||||
@@ -109,14 +113,24 @@ public class HttpRequestExecutingMessageHandler extends AbstractHttpRequestExecu
|
||||
* {@link org.springframework.beans.factory.BeanFactory}.
|
||||
* @param restTemplate The rest template.
|
||||
*/
|
||||
public HttpRequestExecutingMessageHandler(Expression uriExpression, RestTemplate restTemplate) {
|
||||
public HttpRequestExecutingMessageHandler(Expression uriExpression, @Nullable RestTemplate restTemplate) {
|
||||
super(uriExpression);
|
||||
this.restTemplate = (restTemplate == null ? new RestTemplate() : restTemplate);
|
||||
this.restTemplateExplicitlySet = restTemplate != null;
|
||||
this.restTemplate = (this.restTemplateExplicitlySet ? restTemplate : new RestTemplate());
|
||||
if (!this.restTemplateExplicitlySet) {
|
||||
this.restTemplate.setUriTemplateHandler(this.uriFactory);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComponentType() {
|
||||
return (this.isExpectReply() ? "http:outbound-gateway" : "http:outbound-channel-adapter");
|
||||
return (isExpectReply() ? "http:outbound-gateway" : "http:outbound-channel-adapter");
|
||||
}
|
||||
|
||||
private void assertLocalRestTemplate(String option) {
|
||||
Assert.isTrue(!this.restTemplateExplicitlySet,
|
||||
() -> "The option '" + option + "' must be provided on the externally configured RestTemplate: "
|
||||
+ this.restTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,6 +139,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractHttpRequestExecu
|
||||
* @see RestTemplate#setErrorHandler(ResponseErrorHandler)
|
||||
*/
|
||||
public void setErrorHandler(ResponseErrorHandler errorHandler) {
|
||||
assertLocalRestTemplate("errorHandler");
|
||||
this.restTemplate.setErrorHandler(errorHandler);
|
||||
}
|
||||
|
||||
@@ -135,6 +150,7 @@ public class HttpRequestExecutingMessageHandler extends AbstractHttpRequestExecu
|
||||
* @see RestTemplate#setMessageConverters(java.util.List)
|
||||
*/
|
||||
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
|
||||
assertLocalRestTemplate("messageConverters");
|
||||
this.restTemplate.setMessageConverters(messageConverters);
|
||||
}
|
||||
|
||||
@@ -144,24 +160,43 @@ public class HttpRequestExecutingMessageHandler extends AbstractHttpRequestExecu
|
||||
* @see RestTemplate#setRequestFactory(ClientHttpRequestFactory)
|
||||
*/
|
||||
public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
|
||||
assertLocalRestTemplate("requestFactory");
|
||||
this.restTemplate.setRequestFactory(requestFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object exchange(Supplier<URI> uriSupplier, HttpMethod httpMethod, HttpEntity<?> httpRequest,
|
||||
Object expectedResponseType, Message<?> requestMessage) {
|
||||
public void setEncodingMode(DefaultUriBuilderFactory.EncodingMode encodingMode) {
|
||||
assertLocalRestTemplate("encodingMode on UriTemplateHandler");
|
||||
super.setEncodingMode(encodingMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object exchange(Object uri, HttpMethod httpMethod, HttpEntity<?> httpRequest,
|
||||
Object expectedResponseType, Message<?> requestMessage, Map<String, ?> uriVariables) {
|
||||
|
||||
URI uri = uriSupplier.get();
|
||||
ResponseEntity<?> httpResponse;
|
||||
try {
|
||||
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
|
||||
httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest,
|
||||
(ParameterizedTypeReference<?>) expectedResponseType);
|
||||
if (uri instanceof URI) {
|
||||
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
|
||||
httpResponse = this.restTemplate.exchange((URI) uri, httpMethod, httpRequest,
|
||||
(ParameterizedTypeReference<?>) expectedResponseType);
|
||||
}
|
||||
else {
|
||||
httpResponse = this.restTemplate.exchange((URI) uri, httpMethod, httpRequest,
|
||||
(Class<?>) expectedResponseType);
|
||||
}
|
||||
}
|
||||
else {
|
||||
httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest,
|
||||
(Class<?>) expectedResponseType);
|
||||
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
|
||||
httpResponse = this.restTemplate.exchange((String) uri, httpMethod, httpRequest,
|
||||
(ParameterizedTypeReference<?>) expectedResponseType, uriVariables);
|
||||
}
|
||||
else {
|
||||
httpResponse = this.restTemplate.exchange((String) uri, httpMethod, httpRequest,
|
||||
(Class<?>) expectedResponseType, uriVariables);
|
||||
}
|
||||
}
|
||||
|
||||
return getReply(httpResponse);
|
||||
}
|
||||
catch (RestClientException e) {
|
||||
|
||||
@@ -844,12 +844,23 @@
|
||||
<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,
|
||||
[DEPRECATED] 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".
|
||||
Deprecated since 5.3 in favor of 'encoding-mode'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="encoding-mode" default="TEMPLATE_AND_VALUES">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Set the encoding mode during URI building.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:union memberTypes="encodingModeEnumeration xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="http-method">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
@@ -943,4 +954,13 @@
|
||||
</xsd:attribute>
|
||||
</xsd:attributeGroup>
|
||||
|
||||
<xsd:simpleType name="encodingModeEnumeration">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="TEMPLATE_AND_VALUES"/>
|
||||
<xsd:enumeration value="VALUES_ONLY"/>
|
||||
<xsd:enumeration value="URI_COMPONENT"/>
|
||||
<xsd:enumeration value="NONE"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
</xsd:schema>
|
||||
|
||||
Reference in New Issue
Block a user