Spring Cloud Gateway(Greenwich)如何在过滤器中读取请求体request body。
Spring Cloud Gateway(Greenwich)如何在过滤器中读取请求体request body。
因为需要在网关层做一些验证操作,需要访问请求体的参数。我使用的Spring Cloud版本是Greenwich,在网上找了很多资料之后,经过验证找到了简单的解决方案。
Gateway内置过滤器工厂ReadBodyPredicateFactory
ReadBodyPredicateFactory对请求体参数做了缓存,如下代码:
protected static final Log LOGGER = LogFactory.getLog(ReadBodyPredicateFactory.class);
private static final String TEST_ATTRIBUTE = "read_body_predicate_test_attribute";
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();
public ReadBodyPredicateFactory() {
super(Config.class);
}
@Override
@SuppressWarnings("unchecked")
public AsyncPredicate<ServerWebExchange> applyAsync(Config config) {
return exchange -> {
Class inClass = config.getInClass();
Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
Mono<?> modifiedBody;
if (cachedBody != null) {
try {
boolean test = config.predicate.test(cachedBody);
exchange.getAttributes().put(TEST_ATTRIBUTE, test);
return Mono.just(test);
} catch (ClassCastException e) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Predicate test failed because class in predicate does not match the cached body object",
e);
}
}
return Mono.just(false);
} else {
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
DataBufferUtils.retain(dataBuffer);
Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
return ServerRequest.create(exchange.mutate().request(mutatedRequest).build(), messageReaders)
.bodyToMono(inClass)
.doOnNext(objectValue -> {
exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue);
exchange.getAttributes().put(CACHED_REQUEST_BODY_KEY, cachedFlux);
})
.map(objectValue -> config.predicate.test(objectValue));
});
}
};
}
如上所示,ReadBodyPredicateFactory将请求体类容缓存到了exchange中,所以只需要在后续过滤器(我是用的全局过滤器,全局过滤器执行顺序在所有过滤器之后)中,使用
Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
即可获取request body的参数。在此之前还需要配置一下路由器,如下。
路由器配置
这里没有找到使用yml配置ReadBodyPredicateFactory的方法,所以使用@Bean的方式配置,代码如下:
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
/*
route1 是post请求,Content-Type是application/x-www-form-urlencoded,readbody为String.class
route2 是post请求,Content-Type是application/json,readbody为Object.class
*/
RouteLocatorBuilder.Builder routes = builder.routes();
RouteLocatorBuilder.Builder serviceProvider = routes
.route("PUSH-SERVER",
r -> r
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.and()
.method(HttpMethod.POST)
.and()
.readBody(String.class, readBody -> {
return true;
})
.and()
.path("/gateway/push-server/**")
.filters(f -> {
f.stripPrefix(2);
return f;
})
.uri("lb://PUSH-SERVER"))
.route("PUSH-SERVER",
r -> r
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.and()
.method(HttpMethod.POST)
.and()
.readBody(String.class, readBody -> {
return true;
})
.and()
.path("/gateway/push-server/**")
.filters(f -> {
f.stripPrefix(2);
return f;
})
.uri("lb://PUSH-SERVER"));
RouteLocator routeLocator = serviceProvider.build();
return routeLocator;
}
其中.readBody()方法就是配置上述过滤器工厂,可以在其 {} 做一些日志输入操作。