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

在使用Spring Cloud Gateway的过程中,经常需要获取request body,比如用来做日志记录、签名验证、加密解密等等。

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

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

ModifyRequestBodyGatewayFilterFactory

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

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

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

需要特别注意的是inClass和outClass的类型,如果设置得不对,会报错。

小结

优点

  • 不仅可以读取request body,还可以进行修改。

缺点

  • 如果只需要获取不需要修改,用这个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版本的注释都已经没了,放心大胆的用吧。

参考

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring Cloud Gateway 可以通过自定义过滤器来获取请求体(body)。具体步骤如下: 1. 创建一个自定义过滤器类,实现 GatewayFilter 接口。 2. 在过滤器类中重写 filter 方法,在该方法中获取请求体。 3. 在 Spring Cloud Gateway 配置文件中配置该过滤器。 示例代码如下: ```java @Component public class MyFilter implements GatewayFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取请求体 ServerHttpRequest request = exchange.getRequest(); Flux<DataBuffer> body = request.getBody(); // 处理请求体 // ... // 调用下一个过滤器 return chain.filter(exchange); } } ``` 在 Spring Cloud Gateway 配置文件中配置该过滤器: ```yaml spring: cloud: gateway: routes: - id: my_route uri: http://localhost:8080 predicates: - Path=/my_path/** filters: - MyFilter ``` 其中,MyFilter 是自定义过滤器类的名称。在 filters 配置中指定该过滤器即可。 ### 回答2: Spring Cloud Gateway是一个基于Spring Boot的API网关,它允许开发者以统一的方式管理和路由HTTP请求到多个微服务。在实际开发中,有时需要获取HTTP请求的body,在Spring Cloud Gateway获取HTTP请求的body需要注意以下几点: 1. 所有的Route Predicate都需要配置读取HTTP请求体,否则在路由到下游服务时,请求体会丢失。 2. 如果请求体是将JSON字符串作为参数传递,则需要使用JSON库将字符串转成JSON对象Spring Cloud Gateway中推荐使用与Spring Framework组件集成的Jackson JSON库。 3. HTTP请求的body只能读取一次,所以需要配置路由过滤器来实现将读取过的请求体保存在请求上下文中,以便后续的路由过滤器和路由处理器获取请求体。 在Spring Cloud Gateway获取HTTP请求的body,可以通过自定义GatewayFilter来实现。下面给出获取HTTP请求体的代码示例: ```java public class BodyGatewayFilterFactory extends AbstractGatewayFilterFactory<BodyGatewayFilterFactory.Config> { public BodyGatewayFilterFactory() { super(Config.class); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { return DataBufferUtils.join(exchange.getRequest().getBody()) .flatMap(dataBuffer -> { byte[] bytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bytes); DataBufferUtils.release(dataBuffer); String requestBody = new String(bytes, Charset.forName("UTF-8")); exchange.getAttributes().put("requestBody", requestBody); return chain.filter(exchange); }); }; } public static class Config { } } ``` 在上面的代码中,使用DataBufferUtils.join()函数将请求体存储在字节数组中,并通过exchange的setAttribute()方法存储到请求上下文中。这样,在后续的路由过滤器和路由处理器中就可以通过读取exchange.getAttributes().get("requestBody")来获取HTTP请求的body,而无需重新读取请求体。 ### 回答3: Spring Cloud Gateway是一个基于Spring Boot的网关。它可以在微服务架构中起到路由、负载均衡、API管理等多种作用。 在Spring Cloud Gateway中,获取请求体有两种方式:获取单个请求体和获取多个请求体。 获取单个请求体: 在Spring Cloud Gateway中,获取单个请求体可以使用Exchange对象的getBody()方法。这个方法会返回一个Mono对象,需要使用subscribe()方法来订阅结果。 例如: ```java public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { Mono<String> requestBody = exchange.getRequest().getBodyToMono(String.class); requestBody.subscribe(content -> { // 对请求体进行操作 }); return chain.filter(exchange); } ``` 上面代码中,我们使用getBodyToMono()获取请求体,然后使用subscribe()方法来订阅请求体的内容。订阅成功后,我们可以对请求体进行操作。 获取多个请求体: 在Spring Cloud Gateway中,获取多个请求体可以使用GlobalFilter。GlobalFilter是一种全局过滤器,可以对所有的请求进行处理。 我们可以创建一个自定义的GlobalFilter,然后在filter()方法中获取请求体。 例如: ```java @Component public class MyGlobalFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { MediaType mediaType = exchange.getRequest().getHeaders().getContentType(); Flux<DataBuffer> body = exchange.getRequest().getBody(); return chain.filter(exchange.mutate().request( exchange.getRequest().mutate().body(Flux.just(body)).build()) .build()); } } ``` 上面代码中,我们创建了一个MyGlobalFilter类,并实现了GlobalFilter接口。在filter()方法中,我们使用getBody()获取请求体。获取请求体后,我们更改了请求体的数据,然后使用build()方法创建了一个新的Exchange对象,并返回chain.filter()。 总结: Spring Cloud Gateway可以通过Exchange对象获取请求体。可以使用getBody()方法获取单个请求体,也可以使用GlobalFilter获取多个请求体。 注意:在Spring Cloud Gateway中,请求体是一个Flux对象。如果需要将请求体转换成其他类型,请使用getBodyToMono()方法。由于Flux对象可能包含多个元素,因此在订阅操作时需要注意。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值