From 27e1f3a6365b7c7f7d292b341155d88fbd5073c2 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Wed, 31 Mar 2021 15:13:33 -0400 Subject: [PATCH] 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 --- .../HttpOutboundChannelAdapterParser.java | 3 +- .../config/HttpOutboundGatewayParser.java | 4 +- .../http/dsl/BaseHttpMessageHandlerSpec.java | 29 +- ...actHttpRequestExecutingMessageHandler.java | 39 +- .../http/config/spring-integration-http.xsd | 23 +- ...HttpOutboundGatewayParserTests-context.xml | 3 +- .../HttpOutboundGatewayParserTests.java | 50 +- .../integration/http/dsl/HttpDslTests.java | 3 +- .../config/spring-integration-webflux.xsd | 11 + ...FluxOutboundGatewayParserTests-context.xml | 3 +- .../WebFluxOutboundGatewayParserTests.java | 7 +- .../webflux/dsl/WebFluxDslTests.java | 15 +- src/reference/asciidoc/http.adoc | 8 +- src/reference/asciidoc/webflux.adoc | 477 ++++++++---------- src/reference/asciidoc/whats-new.adoc | 3 +- 15 files changed, 331 insertions(+), 347 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 a35f0ceefc..9dfa056300 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-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; 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 ffefef279e..b49156cc4c 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-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; diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/BaseHttpMessageHandlerSpec.java b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/BaseHttpMessageHandlerSpec.java index aa4c9f9d11..a04c63c9ee 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/BaseHttpMessageHandlerSpec.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/BaseHttpMessageHandlerSpec.java @@ -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 BaseHttpMessageHandlerSpecuriVariables - * expanding and before send request via underlying implementation. The default value is true. - * @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 getComponentsToRegister() { this.target.setUriVariableExpressions(this.uriVariableExpressions); diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/AbstractHttpRequestExecutingMessageHandler.java b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/AbstractHttpRequestExecutingMessageHandler.java index 66d2f0b215..22d97e4f11 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/AbstractHttpRequestExecutingMessageHandler.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/outbound/AbstractHttpRequestExecutingMessageHandler.java @@ -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) diff --git a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http.xsd b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http.xsd index ba2cdf218a..b1ccaf59eb 100644 --- a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http.xsd +++ b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http.xsd @@ -536,6 +536,17 @@ + + + + Set to 'false' to return the whole 'ResponseEntity' in the reply message payload. + The default value is 'true'. + + + + + + @@ -841,16 +852,6 @@ ]]> - - - - [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'. - - - @@ -892,7 +893,7 @@ 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 diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml index 2e65fb4c47..d1459beb50 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml @@ -36,7 +36,8 @@ charset="UTF-8" order="77" auto-startup="false" - transfer-cookies="true"> + transfer-cookies="true" + extract-response-body="false"> diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java index e82c3b742f..1dc8ea9611 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java @@ -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("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 { diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java index b7eb2ceb21..7fdc2561ef 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java @@ -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(); diff --git a/spring-integration-webflux/src/main/resources/org/springframework/integration/webflux/config/spring-integration-webflux.xsd b/spring-integration-webflux/src/main/resources/org/springframework/integration/webflux/config/spring-integration-webflux.xsd index ee4def54c2..bb07ef4908 100644 --- a/spring-integration-webflux/src/main/resources/org/springframework/integration/webflux/config/spring-integration-webflux.xsd +++ b/spring-integration-webflux/src/main/resources/org/springframework/integration/webflux/config/spring-integration-webflux.xsd @@ -581,6 +581,17 @@ + + + + Set to 'false' to return the whole 'ResponseEntity' in the reply message payload. + The default value is 'true'. + + + + + + diff --git a/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/config/WebFluxOutboundGatewayParserTests-context.xml b/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/config/WebFluxOutboundGatewayParserTests-context.xml index 6f1a069d60..cbe48c001e 100644 --- a/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/config/WebFluxOutboundGatewayParserTests-context.xml +++ b/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/config/WebFluxOutboundGatewayParserTests-context.xml @@ -38,7 +38,8 @@ transfer-cookies="true" reply-payload-to-flux="true" body-extractor="bodyExtractor" - publisher-element-type-expression="headers.elementType"> + publisher-element-type-expression="headers.elementType" + extract-response-body="false"> diff --git a/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/config/WebFluxOutboundGatewayParserTests.java b/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/config/WebFluxOutboundGatewayParserTests.java index be823dc776..319e8fd74f 100644 --- a/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/config/WebFluxOutboundGatewayParserTests.java +++ b/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/config/WebFluxOutboundGatewayParserTests.java @@ -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. @@ -18,7 +18,6 @@ package org.springframework.integration.webflux.config; import static org.assertj.core.api.Assertions.assertThat; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Map; @@ -79,7 +78,7 @@ public class WebFluxOutboundGatewayParserTests { 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); assertThat(handlerAccessor.getPropertyValue("replyPayloadToFlux")).isEqualTo(false); @@ -128,6 +127,8 @@ public class WebFluxOutboundGatewayParserTests { assertThat(handlerAccessor.getPropertyValue("bodyExtractor")).isSameAs(this.bodyExtractor); assertThat(handlerAccessor.getPropertyValue("publisherElementTypeExpression.expression")) .isEqualTo("headers.elementType"); + assertThat(handlerAccessor.getPropertyValue("extractResponseBody")) + .isEqualTo(false); } } diff --git a/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/dsl/WebFluxDslTests.java b/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/dsl/WebFluxDslTests.java index f7e26acaa3..202b39f0aa 100644 --- a/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/dsl/WebFluxDslTests.java +++ b/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/dsl/WebFluxDslTests.java @@ -25,6 +25,7 @@ import java.security.Principal; import java.time.Duration; import java.util.Collections; +import org.assertj.core.api.InstanceOfAssertFactories; import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -37,9 +38,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.ResolvableType; import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.integration.channel.FixedSubscriberChannel; import org.springframework.integration.channel.QueueChannel; @@ -172,11 +175,14 @@ public class WebFluxDslTests { Message receive = replyChannel.receive(10_000); - assertThat(receive).isNotNull(); - assertThat(receive.getPayload()).isInstanceOf(Flux.class); + assertThat(receive).isNotNull() + .extracting(Message::getPayload) + .asInstanceOf(InstanceOfAssertFactories.type(ResponseEntity.class)) + .extracting(HttpEntity>::getBody) + .isInstanceOf(Flux.class); @SuppressWarnings("unchecked") - Flux response = (Flux) receive.getPayload(); + Flux response = ((ResponseEntity>) receive.getPayload()).getBody(); StepVerifier.create(response) .expectNext("FOO", "BAR") @@ -410,7 +416,8 @@ public class WebFluxDslTests { .handle(WebFlux.outboundGateway("https://www.springsource.org/spring-integration") .httpMethod(HttpMethod.GET) .replyPayloadToFlux(true) - .expectedResponseType(String.class), + .expectedResponseType(String.class) + .extractResponseBody(false), e -> e .id("webFluxWithReplyPayloadToFlux") .customizeMonoReply( diff --git a/src/reference/asciidoc/http.adoc b/src/reference/asciidoc/http.adoc index 0b4156ebd8..1fbff3d7cd 100644 --- a/src/reference/asciidoc/http.adoc +++ b/src/reference/asciidoc/http.adoc @@ -8,8 +8,8 @@ See also <<./webflux.adoc#webflux,WebFlux Support>>. You need to include this dependency into your project: ==== +[source, xml, subs="normal", role="primary"] .Maven -[source, xml, subs="normal"] ---- org.springframework.integration @@ -17,9 +17,8 @@ You need to include this dependency into your project: {project-version} ---- - +[source, groovy, subs="normal", role="secondary"] .Gradle -[source, groovy, subs="normal"] ---- compile "org.springframework.integration:spring-integration-http:{project-version}" ---- @@ -223,6 +222,9 @@ The `expected-response-type` must be compatible with the (configured or default) This can be an abstract class or even an interface (such as `java.io.Serializable` when you use Java serialization and `Content-Type: application/x-java-serialized-object`). ===== +Starting with version 5.5, the `HttpRequestExecutingMessageHandler` exposes an `extractResponseBody` flag (which is `true` by default) to return just the response body, or to return the whole `ResponseEntity` as the reply message payload, independently of the provided `expectedResponseType`. +If a body is not present in the `ResponseEntity`, this flag is ignored and the whole `ResponseEntity` is returned. + [[http-namespace]] === HTTP Namespace Support diff --git a/src/reference/asciidoc/webflux.adoc b/src/reference/asciidoc/webflux.adoc index bb9e11727c..b38451d95d 100644 --- a/src/reference/asciidoc/webflux.adoc +++ b/src/reference/asciidoc/webflux.adoc @@ -6,8 +6,8 @@ The WebFlux Spring Integration module (`spring-integration-webflux`) allows for You need to include this dependency into your project: ==== +[source, xml, subs="normal", role="primary"] .Maven -[source, xml, subs="normal"] ---- org.springframework.integration @@ -15,9 +15,8 @@ You need to include this dependency into your project: {project-version} ---- - +[source, groovy, subs="normal", role="secondary"] .Gradle -[source, groovy, subs="normal"] ---- compile "org.springframework.integration:spring-integration-webflux:{project-version}" ---- @@ -29,133 +28,6 @@ The WebFlux support consists of the following gateway implementations: `WebFluxI The support is fully based on the Spring https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#spring-webflux[WebFlux] and https://projectreactor.io/[Project Reactor] foundations. See <<./http.adoc#http,HTTP Support>> for more information, since many options are shared between reactive and regular HTTP components. -[[webflux-inbound]] -=== WebFlux Inbound Components - -Starting with version 5.0, the `WebFluxInboundEndpoint` implementation of `WebHandler` is provided. -This component is similar to the MVC-based `HttpRequestHandlingEndpointSupport`, with which it shares some common options through the newly extracted `BaseHttpInboundEndpoint`. -It is used in the Spring WebFlux reactive environment (instead of MVC). -The following example shows a simple implementation of a WebFlux endpoint: - -==== -[source,java] ----- -@Configuration -@EnableWebFlux -@EnableIntegration -public class ReactiveHttpConfiguration { - - @Bean - public WebFluxInboundEndpoint simpleInboundEndpoint() { - WebFluxInboundEndpoint endpoint = new WebFluxInboundEndpoint(); - RequestMapping requestMapping = new RequestMapping(); - requestMapping.setPathPatterns("/test"); - endpoint.setRequestMapping(requestMapping); - endpoint.setRequestChannelName("serviceChannel"); - return endpoint; - } - - @ServiceActivator(inputChannel = "serviceChannel") - String service() { - return "It works!"; - } - -} ----- -==== - -The configuration is similar to the `HttpRequestHandlingEndpointSupport` (mentioned prior to the example), except that we use `@EnableWebFlux` to add the WebFlux infrastructure to our integration application. -Also, the `WebFluxInboundEndpoint` performs `sendAndReceive` operations to the downstream flow by using back-pressure, on-demand based capabilities, provided by the reactive HTTP server implementation. - -NOTE: The reply part is non-blocking as well and is based on the internal `FutureReplyChannel`, which is flat-mapped to a reply `Mono` for on-demand resolution. - -You can configure the `WebFluxInboundEndpoint` with a custom `ServerCodecConfigurer`, a `RequestedContentTypeResolver`, and even a `ReactiveAdapterRegistry`. -The latter provides a mechanism you can use to return a reply as any reactive type: Reactor `Flux`, RxJava `Observable`, `Flowable`, and others. -This way, we can implement https://en.wikipedia.org/wiki/Server-sent_events[Server Sent Events] scenarios with Spring Integration components, as the following example shows: - -==== -[source,java] ----- -@Bean -public IntegrationFlow sseFlow() { - return IntegrationFlows - .from(WebFlux.inboundGateway("/sse") - .requestMapping(m -> m.produces(MediaType.TEXT_EVENT_STREAM_VALUE))) - .handle((p, h) -> Flux.just("foo", "bar", "baz")) - .get(); -} ----- -==== - -See <<./http.adoc#http-request-mapping,Request Mapping Support>> and <<./http.adoc#http-cors,Cross-origin Resource Sharing (CORS) Support>> for more possible configuration options. - -When the request body is empty or `payloadExpression` returns `null`, the request params (`MultiValueMap`) is used for a `payload` of the target message to process. - -[[webflux-validation]] -==== Payload Validation - -Starting with version 5.2, the `WebFluxInboundEndpoint` can be configured with a `Validator`. -Unlike the MVC validation in the <<./http.adoc#http-validation,HTTP Support>>, it is used to validate elements in the `Publisher` to which a request has been converted by the `HttpMessageReader`, before performing a fallback and `payloadExpression` functions. -The Framework can't assume how complex the `Publisher` object can be after building the final payload. -If there is a requirements to restrict validation visibility for exactly final payload (or its `Publisher` elements), the validation should go downstream instead of WebFlux endpoint. -See more information in the Spring WebFlux https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/web-reactive.html#webflux-fn-handler-validation[documentation]. -An invalid payload is rejected with an `IntegrationWebExchangeBindException` (a `WebExchangeBindException` extension), containing all the validation `Errors`. -See more in Spring Framework https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation[Reference Manual] about validation. - -[[webflux-outbound]] -=== WebFlux Outbound Components - -The `WebFluxRequestExecutingMessageHandler` (starting with version 5.0) implementation is similar to `HttpRequestExecutingMessageHandler`. -It uses a `WebClient` from the Spring Framework WebFlux module. -To configure it, define a bean similar to the following: - -==== -[source,xml] ----- - - - - ----- -==== - -You can configure a `WebClient` instance to use, as the following example shows: - -==== -[source,xml] ----- - - - - - - - ----- -==== - -The `WebClient` `exchange()` operation returns a `Mono`, which is mapped (by using several `Mono.map()` steps) to an `AbstractIntegrationMessageBuilder` as the output from the `WebFluxRequestExecutingMessageHandler`. -Together with the `ReactiveChannel` as an `outputChannel`, the `Mono` evaluation is deferred until a downstream subscription is made. -Otherwise, it is treated as an `async` mode, and the `Mono` response is adapted to a `SettableListenableFuture` for an asynchronous reply from the `WebFluxRequestExecutingMessageHandler`. -The target payload of the output message depends on the `WebFluxRequestExecutingMessageHandler` configuration. -The `setExpectedResponseType(Class)` or `setExpectedResponseTypeExpression(Expression)` identifies the target type of the response body element conversion. -If `replyPayloadToFlux` is set to `true`, the response body is converted to a `Flux` with the provided `expectedResponseType` for each element, and this `Flux` is sent as the payload downstream. -Afterwards, you can use a <<./splitter.adoc#splitter,splitter>> to iterate over this `Flux` in a reactive manner. - -In addition a `BodyExtractor` can be injected into the `WebFluxRequestExecutingMessageHandler` instead of the `expectedResponseType` and `replyPayloadToFlux` properties. -It can be used for low-level access to the `ClientHttpResponse` and more control over body and HTTP headers conversion. -Spring Integration provides `ClientHttpResponseBodyExtractor` as a identity function to produce (downstream) the whole `ClientHttpResponse` and any other possible custom logic. - -Starting with version 5.2, the `WebFluxRequestExecutingMessageHandler` supports reactive `Publisher`, `Resource`, and `MultiValueMap` types as the request message payload. -A respective `BodyInserter` is used internally to be populated into the `WebClient.RequestBodySpec`. -When the payload is a reactive `Publisher`, a configured `publisherElementType` or `publisherElementTypeExpression` can be used to determine a type for the publisher's element type. -The expression must be resolved to a `Class`, `String` which is resolved to the target `Class` or `ParameterizedTypeReference`. - -See <<./http.adoc#http-outbound,HTTP Outbound Components>> for more possible configuration options. - [[webflux-namespace]] === WebFlux Namespace Support @@ -182,61 +54,221 @@ To include it in your configuration, add the following namespace declaration in ---- ==== -==== Inbound +[[webflux-inbound]] +=== WebFlux Inbound Components -To configure Spring Integration WebFlux with XML, you need to use appropriate components from the `int-webflux` namespace: `inbound-channel-adapter` or `inbound-gateway`, corresponding to request and response requirements, respectively. -The following example shows how to configure both an inbound channel adapter and an inbound gateway: +Starting with version 5.0, the `WebFluxInboundEndpoint` implementation of `WebHandler` is provided. +This component is similar to the MVC-based `HttpRequestHandlingEndpointSupport`, with which it shares some common options through the newly extracted `BaseHttpInboundEndpoint`. +It is used in the Spring WebFlux reactive environment (instead of MVC). +The following example shows a simple implementation of a WebFlux endpoint: ==== -[source,xml] +[source, java, role="primary"] +.Java DSL ---- - - - -
- +@Bean +public IntegrationFlow inboundChannelAdapterFlow() { + return IntegrationFlows + .from(WebFlux.inboundChannelAdapter("/reactivePost") + .requestMapping(m -> m.methods(HttpMethod.POST)) + .requestPayloadType(ResolvableType.forClassWithGenerics(Flux.class, String.class)) + .statusCodeFunction(m -> HttpStatus.ACCEPTED)) + .channel(c -> c.queue("storeChannel")) + .get(); +} +---- +[source, kotlin, role="secondary"] +.Kotlin DSL +---- +@Bean +fun inboundChannelAdapterFlow() = + integrationFlow( + WebFlux.inboundChannelAdapter("/reactivePost") + .apply { + requestMapping { m -> m.methods(HttpMethod.POST) } + requestPayloadType(ResolvableType.forClassWithGenerics(Flux::class.java, String::class.java)) + statusCodeFunction { m -> HttpStatus.ACCEPTED } + }) + { + channel { queue("storeChannel") } + } +---- +[source, java, role="secondary"] +.Java +---- +@Configuration +@EnableWebFlux +@EnableIntegration +public class ReactiveHttpConfiguration { - - - -
- + @Bean + public WebFluxInboundEndpoint simpleInboundEndpoint() { + WebFluxInboundEndpoint endpoint = new WebFluxInboundEndpoint(); + RequestMapping requestMapping = new RequestMapping(); + requestMapping.setPathPatterns("/test"); + endpoint.setRequestMapping(requestMapping); + endpoint.setRequestChannelName("serviceChannel"); + return endpoint; + } + + @ServiceActivator(inputChannel = "serviceChannel") + String service() { + return "It works!"; + } + +} +---- +[source, xml, role="secondary"] +.XML +---- + + + ---- ==== -==== Outbound +The configuration is similar to the `HttpRequestHandlingEndpointSupport` (mentioned prior to the example), except that we use `@EnableWebFlux` to add the WebFlux infrastructure to our integration application. +Also, the `WebFluxInboundEndpoint` performs `sendAndReceive` operations to the downstream flow by using back-pressure, on-demand based capabilities, provided by the reactive HTTP server implementation. -If you want to execute the HTTP request in a reactive, non-blocking way, you can use the `outbound-gateway` or `outbound-channel-adapter`. -The following example shows how to configure both an outbound gateway and an outbound channel adapter: +NOTE: The reply part is non-blocking as well and is based on the internal `FutureReplyChannel`, which is flat-mapped to a reply `Mono` for on-demand resolution. + +You can configure the `WebFluxInboundEndpoint` with a custom `ServerCodecConfigurer`, a `RequestedContentTypeResolver`, and even a `ReactiveAdapterRegistry`. +The latter provides a mechanism you can use to return a reply as any reactive type: Reactor `Flux`, RxJava `Observable`, `Flowable`, and others. +This way, we can implement https://en.wikipedia.org/wiki/Server-sent_events[Server Sent Events] scenarios with Spring Integration components, as the following example shows: ==== -[source,xml] +[source, java, role="primary"] +.Java DSL +---- +@Bean +public IntegrationFlow sseFlow() { + return IntegrationFlows + .from(WebFlux.inboundGateway("/sse") + .requestMapping(m -> m.produces(MediaType.TEXT_EVENT_STREAM_VALUE))) + .handle((p, h) -> Flux.just("foo", "bar", "baz")) + .get(); +} +---- +[source, kotlin, role="secondary"] +.Kotlin DSL +---- +@Bean +fun sseFlow() = + integrationFlow( + WebFlux.inboundGateway("/sse") + .requestMapping(m -> m.produces(MediaType.TEXT_EVENT_STREAM_VALUE))) + { + handle { (p, h) -> Flux.just("foo", "bar", "baz") } + } +---- +[source, java, role="secondary"] +.Java +---- +@Bean +public WebFluxInboundEndpoint webfluxInboundGateway() { + WebFluxInboundEndpoint endpoint = new WebFluxInboundEndpoint(); + RequestMapping requestMapping = new RequestMapping(); + requestMapping.setPathPatterns("/sse"); + requestMapping.setProduces(MediaType.TEXT_EVENT_STREAM_VALUE); + endpoint.setRequestMapping(requestMapping); + endpoint.setRequestChannelName("requests"); + return endpoint; +} +---- +[source, xml, role="secondary"] +.XML +---- + + + + + +---- +==== + +See <<./http.adoc#http-request-mapping,Request Mapping Support>> and <<./http.adoc#http-cors,Cross-origin Resource Sharing (CORS) Support>> for more possible configuration options. + +When the request body is empty or `payloadExpression` returns `null`, the request params (`MultiValueMap`) is used for a `payload` of the target message to process. + +[[webflux-validation]] +==== Payload Validation + +Starting with version 5.2, the `WebFluxInboundEndpoint` can be configured with a `Validator`. +Unlike the MVC validation in the <<./http.adoc#http-validation,HTTP Support>>, it is used to validate elements in the `Publisher` to which a request has been converted by the `HttpMessageReader`, before performing a fallback and `payloadExpression` functions. +The Framework can't assume how complex the `Publisher` object can be after building the final payload. +If there is a requirements to restrict validation visibility for exactly final payload (or its `Publisher` elements), the validation should go downstream instead of WebFlux endpoint. +See more information in the Spring WebFlux https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/web-reactive.html#webflux-fn-handler-validation[documentation]. +An invalid payload is rejected with an `IntegrationWebExchangeBindException` (a `WebExchangeBindException` extension), containing all the validation `Errors`. +See more in Spring Framework https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation[Reference Manual] about validation. + +[[webflux-outbound]] +=== WebFlux Outbound Components + +The `WebFluxRequestExecutingMessageHandler` (starting with version 5.0) implementation is similar to `HttpRequestExecutingMessageHandler`. +It uses a `WebClient` from the Spring Framework WebFlux module. +To configure it, define a bean similar to the following: + +==== +[source, java, role="primary"] +.Java DSL +---- +@Bean +public IntegrationFlow outboundReactive() { + return f -> f + .handle(WebFlux.>outboundGateway(m -> + UriComponentsBuilder.fromUriString("http://localhost:8080/foo") + .queryParams(m.getPayload()) + .build() + .toUri()) + .httpMethod(HttpMethod.GET) + .expectedResponseType(String.class)); +} +---- +[source, kotlin, role="secondary"] +.Kotlin DSL +---- +@Bean +fun outboundReactive() = + integrationFlow { + handle( + WebFlux.outboundGateway>({ m -> + UriComponentsBuilder.fromUriString("http://localhost:8080/foo") + .queryParams(m.getPayload()) + .build() + .toUri() + }) + .httpMethod(HttpMethod.GET) + .expectedResponseType(String::class.java) + ) + } +---- +[source, java, role="secondary"] +.Java +---- +@ServiceActivator(inputChannel = "reactiveHttpOutRequest") +@Bean +public WebFluxRequestExecutingMessageHandler reactiveOutbound(WebClient client) { + WebFluxRequestExecutingMessageHandler handler = + new WebFluxRequestExecutingMessageHandler("http://localhost:8080/foo", client); + handler.setHttpMethod(HttpMethod.POST); + handler.setExpectedResponseType(String.class); + return handler; +} +---- +[source, xml, role="secondary"] +.XML ---- - ---- ==== -[[webflux-java-config]] -=== Configuring WebFlux Endpoints with Java +The `WebClient` `exchange()` operation returns a `Mono`, which is mapped (by using several `Mono.map()` steps) to an `AbstractIntegrationMessageBuilder` as the output from the `WebFluxRequestExecutingMessageHandler`. +Together with the `ReactiveChannel` as an `outputChannel`, the `Mono` evaluation is deferred until a downstream subscription is made. +Otherwise, it is treated as an `async` mode, and the `Mono` response is adapted to a `SettableListenableFuture` for an asynchronous reply from the `WebFluxRequestExecutingMessageHandler`. +The target payload of the output message depends on the `WebFluxRequestExecutingMessageHandler` configuration. +The `setExpectedResponseType(Class)` or `setExpectedResponseTypeExpression(Expression)` identifies the target type of the response body element conversion. +If `replyPayloadToFlux` is set to `true`, the response body is converted to a `Flux` with the provided `expectedResponseType` for each element, and this `Flux` is sent as the payload downstream. +Afterwards, you can use a <<./splitter.adoc#splitter,splitter>> to iterate over this `Flux` in a reactive manner. -The following example shows how to configure a WebFlux inbound endpoint with Java: +In addition a `BodyExtractor` can be injected into the `WebFluxRequestExecutingMessageHandler` instead of the `expectedResponseType` and `replyPayloadToFlux` properties. +It can be used for low-level access to the `ClientHttpResponse` and more control over body and HTTP headers conversion. +Spring Integration provides `ClientHttpResponseBodyExtractor` as a identity function to produce (downstream) the whole `ClientHttpResponse` and any other possible custom logic. -==== -[source, java] ----- -@Bean -public WebFluxInboundEndpoint jsonInboundEndpoint() { - WebFluxInboundEndpoint endpoint = new WebFluxInboundEndpoint(); - RequestMapping requestMapping = new RequestMapping(); - requestMapping.setPathPatterns("/persons"); - endpoint.setRequestMapping(requestMapping); - endpoint.setRequestChannel(fluxResultChannel()); - return endpoint; -} +Starting with version 5.2, the `WebFluxRequestExecutingMessageHandler` supports reactive `Publisher`, `Resource`, and `MultiValueMap` types as the request message payload. +A respective `BodyInserter` is used internally to be populated into the `WebClient.RequestBodySpec`. +When the payload is a reactive `Publisher`, a configured `publisherElementType` or `publisherElementTypeExpression` can be used to determine a type for the publisher's element type. +The expression must be resolved to a `Class`, `String` which is resolved to the target `Class` or `ParameterizedTypeReference`. -@Bean -public MessageChannel fluxResultChannel() { - return new FluxMessageChannel(); -} +Starting with version 5.5, the `WebFluxRequestExecutingMessageHandler` exposes an `extractResponseBody` flag (which is `true` by default) to return just the response body, or to return the whole `ResponseEntity` as the reply message payload, independently of the provided `expectedResponseType` or `replyPayloadToFlux`. +If a body is not present in the `ResponseEntity`, this flag is ignored and the whole `ResponseEntity` is returned. -@ServiceActivator(inputChannel = "fluxResultChannel") -Flux getPersons() { - return Flux.just(new Person("Jane"), new Person("Jason"), new Person("John")); -} ----- -==== - -The following example shows how to configure a WebFlux inbound gateway with the Java DSL: - -==== -[source, java] ----- -@Bean -public IntegrationFlow inboundChannelAdapterFlow() { - return IntegrationFlows - .from(WebFlux.inboundChannelAdapter("/reactivePost") - .requestMapping(m -> m.methods(HttpMethod.POST)) - .requestPayloadType(ResolvableType.forClassWithGenerics(Flux.class, String.class)) - .statusCodeFunction(m -> HttpStatus.ACCEPTED)) - .channel(c -> c.queue("storeChannel")) - .get(); -} ----- -==== - -The following example shows how to configure a WebFlux outbound gateway with Java: - -==== -[source, java] ----- -@ServiceActivator(inputChannel = "reactiveHttpOutRequest") -@Bean -public WebFluxRequestExecutingMessageHandler reactiveOutbound(WebClient client) { - WebFluxRequestExecutingMessageHandler handler = - new WebFluxRequestExecutingMessageHandler("http://localhost:8080/foo", client); - handler.setHttpMethod(HttpMethod.POST); - handler.setExpectedResponseType(String.class); - return handler; -} ----- -==== - -The following example shows how to configure a WebFlux outbound gateway with the Java DSL: - -==== -[source, java] ----- -@Bean -public IntegrationFlow outboundReactive() { - return f -> f - .handle(WebFlux.>outboundGateway(m -> - UriComponentsBuilder.fromUriString("http://localhost:8080/foo") - .queryParams(m.getPayload()) - .build() - .toUri()) - .httpMethod(HttpMethod.GET) - .expectedResponseType(String.class)); -} ----- -==== +See <<./http.adoc#http-outbound,HTTP Outbound Components>> for more possible configuration options. [[webflux-header-mapping]] === WebFlux Header Mappings diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index bebcb8061f..6921b4c70d 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -63,7 +63,8 @@ See <<./redis.adoc#redis,Redis Support>> for more information. ==== HTTP Changes The `HttpRequestExecutingMessageHandler` doesn't fallback to the `application/x-java-serialized-object` content type any more and lets the `RestTemplate` make the final decision for the request body conversion based on the `HttpMessageConverter` provided. - +It also has now an `extractResponseBody` flag (which is `true` by default) to return just the response body, or to return the whole `ResponseEntity` as the reply message payload, independently of the provided `expectedResponseType`. +Same option is presented for the `WebFluxRequestExecutingMessageHandler`, too. See <<./http.adoc#http,HTTP Support>> for more information. [[x5.5-file]]