我们使用SpringCloud Gateway做微服务网关的时候,经常需要在过滤器Filter中读取到Post请求中的Body内容进行日志记录、签名验证、权限验证等操作。我们知道,Request的Body是只能读取一次的,如果直接通过在Filter中读取,而不封装回去回导致后面的服务无法读取数据。
SpringCloud Gateway 内部提供了一个断言工厂类ReadBodyPredicateFactory,这个类实现了读取Request的Body内容并放入缓存,我们可以通过从缓存中获取body内容来实现我们的目的。
1、分析ReadBodyPredicateFactory
public AsyncPredicate<ServerWebExchange> applyAsync(ReadBodyPredicateFactory.Config config) {
return (exchange) -> {
Class inClass = config.getInClass();
Object cachedBody = exchange.getAttribute("cachedRequestBodyObject");
if (cachedBody != null) {
try {
boolean test = config.predicate.test(cachedBody);
exchange.getAttributes().put("read_body_predicate_test_attribute", test);
return Mono.just(test);
} catch (ClassCastException var7) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Predicate test failed because class in predicate does not match the cached body object", var7);
}
return Mono.just(false);
}
} else {
return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap((dataBuffer) -> {
DataBufferUtils.retain(dataBuffer);
final Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
return Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount()));
});
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
return ServerRequest.create(exchange.mutate().request(mutatedRequest).build(), messageReaders).bodyToMono(inClass).doOnNext((objectValue) -> {
exchange.getAttributes().put("cachedRequestBodyObject", objectValue);
exchange.getAttributes().put("cachedRequestBody", cachedFlux);
}).map((objectValue) -> {
return config.predicate.test(objectValue);
});
});
}
};
}
通过查看ReadBodyPredicateFactory内部实现,我们可以看到,该工厂类将request body内容读取后存放在 exchange的cachedRequestBodyObject中。那么我们可以通过代码:exchange.getAttribute("cachedRequestBodyObject"); //将body内容取出来。
知道如何取body内容后,我们只需将该工厂类注册到yml配置文件中的predicates,然后从Filter中获取即可。
2、配置ReadBodyPredicateFactory
查看ReadBodyPredicateFactory关于配置的代码:
public <T> ReadBodyPredicateFactory.Config setPredicate(Class<T> inClass, Predicate<T> predicate) {
this.setInClass(inClass);
this.predicate = predicate;
return this;
}
配置该工厂类需要两个参数:
inClass:接收body内容的对象Class,我们用字符串接收,配置String即可。
Predicate:Predicate的接口实现类,我们自定义一个Predicate的实现类即可。
自定义Predicate实现,并注册Bean。
/**
* 用于readBody断言,可配置到yml
* @return
*/
@Bean
public Predicate bodyPredicate(){
return new Predicate() {
@Override
public boolean test(Object o) {
return true;
}
};
}
两个参数都有了,直接在yml中配置:
predicates:
- Path=/card/api/**
- name: ReadBodyPredicateFactory #使用ReadBodyPredicateFactory断言,将body读入缓存
args:
inClass: '#{T(String)}'
predicate: '#{@bodyPredicate}' #注入实现predicate接口类
3、编写自定义GatewayFilterFactory
编写自己的过滤器工厂类,读取缓存的body内容,并支持在配置文件中配置。
public class ReadBodyGatewayFilterFactory
extends AbstractGatewayFilterFactory<ReadBodyGatewayFilterFactory.Config> {
private Logger logger = LoggerFactory.getLogger(ReadBodyGatewayFilterFactory.class);
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
public ReadBodyGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return ((exchange, chain) -> {
//利用ReadBodyPredicateFactory断言,会将body读入exchange的cachedRequestBodyObject中
Object requestBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
logger.info("request body is:{}", requestBody);
return chain.filter(exchange);
});
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("withParams");//将参数放入
}
public static class Config {
private boolean withParams;//接收配置的参数值,可以随便写
public boolean isWithParams() {
return withParams;
}
public void setWithParams(boolean withParams) {
this.withParams = withParams;
}
}
}
将ReadBodyGatewayFilterFactory工程类在容器中注入。
/**
* 注入ReadBody过滤器
* @return
*/
@Bean
public ReadBodyGatewayFilterFactory readBodyGatewayFilterFactory() {
return new ReadBodyGatewayFilterFactory();
}
到此,我们的Filter类也可以在yml配置文件中直接配置使用了。
4、完整的yml配置
- id: body_route #读取post中的body路由
order: 5
uri: lb://API-CARD
filters:
- ReadBody=true #使用自定义的过滤器工厂类,读取request body内容
predicates:
- Path=/card/api/**
- name: ReadBodyPredicateFactory #使用ReadBodyPredicateFactory断言,将body读入缓存
args:
inClass: '#{T(String)}'
predicate: '#{@bodyPredicate}' #注入实现predicate接口类
OK,以上是通过ReadBodyPredicateFactory这个类读取到request body内容。
另外springcloud gateway内部还提供了ModifyRequestBodyGatewayFilterFactory类用于修改body内容,既然能修改,自然也能获取body,大家可自行去研究。