Move writing response to a filter that executes at the end of the "post" (then) filters
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user