Upgrade to the SF and SA BUILD-SNAPSHOTs
* Upgrade to Jackson-2.9.0 to align with SF * Fix `ReactiveHttpRequestExecutingMessageHandler` to use latest `WebClient` * Introduce `Supplier<URI> uriSupplier` to evaluate the `realUri` on demand. In case of `WebClient` use `.uri(Function)` to perform `uriSupplier` * Move `doNext()` for the `WebClientException` out of `one-way` block. According the `RestTemplate` logic the erroneous response is handled before returning from the `exchange()`. Therefore both request/reply and one-way scenarios must have the same error handling logic * Some refactoring to the `AbstractHttpRequestExecutingMessageHandler` and `AbstractHttpRequestExecutingMessageHandler` according a new `uriSupplier`
This commit is contained in:
committed by
Gary Russell
parent
f751398cfe
commit
9e175054d0
16
build.gradle
16
build.gradle
@@ -106,7 +106,7 @@ subprojects { subproject ->
|
||||
hibernateVersion = '5.2.5.Final'
|
||||
hsqldbVersion = '2.3.3'
|
||||
h2Version = '1.4.180'
|
||||
jackson2Version = '2.8.5'
|
||||
jackson2Version = '2.9.0.pr2'
|
||||
javaxActivationVersion = '1.1.1'
|
||||
javaxMailVersion = '1.5.6'
|
||||
jedisVersion = '2.9.0'
|
||||
@@ -130,15 +130,15 @@ subprojects { subproject ->
|
||||
servletApiVersion = '3.1.0'
|
||||
slf4jVersion = "1.7.21"
|
||||
smackVersion = '4.1.7'
|
||||
springAmqpVersion = project.hasProperty('springAmqpVersion') ? project.springAmqpVersion : '2.0.0.M3'
|
||||
springDataJpaVersion = '2.0.0.M2'
|
||||
springDataMongoVersion = '2.0.0.M2'
|
||||
springDataRedisVersion = '2.0.0.M2'
|
||||
springGemfireVersion = '2.0.0.M2'
|
||||
springSecurityVersion = '4.2.2.RELEASE'
|
||||
springAmqpVersion = project.hasProperty('springAmqpVersion') ? project.springAmqpVersion : '2.0.0.BUILD-SNAPSHOT'
|
||||
springDataJpaVersion = '2.0.0.BUILD-SNAPSHOT'
|
||||
springDataMongoVersion = '2.0.0.BUILD-SNAPSHOT'
|
||||
springDataRedisVersion = '2.0.0.BUILD-SNAPSHOT'
|
||||
springGemfireVersion = '2.0.0.BUILD-SNAPSHOT'
|
||||
springSecurityVersion = '5.0.0.BUILD-SNAPSHOT'
|
||||
springSocialTwitterVersion = '2.0.0.M1'
|
||||
springRetryVersion = '1.2.0.RELEASE'
|
||||
springVersion = project.hasProperty('springVersion') ? project.springVersion : '5.0.0.M5'
|
||||
springVersion = project.hasProperty('springVersion') ? project.springVersion : '5.0.0.BUILD-SNAPSHOT'
|
||||
springWsVersion = '2.4.0.RELEASE'
|
||||
tomcatVersion = "8.5.9"
|
||||
xmlUnitVersion = '1.6'
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.integration.http.outbound;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -25,6 +26,7 @@ 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;
|
||||
|
||||
@@ -46,7 +48,6 @@ import org.springframework.integration.support.AbstractIntegrationMessageBuilder
|
||||
import org.springframework.integration.support.MessageBuilderFactory;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandlingException;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -253,46 +254,44 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
|
||||
@Override
|
||||
protected Object handleRequestMessage(Message<?> requestMessage) {
|
||||
HttpMethod httpMethod = determineHttpMethod(requestMessage);
|
||||
|
||||
if (!shouldIncludeRequestBody(httpMethod) && this.extractPayloadExplicitlySet) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("The 'extractPayload' attribute has no relevance for the current request " +
|
||||
"since the HTTP Method is '" + httpMethod +
|
||||
"', and no request body will be sent for that method.");
|
||||
}
|
||||
}
|
||||
|
||||
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()));
|
||||
URI realUri = null;
|
||||
Map<String, ?> uriVariables = determineUriVariables(requestMessage);
|
||||
UriComponentsBuilder uriComponentsBuilder = uri instanceof String
|
||||
? UriComponentsBuilder.fromUriString((String) uri)
|
||||
: UriComponentsBuilder.fromUri((URI) uri);
|
||||
UriComponents uriComponents = uriComponentsBuilder.buildAndExpand(uriVariables);
|
||||
try {
|
||||
HttpMethod httpMethod = this.determineHttpMethod(requestMessage);
|
||||
|
||||
if (!shouldIncludeRequestBody(httpMethod) && this.extractPayloadExplicitlySet) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("The 'extractPayload' attribute has no relevance for the current request " +
|
||||
"since the HTTP Method is '" + httpMethod +
|
||||
"', and no request body will be sent for that method.");
|
||||
}
|
||||
}
|
||||
|
||||
Object expectedResponseType = determineExpectedResponseType(requestMessage);
|
||||
|
||||
HttpEntity<?> httpRequest = generateHttpRequest(requestMessage, httpMethod);
|
||||
Map<String, ?> uriVariables = this.determineUriVariables(requestMessage);
|
||||
UriComponentsBuilder uriComponentsBuilder = uri instanceof String
|
||||
? UriComponentsBuilder.fromUriString((String) uri)
|
||||
: UriComponentsBuilder.fromUri((URI) uri);
|
||||
UriComponents uriComponents = uriComponentsBuilder.buildAndExpand(uriVariables);
|
||||
realUri = this.encodeUri ? uriComponents.toUri() : new URI(uriComponents.toUriString());
|
||||
|
||||
return exchange(realUri, httpMethod, httpRequest, expectedResponseType, requestMessage);
|
||||
return this.encodeUri ? uriComponents.toUri() : new URI(uriComponents.toUriString());
|
||||
}
|
||||
catch (MessagingException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new MessageHandlingException(requestMessage, "HTTP request execution failed for URI ["
|
||||
+ (realUri == null ? uri : realUri) + "]", e);
|
||||
catch (URISyntaxException e) {
|
||||
throw new MessageHandlingException(requestMessage, "Invalid URI [" + uri + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Object exchange(URI realUri, HttpMethod httpMethod, HttpEntity<?> httpRequest,
|
||||
Object expectedResponseType, Message<?> requestMessage);
|
||||
|
||||
protected Object getReply(ResponseEntity<?> httpResponse) {
|
||||
if (this.expectReply) {
|
||||
HttpHeaders httpHeaders = httpResponse.getHeaders();
|
||||
@@ -341,7 +340,7 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
}
|
||||
}
|
||||
|
||||
private HttpEntity<?> generateHttpRequest(Message<?> message, HttpMethod httpMethod) throws Exception {
|
||||
private HttpEntity<?> generateHttpRequest(Message<?> message, HttpMethod httpMethod) {
|
||||
Assert.notNull(message, "message must not be null");
|
||||
return (this.extractPayload) ? this.createHttpEntityFromPayload(message, httpMethod)
|
||||
: this.createHttpEntityFromMessage(message, httpMethod);
|
||||
@@ -355,7 +354,7 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
}
|
||||
HttpHeaders httpHeaders = this.mapHeaders(message);
|
||||
if (!shouldIncludeRequestBody(httpMethod)) {
|
||||
return new HttpEntity<Object>(httpHeaders);
|
||||
return new HttpEntity<>(httpHeaders);
|
||||
}
|
||||
// otherwise, we are creating a request with a body and need to deal with the content-type header as well
|
||||
if (httpHeaders.getContentType() == null) {
|
||||
@@ -370,7 +369,7 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
payload = this.convertToMultiValueMap((Map<?, ?>) payload);
|
||||
}
|
||||
}
|
||||
return new HttpEntity<Object>(payload, httpHeaders);
|
||||
return new HttpEntity<>(payload, httpHeaders);
|
||||
}
|
||||
|
||||
private HttpEntity<?> createHttpEntityFromMessage(Message<?> message, HttpMethod httpMethod) {
|
||||
@@ -379,7 +378,7 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
httpHeaders.setContentType(new MediaType("application", "x-java-serialized-object"));
|
||||
return new HttpEntity<Object>(message, httpHeaders);
|
||||
}
|
||||
return new HttpEntity<Object>(httpHeaders);
|
||||
return new HttpEntity<>(httpHeaders);
|
||||
}
|
||||
|
||||
protected HttpHeaders mapHeaders(Message<?> message) {
|
||||
@@ -471,7 +470,7 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
* If all keys and values are Strings, we'll consider the Map to be form data.
|
||||
*/
|
||||
private boolean isFormData(Map<Object, ?> map) {
|
||||
for (Object key : map.keySet()) {
|
||||
for (Object key : map.keySet()) {
|
||||
if (!(key instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
@@ -498,7 +497,7 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
}
|
||||
}
|
||||
|
||||
private Object determineExpectedResponseType(Message<?> requestMessage) throws Exception {
|
||||
private Object determineExpectedResponseType(Message<?> requestMessage) {
|
||||
Object expectedResponseType = null;
|
||||
if (this.expectedResponseTypeExpression != null) {
|
||||
expectedResponseType = this.expectedResponseTypeExpression.getValue(this.evaluationContext, requestMessage);
|
||||
@@ -507,11 +506,17 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
Assert.state(expectedResponseType instanceof Class<?>
|
||||
|| expectedResponseType instanceof String
|
||||
|| expectedResponseType instanceof ParameterizedTypeReference,
|
||||
"'expectedResponseType' can be an instance of 'Class<?>', 'String' or 'ParameterizedTypeReference<?>'; "
|
||||
+ "evaluation resulted in a" + expectedResponseType.getClass() + ".");
|
||||
"'expectedResponseType' can be an instance of 'Class<?>', 'String' " +
|
||||
"or 'ParameterizedTypeReference<?>'; " +
|
||||
"evaluation resulted in a" + expectedResponseType.getClass() + ".");
|
||||
if (expectedResponseType instanceof String && StringUtils.hasText((String) expectedResponseType)) {
|
||||
expectedResponseType = ClassUtils.forName((String) expectedResponseType,
|
||||
getApplicationContext().getClassLoader());
|
||||
try {
|
||||
expectedResponseType = ClassUtils.forName((String) expectedResponseType,
|
||||
getApplicationContext().getClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException("Cannot load class for name: " + expectedResponseType, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return expectedResponseType;
|
||||
@@ -532,9 +537,9 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
}
|
||||
|
||||
return ExpressionEvalMap.from(expressions)
|
||||
.usingEvaluationContext(this.evaluationContext)
|
||||
.withRoot(requestMessage)
|
||||
.build();
|
||||
.usingEvaluationContext(this.evaluationContext)
|
||||
.withRoot(requestMessage)
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.integration.http.outbound;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
@@ -33,8 +34,10 @@ import org.springframework.integration.expression.ValueExpression;
|
||||
import org.springframework.integration.mapping.HeaderMapper;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -149,16 +152,24 @@ public class HttpRequestExecutingMessageHandler extends AbstractHttpRequestExecu
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object exchange(URI uri, HttpMethod httpMethod, HttpEntity<?> httpRequest, Object expectedResponseType,
|
||||
Message<?> requestMessage) {
|
||||
protected Object exchange(Supplier<URI> uriSupplier, HttpMethod httpMethod, HttpEntity<?> httpRequest,
|
||||
Object expectedResponseType, Message<?> requestMessage) {
|
||||
URI uri = uriSupplier.get();
|
||||
ResponseEntity<?> httpResponse;
|
||||
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
|
||||
httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest,
|
||||
(ParameterizedTypeReference<?>) expectedResponseType);
|
||||
try {
|
||||
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
|
||||
httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest,
|
||||
(ParameterizedTypeReference<?>) expectedResponseType);
|
||||
}
|
||||
else {
|
||||
httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest,
|
||||
(Class<?>) expectedResponseType);
|
||||
}
|
||||
return getReply(httpResponse);
|
||||
}
|
||||
else {
|
||||
httpResponse = this.restTemplate.exchange(uri, httpMethod, httpRequest, (Class<?>) expectedResponseType);
|
||||
catch (RestClientException e) {
|
||||
throw new MessageHandlingException(requestMessage,
|
||||
"HTTP request execution failed for URI [" + uri + "]", e);
|
||||
}
|
||||
return getReply(httpResponse);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.integration.http.outbound;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
@@ -32,7 +33,6 @@ import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.BodyExtractors;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.reactive.function.client.WebClientException;
|
||||
@@ -111,53 +111,29 @@ public class ReactiveHttpRequestExecutingMessageHandler extends AbstractHttpRequ
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object exchange(URI uri, HttpMethod httpMethod, HttpEntity<?> httpRequest, Object expectedResponseType,
|
||||
Message<?> requestMessage) {
|
||||
WebClient.UriSpec uriSpec;
|
||||
protected Object exchange(Supplier<URI> uriSupplier, HttpMethod httpMethod, HttpEntity<?> httpRequest,
|
||||
Object expectedResponseType, Message<?> requestMessage) {
|
||||
|
||||
// TODO use WebClient.method(HttpMethod) in the future version
|
||||
WebClient.RequestBodySpec requestSpec =
|
||||
this.webClient.method(httpMethod)
|
||||
.uri(b -> uriSupplier.get())
|
||||
.headers(httpRequest.getHeaders());
|
||||
|
||||
switch (httpMethod) {
|
||||
case GET:
|
||||
uriSpec = this.webClient.get();
|
||||
break;
|
||||
case HEAD:
|
||||
uriSpec = this.webClient.head();
|
||||
break;
|
||||
case POST:
|
||||
uriSpec = this.webClient.post();
|
||||
break;
|
||||
case PUT:
|
||||
uriSpec = this.webClient.put();
|
||||
break;
|
||||
case PATCH:
|
||||
uriSpec = this.webClient.patch();
|
||||
break;
|
||||
case DELETE:
|
||||
uriSpec = this.webClient.delete();
|
||||
break;
|
||||
case OPTIONS:
|
||||
uriSpec = this.webClient.options();
|
||||
break;
|
||||
case TRACE:
|
||||
throw new UnsupportedOperationException("WebClient doesn't support the TRACE HTTP method");
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported HTTP method");
|
||||
}
|
||||
|
||||
WebClient.HeaderSpec spec = uriSpec.uri(uri)
|
||||
.headers(httpRequest.getHeaders());
|
||||
|
||||
Mono<ClientResponse> responseMono;
|
||||
if (httpRequest.hasBody()) {
|
||||
responseMono = spec.exchange(BodyInserters.fromObject(httpRequest.getBody()));
|
||||
}
|
||||
else {
|
||||
responseMono = spec.exchange();
|
||||
requestSpec.body(httpRequest.getBody());
|
||||
}
|
||||
|
||||
Mono<ClientResponse> responseMono = requestSpec.exchange()
|
||||
.doOnNext(response -> {
|
||||
HttpStatus httpStatus = response.statusCode();
|
||||
if (httpStatus.is4xxClientError() || httpStatus.is5xxServerError()) {
|
||||
throw new WebClientException(
|
||||
"ClientResponse has erroneous status code: " + httpStatus.value() +
|
||||
" " + httpStatus.getReasonPhrase());
|
||||
}
|
||||
});
|
||||
|
||||
if (isExpectReply()) {
|
||||
|
||||
ResolvableType responseType;
|
||||
|
||||
if (expectedResponseType instanceof ParameterizedTypeReference<?>) {
|
||||
@@ -180,16 +156,7 @@ public class ReactiveHttpRequestExecutingMessageHandler extends AbstractHttpRequ
|
||||
.map(this::getReply);
|
||||
}
|
||||
else {
|
||||
responseMono
|
||||
.doOnNext(response -> {
|
||||
HttpStatus httpStatus = response.statusCode();
|
||||
if (httpStatus.is4xxClientError() || httpStatus.is5xxServerError()) {
|
||||
throw new WebClientException(
|
||||
"ClientResponse has erroneous status code: " + httpStatus.value() +
|
||||
" " + httpStatus.getReasonPhrase());
|
||||
}
|
||||
})
|
||||
.subscribe(v -> { }, ex -> sendErrorMessage(requestMessage, ex));
|
||||
responseMono.subscribe(v -> { }, ex -> sendErrorMessage(requestMessage, ex));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.
|
||||
@@ -16,9 +16,7 @@
|
||||
|
||||
package org.springframework.integration.http.outbound;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@@ -52,6 +50,7 @@ import org.springframework.messaging.support.GenericMessage;
|
||||
* @author Wallace Wadge
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class UriVariableExpressionTests {
|
||||
@@ -78,7 +77,6 @@ public class UriVariableExpressionTests {
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertEquals("intentional", e.getCause().getMessage());
|
||||
assertThat(e.getMessage(), containsString("http://test/bar"));
|
||||
}
|
||||
assertEquals("http://test/bar", uriHolder.get().toString());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user