Spring Cloud Gateway 获取request body(基于源码改造,不走弯路)

在使用Spring Cloud Gateway的过程当中,常常须要获取request body,好比用来作日志记录、签名验证、加密解密等等。html

网上的资料,解决方案五花八门。因此就整理了通过验证且已经在线上使用的两种方法,都是基于官方源码进行扩展。java

本文使用的Spring Cloud Gateway版本为2.1.1.RELEASE。spring

ModifyRequestBodyGatewayFilterFactory

ModifyRequestBodyGatewayFilterFactory在官方文档中的介绍以下:json

This filter can be used to modify the request body before it is sent downstream by the Gateway.

也就是用来修改request body的,既然能修改,天然就能获取到。缓存

java配置

一个简单的配置以下:微信

@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的方法,有四个参数app

  • inClass:转换前request body 的类型。
  • outClass:转换后request body的类型。
  • newContentType:转换后的ContentType。
  • rewriteFunction:改写body的方法。这里就能够取到request body。

须要特别注意的是inClass和outClass的类型,若是设置得不对,会报错。ide

小结

优势post

  • 不只能够读取request body,还能够进行修改。

缺点ui

  • 若是只须要获取不须要修改,用这个filter就显得有点重了。
  • 若是多个filter里都须要读取,官方提供的并无支持缓存,没法一次读屡次取。(主要也不是用来干这个的)

ReadBodyPredicateFactory(推荐)

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屡次。

java配置

一个简单的配置以下:

@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:转换前request body 的类型,会把request body转为这个类型。
  • predicate:谓词判断,若是只是想用作记录,这里能够一直返回true,若是返回false,会致使路由匹配不上。

须要特别注意的是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 {
    }
}

小结

优势

  • 一次读屡次取。
  • 能够用作谓词判断,只接受指定类型的请求体。

扩展

官方文档中说这两种方式都没法经过配置文件进行处理,这样不是很灵活,很难知足实际的须要。

扩展起来其实也不是很难,只要把没法经过配置文件进行配置的内容屏蔽掉就能够,对于ModifyRequestBodyGatewayFilterFactoryRewriteFunction,对于ReadBodyPredicateFactoryPredicate

有两种可行的方案

  • 直接在代码中把逻辑写死,去掉这个配置属性。
  • 经过注入bean的方式,把原来基于接口的实现改成bean注入。相似于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版本的注释都已经没了,放心大胆的用吧。

参考

看到了这里必定是真爱了,关注微信公众号【憨憨的春天】第一时间获取更新。
qrcode_for_gh_7fff61e23381_344.jpg

相关文章
相关标签/搜索