diff --git a/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index 9f74312d..25166b84 100644 --- a/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -11,6 +11,7 @@ import org.springframework.cloud.gateway.actuate.GatewayEndpoint; import org.springframework.cloud.gateway.api.RouteReader; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter; +import org.springframework.cloud.gateway.filter.WriteResponseFilter; import org.springframework.cloud.gateway.filter.route.AddRequestHeaderRouteFilter; import org.springframework.cloud.gateway.filter.route.AddResponseHeaderRouteFilter; import org.springframework.cloud.gateway.filter.route.RemoveRequestHeaderRouteFilter; @@ -27,8 +28,8 @@ import org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicate; import org.springframework.cloud.gateway.handler.predicate.HeaderRoutePredicate; import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicate; import org.springframework.cloud.gateway.handler.predicate.MethodRoutePredicate; -import org.springframework.cloud.gateway.handler.predicate.RoutePredicate; import org.springframework.cloud.gateway.handler.predicate.QueryRoutePredicate; +import org.springframework.cloud.gateway.handler.predicate.RoutePredicate; import org.springframework.cloud.gateway.handler.predicate.UrlRoutePredicate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -48,11 +49,6 @@ public class GatewayAutoConfiguration { return WebClient.builder(new ReactorClientHttpConnector()).build(); } - @Bean - public RouteToRequestUrlFilter findRouteFilter() { - return new RouteToRequestUrlFilter(); - } - @Bean @ConditionalOnMissingBean(RouteReader.class) public PropertiesRouteReader propertiesRouteReader(GatewayProperties properties) { @@ -65,19 +61,17 @@ public class GatewayAutoConfiguration { } @Bean - public GatewayWebHandler gatewayController(WebClient webClient) { + public GatewayWebHandler gatewayWebHandler(WebClient webClient) { return new GatewayWebHandler(webClient); } @Bean - public GatewayFilteringWebHandler gatewayFilteringWebHandler(GatewayWebHandler gatewayWebHandler, - List filters, - Map filterDefinitions) { - return new GatewayFilteringWebHandler(gatewayWebHandler, filters, filterDefinitions); + public GatewayFilteringWebHandler gatewayFilteringWebHandler(GatewayWebHandler webHandler, + List gatewayFilters, + Map routeFilters) { + return new GatewayFilteringWebHandler(webHandler, gatewayFilters, routeFilters); } - // Predicate beans - @Bean public GatewayPredicateHandlerMapping gatewayPredicateHandlerMapping(GatewayFilteringWebHandler webHandler, Map predicates, @@ -85,6 +79,20 @@ public class GatewayAutoConfiguration { return new GatewayPredicateHandlerMapping(webHandler, predicates, routeReader); } + // GatewayFilter beans + + @Bean + public RouteToRequestUrlFilter routeToRequestUrlFilter() { + return new RouteToRequestUrlFilter(); + } + + @Bean + public WriteResponseFilter writeResponseFilter() { + return new WriteResponseFilter(); + } + + // Predicate beans + @Bean(name = "CookieRoutePredicate") public CookieRoutePredicate cookieRoutePredicate() { return new CookieRoutePredicate(); diff --git a/src/main/java/org/springframework/cloud/gateway/filter/WriteResponseFilter.java b/src/main/java/org/springframework/cloud/gateway/filter/WriteResponseFilter.java new file mode 100644 index 00000000..866cad07 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/filter/WriteResponseFilter.java @@ -0,0 +1,47 @@ +package org.springframework.cloud.gateway.filter; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.Ordered; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.server.reactive.ServerHttpResponse; +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 reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * @author Spencer Gibb + */ +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 + public int getOrder() { + return WRITE_RESPONSE_FILTER_ORDER; + } + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + // NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_ATTR is not added + // until the WebHandler is run + return chain.filter(exchange).then(() -> { + ClientResponse clientResponse = getAttribute(exchange, CLIENT_RESPONSE_ATTR, ClientResponse.class); + if (clientResponse == null) { + return Mono.empty(); + } + log.trace("WriteResponseFilter start"); + ServerHttpResponse response = exchange.getResponse(); + Flux body = clientResponse.body((inputMessage, context) -> inputMessage.getBody()); + return response.writeWith(body); + }); + } + +} 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 a12e8441..115386cf 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,8 +1,8 @@ package org.springframework.cloud.gateway.filter.route; -import org.springframework.web.server.WebFilter; import org.springframework.http.HttpStatus; -import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; + import reactor.core.publisher.Mono; /** @@ -24,14 +24,11 @@ public class SetStatusRouteFilter implements RouteFilter { final HttpStatus finalStatus = httpStatus; - //TODO: caching can happen here return (exchange, chain) -> - chain.filter(exchange).then(() -> setStatus(exchange, finalStatus)); - } - - protected Mono setStatus(ServerWebExchange exchange, HttpStatus status) { - exchange.getResponse().setStatusCode(status); - return Mono.empty(); + chain.filter(exchange).then(() -> { + exchange.getResponse().setStatusCode(finalStatus); + 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 f2706b7f..be528e90 100644 --- a/src/main/java/org/springframework/cloud/gateway/handler/GatewayFilteringWebHandler.java +++ b/src/main/java/org/springframework/cloud/gateway/handler/GatewayFilteringWebHandler.java @@ -55,13 +55,13 @@ public class GatewayFilteringWebHandler extends WebHandlerDecorator { protected final Log logger = LogFactory.getLog(getClass()); private final List filters; - private final Map filterDefinitions = new HashMap<>(); + private final Map routeFilters = new HashMap<>(); public GatewayFilteringWebHandler(WebHandler targetHandler, List filters, - Map filterDefinitions) { + Map routeFilters) { super(targetHandler); this.filters = initList(filters); - filterDefinitions.forEach((name, def) -> this.filterDefinitions.put(nornamlizeName(name), def)); + routeFilters.forEach((name, def) -> this.routeFilters.put(nornamlizeName(name), def)); } private String nornamlizeName(String name) { @@ -86,7 +86,7 @@ public class GatewayFilteringWebHandler extends WebHandlerDecorator { Optional route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); if (route.isPresent() && !route.get().getFilters().isEmpty()) { - routeFilters.addAll(loadFilters(route.get())); + routeFilters.addAll(loadRouteFilters(route.get())); } AnnotationAwareOrderComparator.sort(routeFilters); @@ -106,10 +106,10 @@ public class GatewayFilteringWebHandler extends WebHandlerDecorator { }).collect(Collectors.toList()); } - private List loadFilters(Route route) { + private List loadRouteFilters(Route route) { List filters = route.getFilters().stream() .map(definition -> { - RouteFilter filter = this.filterDefinitions.get(definition.getName()); + RouteFilter filter = this.routeFilters.get(definition.getName()); if (filter == null) { throw new IllegalArgumentException("Unable to find RouteFilter with name " + definition.getName()); } 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 8449a929..fedd1fbd 100644 --- a/src/main/java/org/springframework/cloud/gateway/handler/GatewayWebHandler.java +++ b/src/main/java/org/springframework/cloud/gateway/handler/GatewayWebHandler.java @@ -3,7 +3,6 @@ package org.springframework.cloud.gateway.handler; import java.net.URI; import java.util.Optional; -import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.reactive.function.client.ClientRequest; @@ -13,7 +12,6 @@ import org.springframework.web.server.WebHandler; import static org.springframework.cloud.gateway.filter.GatewayFilter.GATEWAY_REQUEST_URL_ATTR; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** @@ -21,6 +19,8 @@ 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) { @@ -37,11 +37,16 @@ public class GatewayWebHandler implements WebHandler { .body((r, context) -> r.writeWith(request.getBody())); return this.webClient.exchange(clientRequest).flatMap(clientResponse -> { + // Defer committing the response until all route filters have run + // Put client response as ServerWebExchange attribute and write response later WriteResponseFilter + + exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, clientResponse); + ServerHttpResponse response = exchange.getResponse(); + // put headers and status so filters can modify the response response.getHeaders().putAll(clientResponse.headers().asHttpHeaders()); response.setStatusCode(clientResponse.statusCode()); - Flux body = clientResponse.body((inputMessage, context) -> inputMessage.getBody()); - return response.writeWith(body); + return Mono.empty(); }).next(); // TODO: is this correct? } } 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 676ff47f..6cdad4cf 100644 --- a/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java +++ b/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java @@ -7,7 +7,6 @@ import java.util.stream.IntStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.SpringBootConfiguration; @@ -300,13 +299,11 @@ public class GatewayIntegrationTests { } @Test - @Ignore("TODO: figure out how to set the status before response committed") public void setStatusIntWorks() { setStatusStringTest("www.setstatusint.org", HttpStatus.UNAUTHORIZED); } @Test - @Ignore("TODO: figure out how to set the status before response committed") public void setStatusStringWorks() { setStatusStringTest("www.setstatusstring.org", HttpStatus.BAD_REQUEST); }