在使用Spring Cloud Gateway的过程中,经常需要获取request body,比如用来做日志记录、签名验证、加密解密等等。
网上的资料,解决方案五花八门。所以就整理了经过验证且已经在线上使用的两种方法,都是基于官方源码进行扩展。
本文使用的Spring Cloud Gateway版本为2.1.1.RELEASE。
ModifyRequestBodyGatewayFilterFactory
在官方文档中的介绍如下:
This filter can be used to modify the request body before it is sent downstream by the Gateway.
也就是用来修改request body的,既然能修改,自然就能获取到。
一个简单的配置如下:
@Bean public RouteLocator routes(RouteLocatorBuilder builder) { return builder.routes() .route("rewrite_request_body", r -> r.path("/post_json") .filters(f -> f.modifyRequestBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE, (exchange, s) -> Mono.just(s))) .uri("lb://waiter")) .build(); }
注意 modifyRequestBody
的方法,有四个参数
需要特别注意的是inClass和outClass的类型,如果设置得不对,会报错。
ReadBodyPredicateFactory是用来读取并判断request body是否匹配的谓词。只是在官方文档中都没有说明,但不影响使用。
在代码里有如下注释:
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
大致意思是我们只能从request中读取request body一次,如果读取过了,再次读取就会抛错。下面的代码把request body当作了request attribute缓存在 ServerWebExchange
中,如果这个filter运行了多次,也无需读取request body多次。
一个简单的配置如下:
@Autowired private LogRequestBodyGatewayFilterFactory logRequestBodyGatewayFilterFactory; @Bean public RouteLocator routes(RouteLocatorBuilder builder) { return builder.routes() .route("rewrite_json", r -> r.path("/post_json") .and() .readBody(String.class, requestBody -> true) .filters(f -> f.filter(logRequestBodyGatewayFilterFactory.apply(new LogRequestBodyGatewayFilterFactory.Config()))) .uri("lb://waiter")) .build(); }
注意 readBody
的方法,有两个参数
需要特别注意的是inClass的类型,如果设置得不对,会报错。
LogRequestBodyGatewayFilterFactory
是自定义的一个 GatewayFilterFactory
,由于 ReadBodyPredicateFactory
会缓存request body到 ServerWebExchange
,需要用的地方只需要从 ServerWebExchange
中获取即可。
@Slf4j @Component public class LogRequestBodyGatewayFilterFactory extends AbstractGatewayFilterFactory<LogRequestBodyGatewayFilterFactory.Config> { private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject"; public LogRequestBodyGatewayFilterFactory() { super(Config.class); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { String requestBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY); log.info(requestBody); return chain.filter(exchange); }; } public static class Config { } }
官方文档中说这两种方式都无法通过配置文件进行处理,这样不是很灵活,很难满足实际的需要。
扩展起来其实也不是很难,只要把无法通过配置文件进行配置的内容屏蔽掉就可以,对于 ModifyRequestBodyGatewayFilterFactory
是 RewriteFunction
,对于 ReadBodyPredicateFactory
是 Predicate
。
有两种可行的方案
RequestRateLimiterGatewayFilterFactory
中注入 keyResolver
。 上文中 ReadBodyPredicateFactory
部分写的java config和以下配置文件是等效的
spring.cloud.gateway.routes[0].id=rewrite_json spring.cloud.gateway.routes[0].predicates[0]=Path=/post_json spring.cloud.gateway.routes[0].predicates[1].name=ReadBodyPredicateFactory spring.cloud.gateway.routes[0].predicates[1].args.inClass=#{T(String)} spring.cloud.gateway.routes[0].predicates[1].args.predicate=#{@testPredicate} spring.cloud.gateway.routes[0].filters[0].name=LogRequestBody spring.cloud.gateway.routes[0].uri=lb://waiter
需要提供一个bean,也就是配置文件中的 testPredicate
@Component public class TestPredicate implements Predicate { @Override public boolean test(Object o) { return true; } }
主要利用了SpEL,注入类型和bean。
目前无论是 ModifyRequestBodyGatewayFilterFactory
还是 ReadBodyPredicateFactory
在2.1.1.RELEASE都还是BETA版本,但在2.2.0.RC1中,这两个类上关于BETA版本的注释都已经没了,放心大胆的用吧。
看到了这里一定是真爱了,关注微信公众号【憨憨的春天】第一时间获取更新。