GH-3529: Add HTTP & WebFlux extractResponseBody (#3530)
* GH-3529: Add HTTP & WebFlux extractResponseBody Fixes https://github.com/spring-projects/spring-integration/issues/3529 * Expose a convenient `extractResponseBody` option on the HTTP client components to let end-user to decide if the body of `ResponseEntity` must be extracted (default) or the whole `ResponseEntity` should be produced as a reply message payload * Remove a deprecated since `5.3` `encode-uri` option * Document the new feature * Rework `webflux.adoc` chapter for the code snippet switcher * * Fix language in Docs * Mention a default value in JavaDocs
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -90,7 +90,6 @@ 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-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -83,6 +83,7 @@ public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser {
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel");
|
||||
HttpAdapterParsingUtils.configureUriVariableExpressions(builder, parserContext, element);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "transfer-cookies");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-response-body");
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -102,7 +103,6 @@ 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-2020 the original author or authors.
|
||||
* Copyright 2017-2021 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.core.ParameterizedTypeReference;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.integration.dsl.ComponentsRegistration;
|
||||
import org.springframework.integration.dsl.MessageHandlerSpec;
|
||||
import org.springframework.integration.expression.FunctionExpression;
|
||||
@@ -67,19 +68,6 @@ public abstract class BaseHttpMessageHandlerSpec<S extends BaseHttpMessageHandle
|
||||
return _this();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether the real URI should be encoded after <code>uriVariables</code>
|
||||
* 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.
|
||||
@@ -314,6 +302,7 @@ public abstract class BaseHttpMessageHandlerSpec<S extends BaseHttpMessageHandle
|
||||
/**
|
||||
* Set to {@code true} if you wish {@code Set-Cookie} header in response to be
|
||||
* transferred as {@code Cookie} header in subsequent interaction for a message.
|
||||
* Defaults to false.
|
||||
* @param transferCookies the transferCookies to set.
|
||||
* @return the current Spec.
|
||||
*/
|
||||
@@ -322,6 +311,18 @@ public abstract class BaseHttpMessageHandlerSpec<S extends BaseHttpMessageHandle
|
||||
return _this();
|
||||
}
|
||||
|
||||
/**
|
||||
* The flag to extract a body of the {@link ResponseEntity} for reply message payload.
|
||||
* Defaults to true.
|
||||
* @param extractResponseBody produce a reply message with a whole {@link ResponseEntity} or just its body.
|
||||
* @return the current Spec.
|
||||
* @since 5.5
|
||||
*/
|
||||
public S extractResponseBody(boolean extractResponseBody) {
|
||||
this.target.setExtractResponseBody(extractResponseBody);
|
||||
return _this();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, String> getComponentsToRegister() {
|
||||
this.target.setUriVariableExpressions(this.uriVariableExpressions);
|
||||
|
||||
@@ -101,6 +101,8 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
|
||||
private boolean extractPayloadExplicitlySet = false;
|
||||
|
||||
private boolean extractResponseBody = true;
|
||||
|
||||
private Charset charset = StandardCharsets.UTF_8;
|
||||
|
||||
private boolean transferCookies = false;
|
||||
@@ -114,22 +116,6 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
this.uriExpression = uriExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether the real URI should be encoded after {@code uriVariables}
|
||||
* expanding and before send request via {@link org.springframework.web.client.RestTemplate}.
|
||||
* The default value is {@code true}.
|
||||
* @param encodeUri true if the URI should be encoded.
|
||||
* @see org.springframework.web.util.UriComponentsBuilder
|
||||
* @deprecated since 5.3 in favor of {@link #setEncodingMode}
|
||||
*/
|
||||
@Deprecated
|
||||
public void setEncodeUri(boolean 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}.
|
||||
@@ -202,9 +188,8 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the expected response type for the REST request
|
||||
* otherwise the default response type is {@link ResponseEntity} and will
|
||||
* be returned as a payload of the reply Message.
|
||||
* Specify the expected response type for the REST request.
|
||||
* Otherwise it is null and an empty {@link ResponseEntity} is returned from HTTP client.
|
||||
* To take advantage of the HttpMessageConverters
|
||||
* registered on this adapter, provide a different type).
|
||||
* @param expectedResponseType The expected type.
|
||||
@@ -259,8 +244,8 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
|
||||
/**
|
||||
* Set to true if you wish 'Set-Cookie' headers in responses to be
|
||||
* transferred as 'Cookie' headers in subsequent interactions for
|
||||
* a message.
|
||||
* transferred as 'Cookie' headers in subsequent interactions for a message.
|
||||
* Defaults to false.
|
||||
* @param transferCookies the transferCookies to set.
|
||||
*/
|
||||
public void setTransferCookies(boolean transferCookies) {
|
||||
@@ -278,6 +263,16 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
this.trustedSpel = trustedSpel;
|
||||
}
|
||||
|
||||
/**
|
||||
* The flag to extract a body of the {@link ResponseEntity} for reply message payload.
|
||||
* Defaults to true.
|
||||
* @param extractResponseBody produce a reply message with a whole {@link ResponseEntity} or just its body.
|
||||
* @since 5.5
|
||||
*/
|
||||
public void setExtractResponseBody(boolean extractResponseBody) {
|
||||
this.extractResponseBody = extractResponseBody;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntegrationPatternType getIntegrationPatternType() {
|
||||
return this.expectReply ? super.getIntegrationPatternType() : IntegrationPatternType.outbound_channel_adapter;
|
||||
@@ -329,7 +324,7 @@ public abstract class AbstractHttpRequestExecutingMessageHandler extends Abstrac
|
||||
|
||||
AbstractIntegrationMessageBuilder<?> replyBuilder;
|
||||
MessageBuilderFactory messageBuilderFactory = getMessageBuilderFactory();
|
||||
if (httpResponse.hasBody()) {
|
||||
if (httpResponse.hasBody() && this.extractResponseBody) {
|
||||
Object responseBody = httpResponse.getBody();
|
||||
replyBuilder = (responseBody instanceof Message<?>)
|
||||
? messageBuilderFactory.fromMessage((Message<?>) responseBody)
|
||||
|
||||
@@ -536,6 +536,17 @@
|
||||
<xsd:union memberTypes="xsd:boolean xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="extract-response-body" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Set to 'false' to return the whole 'ResponseEntity' in the reply message payload.
|
||||
The default value is 'true'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:union memberTypes="xsd:boolean xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
@@ -841,16 +852,6 @@
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="encode-uri" type="xsd:string" default="true">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
[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>
|
||||
@@ -892,7 +893,7 @@
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The expected type to which the response body should be converted.
|
||||
Default is 'org.springframework.http.ResponseEntity'.
|
||||
Default is 'null' - no body expected.
|
||||
This attribute cannot be provided if expected-response-type-expression has a value
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
charset="UTF-8"
|
||||
order="77"
|
||||
auto-startup="false"
|
||||
transfer-cookies="true">
|
||||
transfer-cookies="true"
|
||||
extract-response-body="false">
|
||||
<uri-variable name="foo" expression="headers.bar"/>
|
||||
</outbound-gateway>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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,16 +17,13 @@
|
||||
package org.springframework.integration.http.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -48,8 +45,7 @@ import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
|
||||
@@ -59,8 +55,7 @@ import org.springframework.web.client.ResponseErrorHandler;
|
||||
* @author Artem Bilan
|
||||
* @author Biju Kunjummen
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext
|
||||
public class HttpOutboundGatewayParserTests {
|
||||
|
||||
@@ -107,16 +102,17 @@ public class HttpOutboundGatewayParserTests {
|
||||
assertThat(uriExpression.getValue()).isEqualTo("http://localhost/test1");
|
||||
assertThat(TestUtils.getPropertyValue(handler, "httpMethodExpression", Expression.class).getExpressionString())
|
||||
.isEqualTo(HttpMethod.POST.name());
|
||||
assertThat(handlerAccessor.getPropertyValue("charset")).isEqualTo(Charset.forName("UTF-8"));
|
||||
assertThat(handlerAccessor.getPropertyValue("charset")).isEqualTo(StandardCharsets.UTF_8);
|
||||
assertThat(handlerAccessor.getPropertyValue("extractPayload")).isEqualTo(true);
|
||||
assertThat(handlerAccessor.getPropertyValue("transferCookies")).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void fullConfig() throws Exception {
|
||||
public void fullConfig() {
|
||||
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(this.fullConfigEndpoint);
|
||||
HttpRequestExecutingMessageHandler handler = (HttpRequestExecutingMessageHandler) endpointAccessor.getPropertyValue("handler");
|
||||
HttpRequestExecutingMessageHandler handler =
|
||||
(HttpRequestExecutingMessageHandler) endpointAccessor.getPropertyValue("handler");
|
||||
MessageChannel requestChannel = (MessageChannel) new DirectFieldAccessor(
|
||||
this.fullConfigEndpoint).getPropertyValue("inputChannel");
|
||||
assertThat(requestChannel).isEqualTo(this.applicationContext.getBean("requests"));
|
||||
@@ -139,7 +135,7 @@ public class HttpOutboundGatewayParserTests {
|
||||
assertThat(uriExpression.getValue()).isEqualTo("http://localhost/test2");
|
||||
assertThat(TestUtils.getPropertyValue(handler, "httpMethodExpression", Expression.class).getExpressionString())
|
||||
.isEqualTo(HttpMethod.PUT.name());
|
||||
assertThat(handlerAccessor.getPropertyValue("charset")).isEqualTo(Charset.forName("UTF-8"));
|
||||
assertThat(handlerAccessor.getPropertyValue("charset")).isEqualTo(StandardCharsets.UTF_8);
|
||||
assertThat(handlerAccessor.getPropertyValue("extractPayload")).isEqualTo(false);
|
||||
Object requestFactoryBean = this.applicationContext.getBean("testRequestFactory");
|
||||
assertThat(requestFactory).isEqualTo(requestFactoryBean);
|
||||
@@ -161,6 +157,7 @@ public class HttpOutboundGatewayParserTests {
|
||||
assertThat(ObjectUtils.containsElement(mappedRequestHeaders, "requestHeader2")).isTrue();
|
||||
assertThat(mappedResponseHeaders[0]).isEqualTo("responseHeader");
|
||||
assertThat(handlerAccessor.getPropertyValue("transferCookies")).isEqualTo(true);
|
||||
assertThat(handlerAccessor.getPropertyValue("extractResponseBody")).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -182,7 +179,7 @@ public class HttpOutboundGatewayParserTests {
|
||||
assertThat(expression.getExpressionString()).isEqualTo("'http://localhost/test1'");
|
||||
assertThat(TestUtils.getPropertyValue(handler, "httpMethodExpression", Expression.class).getExpressionString())
|
||||
.isEqualTo(HttpMethod.POST.name());
|
||||
assertThat(handlerAccessor.getPropertyValue("charset")).isEqualTo(Charset.forName("UTF-8"));
|
||||
assertThat(handlerAccessor.getPropertyValue("charset")).isEqualTo(StandardCharsets.UTF_8);
|
||||
assertThat(handlerAccessor.getPropertyValue("extractPayload")).isEqualTo(true);
|
||||
assertThat(handlerAccessor.getPropertyValue("transferCookies")).isEqualTo(false);
|
||||
|
||||
@@ -199,21 +196,17 @@ public class HttpOutboundGatewayParserTests {
|
||||
public void withAdvice() {
|
||||
HttpRequestExecutingMessageHandler handler = (HttpRequestExecutingMessageHandler) new DirectFieldAccessor(
|
||||
this.withAdvice).getPropertyValue("handler");
|
||||
handler.handleMessage(new GenericMessage<String>("foo"));
|
||||
handler.handleMessage(new GenericMessage<>("foo"));
|
||||
assertThat(adviceCalled).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInt2718FailForGatewayRequestChannelAttribute() {
|
||||
try {
|
||||
new ClassPathXmlApplicationContext("HttpOutboundGatewayWithinChainTests-fail-context.xml", this.getClass())
|
||||
.close();
|
||||
fail("Expected BeanDefinitionParsingException");
|
||||
}
|
||||
catch (BeansException e) {
|
||||
assertThat(e instanceof BeanDefinitionParsingException).isTrue();
|
||||
assertThat(e.getMessage().contains("'request-channel' attribute isn't allowed for a nested")).isTrue();
|
||||
}
|
||||
assertThatExceptionOfType(BeanDefinitionParsingException.class)
|
||||
.isThrownBy(() ->
|
||||
new ClassPathXmlApplicationContext("HttpOutboundGatewayWithinChainTests-fail-context.xml",
|
||||
getClass()))
|
||||
.withMessageContaining("'request-channel' attribute isn't allowed for a nested");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -225,13 +218,14 @@ public class HttpOutboundGatewayParserTests {
|
||||
public static class StubErrorHandler implements ResponseErrorHandler {
|
||||
|
||||
@Override
|
||||
public boolean hasError(ClientHttpResponse response) throws IOException {
|
||||
public boolean hasError(ClientHttpResponse response) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse response) throws IOException {
|
||||
public void handleError(ClientHttpResponse response) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class FooAdvice extends AbstractRequestHandlerAdvice {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-2019 the original author or authors.
|
||||
* Copyright 2016-2021 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.
|
||||
@@ -363,6 +363,7 @@ public class HttpDslTests {
|
||||
.handle(Http.outboundGateway("/service/internal?{params}")
|
||||
.uriVariable("params", "payload")
|
||||
.expectedResponseType(String.class)
|
||||
.extractResponseBody(false)
|
||||
.errorHandler(new HttpProxyResponseErrorHandler()),
|
||||
e -> e.id("serviceInternalGateway"))
|
||||
.get();
|
||||
|
||||
Reference in New Issue
Block a user