From 262e624ef47b6ac2709ed480c6e9614ebc96736f Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Thu, 4 Oct 2018 16:26:01 -0400 Subject: [PATCH] INT-4479: RequestEntity as root for status code JIRA: https://jira.spring.io/browse/INT-4479 For better user experience expose a `RequestEntity` as a root object for evaluation context for `statusCodeExpression` execution --- .../dsl/HttpInboundEndpointSupportSpec.java | 5 +- .../http/inbound/BaseHttpInboundEndpoint.java | 5 +- .../HttpRequestHandlingController.java | 41 +++++++++----- .../HttpRequestHandlingEndpointSupport.java | 55 ++++++++++--------- .../HttpRequestHandlingMessagingGateway.java | 22 +++++--- ...gMessagingGatewayWithPathMappingTests.java | 34 +++++++++--- .../inbound/WebFluxInboundEndpoint.java | 29 ++++++---- .../webflux/dsl/WebFluxDslTests.java | 5 +- src/reference/asciidoc/http.adoc | 3 +- src/reference/asciidoc/whats-new.adoc | 7 +++ 10 files changed, 130 insertions(+), 76 deletions(-) diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpInboundEndpointSupportSpec.java b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpInboundEndpointSupportSpec.java index cba75f78fb..bd8c2b62e5 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpInboundEndpointSupportSpec.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/dsl/HttpInboundEndpointSupportSpec.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2018 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. @@ -27,6 +27,7 @@ import org.springframework.expression.Expression; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.RequestEntity; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.integration.dsl.ComponentsRegistration; import org.springframework.integration.dsl.MessagingGatewaySpec; @@ -277,7 +278,7 @@ public abstract class HttpInboundEndpointSupportSpec statusCodeFunction) { + public S statusCodeFunction(Function, ?> statusCodeFunction) { return statusCodeExpression(new FunctionExpression<>(statusCodeFunction)); } diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/BaseHttpInboundEndpoint.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/BaseHttpInboundEndpoint.java index fc01dbf8c3..5a001a6387 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/BaseHttpInboundEndpoint.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/BaseHttpInboundEndpoint.java @@ -25,6 +25,7 @@ import org.springframework.core.ResolvableType; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; @@ -276,8 +277,8 @@ public class BaseHttpInboundEndpoint extends MessagingGatewaySupport implements } } - protected HttpStatus evaluateHttpStatus() { - Object value = this.statusCodeExpression.getValue(this.evaluationContext); + protected HttpStatus evaluateHttpStatus(HttpEntity httpEntity) { + Object value = this.statusCodeExpression.getValue(this.evaluationContext, httpEntity); return buildHttpStatus(value); } diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingController.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingController.java index 7e81d19a0d..07a51ae6dd 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingController.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingController.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -27,6 +27,8 @@ import org.springframework.context.MessageSource; import org.springframework.expression.Expression; import org.springframework.expression.common.LiteralExpression; import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.http.RequestEntity; +import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.messaging.Message; import org.springframework.util.Assert; @@ -52,15 +54,25 @@ import org.springframework.web.servlet.mvc.Controller; * @author Mark Fisher * @author Gary Russell * @author Artem Bilan + * * @since 2.0 */ public class HttpRequestHandlingController extends HttpRequestHandlingEndpointSupport implements Controller { - private static final String DEFAULT_ERROR_CODE = "spring.integration.http.handler.error"; + /** + * The View model key for the error code. + */ + public static final String DEFAULT_ERROR_CODE = "spring.integration.http.handler.error"; - private static final String DEFAULT_REPLY_KEY = "reply"; + /** + * The View model key for reply. + */ + public static final String DEFAULT_REPLY_KEY = "reply"; - private static final String DEFAULT_ERRORS_KEY = "errors"; + /** + * The View model key for errors. + */ + public static final String DEFAULT_ERRORS_KEY = "errors"; private volatile Expression viewExpression; @@ -82,7 +94,6 @@ public class HttpRequestHandlingController extends HttpRequestHandlingEndpointSu /** * Specify the view name. - * * @param viewName The view name. */ public void setViewName(String viewName) { @@ -94,7 +105,6 @@ public class HttpRequestHandlingController extends HttpRequestHandlingEndpointSu * Specify the key to be used when adding the reply Message or payload to the core map (will be payload only unless * the value of {@link HttpRequestHandlingController#setExtractReplyPayload(boolean)} is false). The * default key is "reply". - * * @param replyKey The reply key. */ public void setReplyKey(String replyKey) { @@ -104,7 +114,6 @@ public class HttpRequestHandlingController extends HttpRequestHandlingEndpointSu /** * The key used to expose {@link Errors} in the core, in the case that message handling fails. Defaults to * "errors". - * * @param errorsKey The key value to set. */ public void setErrorsKey(String errorsKey) { @@ -116,7 +125,6 @@ public class HttpRequestHandlingController extends HttpRequestHandlingEndpointSu * provided in an object error to be optionally translated in the standard MVC way using a {@link MessageSource}. * The default value is spring.integration.http.handler.error. Three arguments are provided: the * exception, its message and its stack trace as a String. - * * @param errorCode The error code to set. */ public void setErrorCode(String errorCode) { @@ -126,7 +134,6 @@ public class HttpRequestHandlingController extends HttpRequestHandlingEndpointSu /** * Specifies a SpEL expression to evaluate in order to generate the view name. * The EvaluationContext will be populated with the reply message as the root object, - * * @param viewExpression The view expression. */ public void setViewExpression(Expression viewExpression) { @@ -144,11 +151,14 @@ public class HttpRequestHandlingController extends HttpRequestHandlingEndpointSu * 'expectReply' property is true, it will also generate a response from the reply Message once received. */ @Override - public final ModelAndView handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) - throws Exception { + public final ModelAndView handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) { ModelAndView modelAndView = new ModelAndView(); try { - Message replyMessage = super.doHandleRequest(servletRequest, servletResponse); + ServletServerHttpRequest request = prepareRequest(servletRequest); + + RequestEntity httpEntity = prepareRequestEntity(request); + + Message replyMessage = doHandleRequest(servletRequest, httpEntity, servletResponse); ServletServerHttpResponse response = new ServletServerHttpResponse(servletResponse); if (replyMessage != null) { Object reply = setupResponseAndConvertReply(response, replyMessage); @@ -156,7 +166,7 @@ public class HttpRequestHandlingController extends HttpRequestHandlingEndpointSu modelAndView.addObject(this.replyKey, reply); } else { - setStatusCodeIfNeeded(response); + setStatusCodeIfNeeded(response, httpEntity); } if (this.viewExpression != null) { @@ -183,8 +193,9 @@ public class HttpRequestHandlingController extends HttpRequestHandlingEndpointSu PrintWriter stackTrace = new PrintWriter(new StringWriter()); e.printStackTrace(stackTrace); errors.reject(this.errorCode, new Object[] { e, e.getMessage(), stackTrace.toString() }, - "A Spring Integration handler raised an exception while handling an HTTP request. The exception is of type " - + e.getClass() + " and it has a message: (" + e.getMessage() + ")"); + "A Spring Integration handler raised an exception while handling an HTTP request. " + + "The exception is of type " + e.getClass() + " and it has a message: (" + + e.getMessage() + ")"); modelAndView.addObject(this.errorsKey, errors); } return modelAndView; diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java index 2ac91d66c3..95a7e22505 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingEndpointSupport.java @@ -36,6 +36,7 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.http.HttpEntity; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.ResourceHttpMessageConverter; @@ -245,33 +246,26 @@ public abstract class HttpRequestHandlingEndpointSupport extends BaseHttpInbound * Handles the HTTP request by generating a Message and sending it to the request channel. If this gateway's * 'expectReply' property is true, it will also generate a response from the reply Message once received. * @param servletRequest The servlet request. + * @param httpEntity the request entity to use. * @param servletResponse The servlet response. * @return The response Message. - * @throws IOException Any IOException. */ - protected final Message doHandleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) - throws IOException { + protected final Message doHandleRequest(HttpServletRequest servletRequest, RequestEntity httpEntity, + HttpServletResponse servletResponse) { if (isRunning()) { - return actualDoHandleRequest(servletRequest, servletResponse); + return actualDoHandleRequest(servletRequest, httpEntity, servletResponse); } else { return createServiceUnavailableResponse(); } } - @SuppressWarnings({ "rawtypes", "unchecked" }) - private Message actualDoHandleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) - throws IOException { + @SuppressWarnings("unchecked") + private Message actualDoHandleRequest(HttpServletRequest servletRequest, RequestEntity httpEntity, + HttpServletResponse servletResponse) { + this.activeCount.incrementAndGet(); try { - ServletServerHttpRequest request = this.prepareRequest(servletRequest); - - Object requestBody = null; - if (isReadable(request)) { - requestBody = extractRequestBody(request); - } - HttpEntity httpEntity = new HttpEntity(requestBody, request.getHeaders()); - StandardEvaluationContext evaluationContext = this.createEvaluationContext(); evaluationContext.setRootObject(httpEntity); @@ -284,7 +278,7 @@ public abstract class HttpRequestHandlingEndpointSupport extends BaseHttpInbound Cookie[] requestCookies = servletRequest.getCookies(); if (!ObjectUtils.isEmpty(requestCookies)) { - Map cookies = new HashMap(requestCookies.length); + Map cookies = new HashMap<>(requestCookies.length); for (Cookie requestCookie : requestCookies) { cookies.put(requestCookie.getName(), requestCookie); } @@ -312,7 +306,7 @@ public abstract class HttpRequestHandlingEndpointSupport extends BaseHttpInbound evaluationContext.setVariable("matrixVariables", matrixVariables); } - Map headers = getHeaderMapper().toHeaders(request.getHeaders()); + Map headers = getHeaderMapper().toHeaders(httpEntity.getHeaders()); Object payload = null; if (getPayloadExpression() != null) { // create payload based on SpEL @@ -330,8 +324,8 @@ public abstract class HttpRequestHandlingEndpointSupport extends BaseHttpInbound } if (payload == null) { - if (requestBody != null) { - payload = requestBody; + if (httpEntity.getBody() != null) { + payload = httpEntity.getBody(); } else { payload = requestParams; @@ -350,9 +344,9 @@ public abstract class HttpRequestHandlingEndpointSupport extends BaseHttpInbound Message message = messageBuilder .setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_URL, - request.getURI().toString()) + httpEntity.getUrl().toString()) .setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_METHOD, - request.getMethod().toString()) + httpEntity.getMethod().toString()) .setHeader(org.springframework.integration.http.HttpHeaders.USER_PRINCIPAL, servletRequest.getUserPrincipal()) .build(); @@ -366,7 +360,7 @@ public abstract class HttpRequestHandlingEndpointSupport extends BaseHttpInbound if (getStatusCodeExpression() != null) { reply = getMessageBuilderFactory().withPayload(e.getMessage()) .setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE, - evaluateHttpStatus()) + evaluateHttpStatus(httpEntity)) .build(); } else { @@ -419,9 +413,9 @@ public abstract class HttpRequestHandlingEndpointSupport extends BaseHttpInbound } - protected void setStatusCodeIfNeeded(ServerHttpResponse response) { + protected void setStatusCodeIfNeeded(ServerHttpResponse response, HttpEntity httpEntity) { if (getStatusCodeExpression() != null) { - HttpStatus httpStatus = evaluateHttpStatus(); + HttpStatus httpStatus = evaluateHttpStatus(httpEntity); if (httpStatus != null) { response.setStatusCode(httpStatus); } @@ -437,7 +431,7 @@ public abstract class HttpRequestHandlingEndpointSupport extends BaseHttpInbound * @return the processed request (multipart wrapper if necessary) * @see MultipartResolver#resolveMultipart */ - private ServletServerHttpRequest prepareRequest(HttpServletRequest servletRequest) { + protected ServletServerHttpRequest prepareRequest(HttpServletRequest servletRequest) { if (servletRequest instanceof MultipartHttpServletRequest) { return new MultipartHttpInputMessage((MultipartHttpServletRequest) servletRequest); } @@ -472,8 +466,17 @@ public abstract class HttpRequestHandlingEndpointSupport extends BaseHttpInbound return convertedMap; } + protected RequestEntity prepareRequestEntity(ServletServerHttpRequest request) throws IOException { + Object requestBody = null; + if (isReadable(request)) { + requestBody = extractRequestBody(request); + } + + return new RequestEntity<>(requestBody, request.getHeaders(), request.getMethod(), request.getURI()); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) - private Object extractRequestBody(ServletServerHttpRequest request) throws IOException { + protected Object extractRequestBody(ServletServerHttpRequest request) throws IOException { MediaType contentType = request.getHeaders().getContentType(); if (contentType == null) { contentType = MediaType.APPLICATION_OCTET_STREAM; diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGateway.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGateway.java index c6409184f0..44ff622f62 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGateway.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGateway.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -20,13 +20,13 @@ import java.io.IOException; import java.util.Collections; import java.util.List; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServletServerHttpRequest; @@ -60,6 +60,7 @@ import org.springframework.web.HttpRequestHandler; * @author Mark Fisher * @author Oleg Zhurakousky * @author Artem Bilan + * * @since 2.0 */ public class HttpRequestHandlingMessagingGateway extends HttpRequestHandlingEndpointSupport @@ -81,7 +82,6 @@ public class HttpRequestHandlingMessagingGateway extends HttpRequestHandlingEndp * Flag to determine if conversion and writing out of message handling exceptions should be attempted (default * false, in which case they will simply be re-thrown). If the flag is true and no message converter can convert the * exception a new exception will be thrown. - * * @param convertExceptions the flag to set */ public void setConvertExceptions(boolean convertExceptions) { @@ -94,15 +94,18 @@ public class HttpRequestHandlingMessagingGateway extends HttpRequestHandlingEndp * response will be written by the {@link HttpMessageConverter}s. */ public final void handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) - throws ServletException, IOException { + throws IOException { + Object responseContent = null; Message responseMessage; - final ServletServerHttpRequest request = new ServletServerHttpRequest(servletRequest); - final ServletServerHttpResponse response = new ServletServerHttpResponse(servletResponse); + ServletServerHttpRequest request = new ServletServerHttpRequest(servletRequest); + ServletServerHttpResponse response = new ServletServerHttpResponse(servletResponse); + + RequestEntity httpEntity = prepareRequestEntity(request); try { - responseMessage = super.doHandleRequest(servletRequest, servletResponse); + responseMessage = doHandleRequest(servletRequest, httpEntity, servletResponse); if (responseMessage != null) { responseContent = setupResponseAndConvertReply(response, responseMessage); } @@ -140,7 +143,7 @@ public class HttpRequestHandlingMessagingGateway extends HttpRequestHandlingEndp } } else { - setStatusCodeIfNeeded(response); + setStatusCodeIfNeeded(response, httpEntity); } } @@ -164,10 +167,11 @@ public class HttpRequestHandlingMessagingGateway extends HttpRequestHandlingEndp @SuppressWarnings({ "unchecked", "rawtypes" }) private void writeResponse(Object content, ServletServerHttpResponse response, List acceptTypes) throws IOException { + if (CollectionUtils.isEmpty(acceptTypes)) { acceptTypes = Collections.singletonList(MediaType.ALL); } - for (HttpMessageConverter converter : this.getMessageConverters()) { + for (HttpMessageConverter converter : getMessageConverters()) { for (MediaType acceptType : acceptTypes) { if (converter.canWrite(content.getClass(), acceptType)) { converter.write(content, acceptType, response); diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java index e4b4fe8b6e..23b1727721 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/inbound/HttpRequestHandlingMessagingGatewayWithPathMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; +import java.io.IOException; import java.util.Map; import org.junit.Test; @@ -28,6 +29,8 @@ import org.junit.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.http.RequestEntity; +import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.http.AbstractHttpInboundTests; import org.springframework.messaging.Message; @@ -47,11 +50,11 @@ import org.springframework.web.servlet.HandlerMapping; */ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends AbstractHttpInboundTests { - private static ExpressionParser PARSER = new SpelExpressionParser(); + private static final ExpressionParser PARSER = new SpelExpressionParser(); @Test - public void withoutExpression() throws Exception { + public void withoutExpression() throws IOException { DirectChannel echoChannel = new DirectChannel(); echoChannel.subscribe(message -> { MessageChannel replyChannel = (MessageChannel) message.getHeaders().getReplyChannel(); @@ -62,7 +65,8 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends Abs request.setContentType("text/plain"); request.setParameter("foo", "bar"); - request.setContent("hello".getBytes()); + String body = "hello"; + request.setContent(body.getBytes()); request.setRequestURI("/fname/bill/lname/clinton"); HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true); @@ -78,7 +82,9 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends Abs MockHttpServletResponse response = new MockHttpServletResponse(); - Object result = gateway.doHandleRequest(request, response); + RequestEntity httpEntity = prepareRequestEntity(body, new ServletServerHttpRequest(request)); + + Object result = gateway.doHandleRequest(request, httpEntity, response); assertThat(result, instanceOf(Message.class)); assertEquals("hello", ((Message) result).getPayload()); @@ -97,7 +103,8 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends Abs request.setMethod("POST"); request.setContentType("text/plain"); request.setParameter("foo", "bar"); - request.setContent("hello".getBytes()); + String body = "hello"; + request.setContent(body.getBytes()); String requestURI = "/fname/bill/lname/clinton"; @@ -120,7 +127,9 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends Abs gateway.afterPropertiesSet(); gateway.start(); - Object result = gateway.doHandleRequest(request, response); + RequestEntity httpEntity = prepareRequestEntity(body, new ServletServerHttpRequest(request)); + + Object result = gateway.doHandleRequest(request, httpEntity, response); assertThat(result, instanceOf(Message.class)); assertEquals("bill", ((Message) result).getPayload()); } @@ -140,7 +149,8 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends Abs request.setMethod("POST"); request.setContentType("text/plain"); request.setParameter("foo", "bar"); - request.setContent("hello".getBytes()); + String body = "hello"; + request.setContent(body.getBytes()); String requestURI = "/fname/bill/lname/clinton"; @@ -163,9 +173,15 @@ public class HttpRequestHandlingMessagingGatewayWithPathMappingTests extends Abs gateway.afterPropertiesSet(); gateway.start(); - Object result = gateway.doHandleRequest(request, response); + RequestEntity httpEntity = prepareRequestEntity(body, new ServletServerHttpRequest(request)); + + Object result = gateway.doHandleRequest(request, httpEntity, response); assertThat(result, instanceOf(Message.class)); assertEquals("bill", ((Map) ((Message) result).getPayload()).get("f")); } + private static RequestEntity prepareRequestEntity(Object body, ServletServerHttpRequest request) throws IOException { + return new RequestEntity<>(body, request.getHeaders(), request.getMethod(), request.getURI()); + } + } diff --git a/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/inbound/WebFluxInboundEndpoint.java b/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/inbound/WebFluxInboundEndpoint.java index 2a35863c42..5f6aecf504 100644 --- a/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/inbound/WebFluxInboundEndpoint.java +++ b/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/inbound/WebFluxInboundEndpoint.java @@ -35,11 +35,11 @@ import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ResolvableType; import org.springframework.expression.Expression; import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; @@ -63,6 +63,7 @@ import org.springframework.web.server.WebHandler; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; /** * A {@link MessagingGatewaySupport} implementation for Spring WebFlux @@ -151,16 +152,18 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W return extractRequestBody(exchange) .doOnSubscribe(s -> this.activeCount.incrementAndGet()) .switchIfEmpty(Mono.just(exchange.getRequest().getQueryParams())) - .map(body -> new HttpEntity<>(body, exchange.getRequest().getHeaders())) + .map(body -> + new RequestEntity<>(body, exchange.getRequest().getHeaders(), + exchange.getRequest().getMethod(), exchange.getRequest().getURI())) .flatMap(entity -> buildMessage(entity, exchange)) - .flatMap(requestMessage -> { + .flatMap(requestTuple -> { if (this.expectReply) { - return sendAndReceiveMessageReactive(requestMessage) + return sendAndReceiveMessageReactive(requestTuple.getT1()) .flatMap(replyMessage -> populateResponse(exchange, replyMessage)); } else { - send(requestMessage); - return setStatusCode(exchange); + send(requestTuple.getT1()); + return setStatusCode(exchange, requestTuple.getT2()); } }) .doOnTerminate(this.activeCount::decrementAndGet); @@ -235,7 +238,9 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W } @SuppressWarnings("unchecked") - private Mono> buildMessage(HttpEntity httpEntity, ServerWebExchange exchange) { + private Mono, RequestEntity>> buildMessage(RequestEntity httpEntity, + ServerWebExchange exchange) { + ServerHttpRequest request = exchange.getRequest(); HttpHeaders requestHeaders = request.getHeaders(); Map exchangeAttributes = exchange.getAttributes(); @@ -258,7 +263,8 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W } Map> matrixVariables = - (Map>) exchangeAttributes.get(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE); + (Map>) exchangeAttributes + .get(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE); if (!CollectionUtils.isEmpty(matrixVariables)) { evaluationContext.setVariable("matrixVariables", matrixVariables); @@ -314,7 +320,8 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W messageBuilder .setHeader(org.springframework.integration.http.HttpHeaders.USER_PRINCIPAL, principal)) .defaultIfEmpty(messageBuilder) - .map(AbstractIntegrationMessageBuilder::build); + .map(AbstractIntegrationMessageBuilder::build) + .zipWith(Mono.just(httpEntity)); } private Mono populateResponse(ServerWebExchange exchange, Message replyMessage) { @@ -481,10 +488,10 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W } - private Mono setStatusCode(ServerWebExchange exchange) { + private Mono setStatusCode(ServerWebExchange exchange, RequestEntity requestEntity) { ServerHttpResponse response = exchange.getResponse(); if (getStatusCodeExpression() != null) { - HttpStatus httpStatus = evaluateHttpStatus(); + HttpStatus httpStatus = evaluateHttpStatus(requestEntity); if (httpStatus != null) { response.setStatusCode(httpStatus); } 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 8cfe3cd4e3..ba4bd552d1 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 @@ -355,7 +355,10 @@ public class WebFluxDslTests { .from(WebFlux.inboundChannelAdapter("/reactivePost") .requestMapping(m -> m.methods(HttpMethod.POST)) .requestPayloadType(ResolvableType.forClassWithGenerics(Flux.class, String.class)) - .statusCodeFunction(m -> HttpStatus.ACCEPTED)) + .statusCodeFunction(e -> + HttpMethod.POST.equals(e.getMethod()) + ? HttpStatus.ACCEPTED + : HttpStatus.BAD_REQUEST)) .channel(c -> c.queue("storeChannel")) .get(); } diff --git a/src/reference/asciidoc/http.adoc b/src/reference/asciidoc/http.adoc index f2c0651faf..fc3a6fdf4a 100644 --- a/src/reference/asciidoc/http.adoc +++ b/src/reference/asciidoc/http.adoc @@ -360,10 +360,11 @@ instances of which can be injected into the `HttpRequestHandlingEndpointSupport` Starting with version 4.1, you can configure the `` with a `status-code-expression` to override the default `200 OK` status. The expression must return an object that can be converted to an `org.springframework.http.HttpStatus` enum value. -The `evaluationContext` has a `BeanResolver` but no variables, so the usage of this attribute is somewhat limited. +The `evaluationContext` has a `BeanResolver` and, starting with version 5.1, is supplied with the `RequestEntity` as root object. An example might be to resolve, at runtime, some scoped bean that returns a status code value. However, most likely, it is set to a fixed value such as `status-code=expression="204"` (No Content), or `status-code-expression="T(org.springframework.http.HttpStatus).NO_CONTENT"`. By default, `status-code-expression` is null, meaning that the normal '200 OK' response status is returned. +Using the `RequestEntity` as root object, the status code can be conditional e.g. on the request method, some header, URI content or even request body. The following example shows how to set the status code to `ACCEPTED`: ==== diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index 452af27a9e..0397d06792 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -165,3 +165,10 @@ When a `JmsMessageDrivenEndpoint` or `JmsInboundGateway` is stopped, the associa You can configure the endpoints to revert to the previous behavior. See <> for more information. + +[[x51.-http]] +=== HTTP/WebFlux Support + +The `statusCodeExpression` (and `Function`) is now supplied with the `RequestEntity` as a root object for evaluation context, so request headers, method, URI and body are available for target status code calculation. + +See <> and <> for more information.