spring cloud gateway 复用 request body(二)
实现方案:
- 每个路由单独配置
- 全局配置
单独配置
使用 spring cloud gateway 默认提供 的 ReadBodyRoutePredicateFactory
// ReadBodyRoutePredicateFactory.java
public AsyncPredicate<ServerWebExchange> applyAsync(Config config) {
return new AsyncPredicate<ServerWebExchange>() {
@Override
public Publisher<Boolean> apply(ServerWebExchange exchange) {
Class inClass = config.getInClass();
Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
Mono<?> modifiedBody;
// We can only read the body from the request once, once that happens if
// we try to read the body again an exception will be thrown. The below
// if/else caches the body object as a request attribute in the
// ServerWebExchange so if this filter is run more than once (due to more
// than one route using it) we do not try to read the request body
// multiple times
if (cachedBody != null) {
try {
boolean test = config.predicate.test(cachedBody);
exchange.getAttributes().put(TEST_ATTRIBUTE, test);
return Mono.just(test);
}
catch (ClassCastException e) {
if (log.isDebugEnabled()) {
log.debug("Predicate test failed because class in predicate "
+ "does not match the cached body object", e);
}
}
return Mono.just(false);
}
else {
return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange,
(serverHttpRequest) -> ServerRequest
.create(exchange.mutate().request(serverHttpRequest).build(), messageReaders)
.bodyToMono(inClass).doOnNext(objectValue -> exchange.getAttributes()
.put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue))
.map(objectValue -> config.getPredicate().test(objectValue)));
}
}
@Override
public Object getConfig() {
return config;
}
@Override
public String toString() {
return String.format("ReadBody: %s", config.getInClass());
}
};
}
原理就是,将 body数据取出转成 string, 然后放在 exchange 的 CACHE_REQUEST_BODY_OBJECT_KEY
属性中,要使用时,取出这个属性值 就行
配置:
1.application.yml配置:
spring:
application:
name: nacos-gateway
cloud:
gateway:
routes:
- id: nacos-user_3
uri: http://localhost:5558
predicates:
- Path=/r/**
- name: ReadBody #使用ReadBodyPredicateFactory断言,将body读入缓存
args:
inClass: 'java.lang.String'
predicate: '#{@bodyPredicate}' #注入实现predicate接口类
filters:
- StripPrefix=1
断言:
// el 表达式取
@Bean
public Predicate bodyPredicate() {
return o -> true;
}
过滤器:
@Component
@Slf4j
public class ReadBodyFilter implements GlobalFilter {
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String contentType = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasText(contentType) && contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) {
log.info("POST form-data request ignore, query params: {}", exchange.getRequest().getQueryParams());
return chain.filter(exchange);
}
ServerHttpRequest request = exchange.getRequest();
HttpMethod method = request.getMethod();
if (method.matches("GET")) {
log.info("GET request param: {}", request.getQueryParams());
} else if (method.matches("POST")) {
Object requestBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
log.info("POST request param: {}-body: {}", request.getQueryParams(), requestBody);
}
return chain.filter(exchange);
}
}
全局过滤器
将 ReadBodyRoutePredicateFactory
改造一下:
@Slf4j
public class GlobalCacheBodyStringRequestFilter implements GlobalFilter, Ordered {
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
private List<HttpMessageReader<?>> messageReaders;
public GlobalCacheBodyStringRequestFilter(List<HttpMessageReader<?>> readers) {
this.messageReaders = readers;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String contentType = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasText(contentType) && contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) {
return chain.filter(exchange);
}
log.info("cache body...");
return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange,
(serverHttpRequest) -> ServerRequest.create(exchange.mutate().request(serverHttpRequest).build(), messageReaders)
.bodyToMono(String.class).doOnNext(objectValue -> exchange.getAttributes()
.put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue)
).then(Mono.defer(() -> chain.filter(exchange)))
);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}
@Bean
public GlobalCacheBodyStringRequestFilter globalCacheBodyStringRequestFilter(ServerCodecConfigurer configurer) {
return new GlobalCacheBodyStringRequestFilter(configurer.getReaders());
}
模拟重复取数据:
GlobalTraceFilter 日志拦截:
@Component
@Slf4j
public class GlobalTraceFilter implements GlobalFilter, Ordered {
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
@SneakyThrows
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String contentType = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasText(contentType) && contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) {
log.info("POST form-data request ignore, query params: {}", exchange.getRequest().getQueryParams());
return chain.filter(exchange);
}
ServerHttpRequest request = exchange.getRequest();
HttpMethod method = request.getMethod();
if (method.matches("GET")) {
log.info("GET request param: {}", request.getQueryParams());
} else if (method.matches("POST")) {
Object bodyStr = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
log.info("POST request param str: {}-body: {}", request.getQueryParams(), bodyStr);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 2;
}
}
GlobalAuthRequestFilter 认证拦截
@Component
@Slf4j
public class GlobalAuthRequestFilter implements GlobalFilter {
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
@SneakyThrows
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String contentType = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasText(contentType) && contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) {
return chain.filter(exchange);
}
ServerHttpRequest request = exchange.getRequest();
HttpMethod method = request.getMethod();
if (method.matches("GET")) {
log.info("Auth GET request param: {}", request.getQueryParams());
} else if (method.matches("POST")) {
Object bodyStr = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
log.info("POST request param str: {}-body: {}", request.getQueryParams(), bodyStr);
}
return chain.filter(exchange);
}
}
结果:
c.e.nacosgatew.config.GlobalTraceFilter : POST request param str: {a1=[aa], b1=[cc]}-body: { "a": "hello"}
c.e.n.config.GlobalAuthRequestFilter : POST request param str: {a1=[aa], b1=[cc]}-body: { "a": "hello"}
good luck!