diff --git a/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilter.java b/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilter.java index 8596fcfe..5f5161e1 100644 --- a/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilter.java @@ -31,10 +31,6 @@ import reactor.core.publisher.Mono; */ public interface GatewayFilter { - String GATEWAY_ROUTE_ATTR = "gatewayRoute"; - String GATEWAY_REQUEST_URL_ATTR = "gatewayRequestUrl"; - String GATEWAY_HANDLER_MAPPER_ATTR = "gatewayHandlerMapper"; - /** * Process the Web request and (optionally) delegate to the next * {@code WebFilter} through the given {@link WebFilterChain}. @@ -44,14 +40,4 @@ public interface GatewayFilter { */ Mono filter(ServerWebExchange exchange, WebFilterChain chain); - static T getAttribute(ServerWebExchange exchange, String attributeName, Class type) { - if (exchange.getAttributes().containsKey(attributeName)) { - Object attr = exchange.getAttributes().get(attributeName); - if (type.isAssignableFrom(attr.getClass())) { - return type.cast(attr); - } - throw new ClassCastException(attributeName + " is not of type " + type); - } - return null; - } } diff --git a/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java b/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java index a8c715ea..6a05df73 100644 --- a/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java @@ -5,12 +5,13 @@ import java.net.URI; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.gateway.config.Route; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.core.Ordered; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilterChain; import org.springframework.web.util.UriComponentsBuilder; -import static org.springframework.cloud.gateway.filter.GatewayFilter.getAttribute; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.getAttribute; import reactor.core.publisher.Mono; @@ -29,7 +30,7 @@ public class RouteToRequestUrlFilter implements GatewayFilter, Ordered { @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - Route route = getAttribute(exchange, GATEWAY_ROUTE_ATTR, Route.class); + Route route = getAttribute(exchange, ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, Route.class); if (route == null) { return chain.filter(exchange); } @@ -38,7 +39,7 @@ public class RouteToRequestUrlFilter implements GatewayFilter, Ordered { .uri(route.getUri()) .build(true) .toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl); + exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl); return chain.filter(exchange); } diff --git a/src/main/java/org/springframework/cloud/gateway/filter/WriteResponseFilter.java b/src/main/java/org/springframework/cloud/gateway/filter/WriteResponseFilter.java index 866cad07..c8236517 100644 --- a/src/main/java/org/springframework/cloud/gateway/filter/WriteResponseFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/filter/WriteResponseFilter.java @@ -9,8 +9,9 @@ import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilterChain; -import static org.springframework.cloud.gateway.filter.GatewayFilter.getAttribute; -import static org.springframework.cloud.gateway.handler.GatewayWebHandler.CLIENT_RESPONSE_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.RESPONSE_COMMITTED_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.getAttribute; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -21,6 +22,7 @@ import reactor.core.publisher.Mono; public class WriteResponseFilter implements GatewayFilter, Ordered { private static final Log log = LogFactory.getLog(WriteResponseFilter.class); + public static final int WRITE_RESPONSE_FILTER_ORDER = -1; @Override @@ -39,6 +41,13 @@ public class WriteResponseFilter implements GatewayFilter, Ordered { } log.trace("WriteResponseFilter start"); ServerHttpResponse response = exchange.getResponse(); + + //make other filters aware that the response has been committed + response.beforeCommit(() -> { + exchange.getAttributes().put(RESPONSE_COMMITTED_ATTR, true); + return Mono.empty(); + }); + Flux body = clientResponse.body((inputMessage, context) -> inputMessage.getBody()); return response.writeWith(body); }); diff --git a/src/main/java/org/springframework/cloud/gateway/filter/route/SetPathRouteFilter.java b/src/main/java/org/springframework/cloud/gateway/filter/route/SetPathRouteFilter.java index b4e5204c..8cf1040e 100644 --- a/src/main/java/org/springframework/cloud/gateway/filter/route/SetPathRouteFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/filter/route/SetPathRouteFilter.java @@ -7,7 +7,7 @@ import org.springframework.web.server.WebFilter; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.util.UriTemplate; -import static org.springframework.cloud.gateway.filter.GatewayFilter.getAttribute; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.getAttribute; import static org.springframework.cloud.gateway.handler.predicate.UrlRoutePredicate.URL_PREDICATE_VARS_ATTR; /** diff --git a/src/main/java/org/springframework/cloud/gateway/filter/route/SetStatusRouteFilter.java b/src/main/java/org/springframework/cloud/gateway/filter/route/SetStatusRouteFilter.java index 115386cf..4a66f129 100644 --- a/src/main/java/org/springframework/cloud/gateway/filter/route/SetStatusRouteFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/filter/route/SetStatusRouteFilter.java @@ -1,14 +1,19 @@ package org.springframework.cloud.gateway.filter.route; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.http.HttpStatus; import org.springframework.web.server.WebFilter; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.isResponseCommitted; + import reactor.core.publisher.Mono; /** * @author Spencer Gibb */ public class SetStatusRouteFilter implements RouteFilter { + protected final Log logger = LogFactory.getLog(getClass()); @Override public WebFilter apply(String statusString, String[] args) { @@ -25,10 +30,27 @@ public class SetStatusRouteFilter implements RouteFilter { final HttpStatus finalStatus = httpStatus; //TODO: caching can happen here - return (exchange, chain) -> - chain.filter(exchange).then(() -> { + return (exchange, chain) -> { + + // option 1 (runs in filter order) + /*exchange.getResponse().beforeCommit(() -> { exchange.getResponse().setStatusCode(finalStatus); return Mono.empty(); }); + return chain.filter(exchange);*/ + + // option 2 (runs in reverse filter order) + return chain.filter(exchange).then(() -> { + // check not really needed, since it is guarded in setStatusCode, but it's a good example + if (!isResponseCommitted(exchange)) { + boolean response = exchange.getResponse().setStatusCode(finalStatus); + if (!response && logger.isWarnEnabled()) { + logger.warn("Unable to set status code to "+ finalStatus + ". Response already committed."); + } + } + return Mono.empty(); + }); + }; } + } diff --git a/src/main/java/org/springframework/cloud/gateway/handler/GatewayFilteringWebHandler.java b/src/main/java/org/springframework/cloud/gateway/handler/GatewayFilteringWebHandler.java index be528e90..b452c606 100644 --- a/src/main/java/org/springframework/cloud/gateway/handler/GatewayFilteringWebHandler.java +++ b/src/main/java/org/springframework/cloud/gateway/handler/GatewayFilteringWebHandler.java @@ -40,7 +40,7 @@ import org.springframework.web.server.WebHandler; import org.springframework.web.server.handler.WebHandlerDecorator; import static java.util.Collections.emptyList; -import static org.springframework.cloud.gateway.filter.GatewayFilter.GATEWAY_ROUTE_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; import reactor.core.publisher.Mono; diff --git a/src/main/java/org/springframework/cloud/gateway/handler/GatewayPredicateHandlerMapping.java b/src/main/java/org/springframework/cloud/gateway/handler/GatewayPredicateHandlerMapping.java index 2803dd57..74aacb0d 100644 --- a/src/main/java/org/springframework/cloud/gateway/handler/GatewayPredicateHandlerMapping.java +++ b/src/main/java/org/springframework/cloud/gateway/handler/GatewayPredicateHandlerMapping.java @@ -18,8 +18,8 @@ import org.springframework.web.server.WebHandler; import reactor.core.publisher.Mono; -import static org.springframework.cloud.gateway.filter.GatewayFilter.GATEWAY_HANDLER_MAPPER_ATTR; -import static org.springframework.cloud.gateway.filter.GatewayFilter.GATEWAY_ROUTE_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_HANDLER_MAPPER_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; /** * @author Spencer Gibb diff --git a/src/main/java/org/springframework/cloud/gateway/handler/GatewayWebHandler.java b/src/main/java/org/springframework/cloud/gateway/handler/GatewayWebHandler.java index fedd1fbd..5da4cc83 100644 --- a/src/main/java/org/springframework/cloud/gateway/handler/GatewayWebHandler.java +++ b/src/main/java/org/springframework/cloud/gateway/handler/GatewayWebHandler.java @@ -10,7 +10,8 @@ import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebHandler; -import static org.springframework.cloud.gateway.filter.GatewayFilter.GATEWAY_REQUEST_URL_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; import reactor.core.publisher.Mono; @@ -19,8 +20,6 @@ import reactor.core.publisher.Mono; */ public class GatewayWebHandler implements WebHandler { - public static final String CLIENT_RESPONSE_ATTR = "webHandlerClientResponse"; - private final WebClient webClient; public GatewayWebHandler(WebClient webClient) { diff --git a/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java b/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java new file mode 100644 index 00000000..baa2ccd8 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java @@ -0,0 +1,31 @@ +package org.springframework.cloud.gateway.support; + +import org.springframework.web.server.ServerWebExchange; + +/** + * @author Spencer Gibb + */ +public class ServerWebExchangeUtils { + + public static final String CLIENT_RESPONSE_ATTR = "webHandlerClientResponse"; + public static final String GATEWAY_ROUTE_ATTR = "gatewayRoute"; + public static final String GATEWAY_REQUEST_URL_ATTR = "gatewayRequestUrl"; + public static final String GATEWAY_HANDLER_MAPPER_ATTR = "gatewayHandlerMapper"; + public static final String RESPONSE_COMMITTED_ATTR = "responseCommitted"; + + public static T getAttribute(ServerWebExchange exchange, String attributeName, Class type) { + if (exchange.getAttributes().containsKey(attributeName)) { + Object attr = exchange.getAttributes().get(attributeName); + if (type.isAssignableFrom(attr.getClass())) { + return type.cast(attr); + } + throw new ClassCastException(attributeName + " is not of type " + type); + } + return null; + } + + public static boolean isResponseCommitted(ServerWebExchange exchange) { + Boolean responseCommitted = getAttribute(exchange, RESPONSE_COMMITTED_ATTR, Boolean.class); + return responseCommitted != null && responseCommitted; + } +} diff --git a/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java b/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java index 6cdad4cf..6cdc04b1 100644 --- a/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java +++ b/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java @@ -29,6 +29,8 @@ import org.springframework.web.server.ServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_HANDLER_MAPPER_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; import static org.springframework.web.reactive.function.BodyExtractors.toMono; import static org.springframework.web.reactive.function.client.ClientRequest.GET; import static org.springframework.web.reactive.function.client.ClientRequest.POST; @@ -41,8 +43,8 @@ import reactor.test.StepVerifier; @SuppressWarnings("unchecked") public class GatewayIntegrationTests { - public static final String HANDLER_MAPPER_HEADER = "X-Gateway-Handler-Mapper-Class"; - public static final String ROUTE_ID_HEADER = "X-Gateway-Route-Id"; + private static final String HANDLER_MAPPER_HEADER = "X-Gateway-Handler-Mapper-Class"; + private static final String ROUTE_ID_HEADER = "X-Gateway-Route-Id"; @LocalServerPort private int port; @@ -361,9 +363,9 @@ public class GatewayIntegrationTests { public GatewayFilter modifyResponseFilter() { return (exchange, chain) -> { log.info("modifyResponseFilter start"); - String value = (String) exchange.getAttribute(GatewayFilter.GATEWAY_HANDLER_MAPPER_ATTR).orElse("N/A"); + String value = (String) exchange.getAttribute(GATEWAY_HANDLER_MAPPER_ATTR).orElse("N/A"); exchange.getResponse().getHeaders().add(HANDLER_MAPPER_HEADER, value); - Route route = (Route) exchange.getAttribute(GatewayFilter.GATEWAY_ROUTE_ATTR).orElse(null); + Route route = (Route) exchange.getAttribute(GATEWAY_ROUTE_ATTR).orElse(null); if (route != null) { exchange.getResponse().getHeaders().add(ROUTE_ID_HEADER, route.getId()); }