Move writing response to a filter that executes at the end of the "post" (then) filters

This commit is contained in:
Spencer Gibb
2017-01-16 13:06:58 -07:00
parent a431b7ed9a
commit 4d95e37125
6 changed files with 89 additions and 35 deletions

View File

@@ -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<GatewayFilter> filters,
Map<String, RouteFilter> filterDefinitions) {
return new GatewayFilteringWebHandler(gatewayWebHandler, filters, filterDefinitions);
public GatewayFilteringWebHandler gatewayFilteringWebHandler(GatewayWebHandler webHandler,
List<GatewayFilter> gatewayFilters,
Map<String, RouteFilter> routeFilters) {
return new GatewayFilteringWebHandler(webHandler, gatewayFilters, routeFilters);
}
// Predicate beans
@Bean
public GatewayPredicateHandlerMapping gatewayPredicateHandlerMapping(GatewayFilteringWebHandler webHandler,
Map<String, RoutePredicate> 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();

View File

@@ -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<Void> 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<DataBuffer> body = clientResponse.body((inputMessage, context) -> inputMessage.getBody());
return response.writeWith(body);
});
}
}

View File

@@ -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<Void> setStatus(ServerWebExchange exchange, HttpStatus status) {
exchange.getResponse().setStatusCode(status);
return Mono.empty();
chain.filter(exchange).then(() -> {
exchange.getResponse().setStatusCode(finalStatus);
return Mono.empty();
});
}
}

View File

@@ -55,13 +55,13 @@ public class GatewayFilteringWebHandler extends WebHandlerDecorator {
protected final Log logger = LogFactory.getLog(getClass());
private final List<GatewayFilter> filters;
private final Map<String, RouteFilter> filterDefinitions = new HashMap<>();
private final Map<String, RouteFilter> routeFilters = new HashMap<>();
public GatewayFilteringWebHandler(WebHandler targetHandler, List<GatewayFilter> filters,
Map<String, RouteFilter> filterDefinitions) {
Map<String, RouteFilter> 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> 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<WebFilter> loadFilters(Route route) {
private List<WebFilter> loadRouteFilters(Route route) {
List<WebFilter> 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());
}

View File

@@ -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<DataBuffer> body = clientResponse.body((inputMessage, context) -> inputMessage.getBody());
return response.writeWith(body);
return Mono.<Void>empty();
}).next(); // TODO: is this correct?
}
}

View File

@@ -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);
}