SpringCloud Gateway 网关的请求体body的读取和修改

本文介绍了在SpringCloud Gateway中如何处理请求体body,由于body只能读取一次的限制,提出了解决方案——创建缓存请求体过滤器CacheBodyGlobalFilter。该过滤器在过滤器链中优先执行,缓存不同类型的请求体,确保后续过滤器可以多次读取。此外,文章还展示了如何在过滤器中对请求体进行修改,如json封装和签名处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 需求背景

        Gateway 作为微服务集群的入口,除了进行一些权限验证、header封装以外,可能也需要对请求体body进行封装。

        比如随着业务子系统的扩展,各子系统的请求体body格式各不一致,例如:子系统A的请求体入参要求是Json格式体既可:{"name":"aaa"},但是子系统B请求体入参要求是Json格式体,但是因为历史原因,虽然也是要求Json格式,但是在最外层进行一层封装,格式为:{body: 实际的json},这个封装的操作就可以在Gateway 的过滤器中进行封装。

        再比如:我们在网关里面需要读取到原始请求体的入参,其中包括json格式和文件上传类型的入参,获取到这里入参后需要进行一些签名处理后,保存在header中。这种情况下,我们就需要针对不同的请求类型的请求体进行缓存。而不能全部当作json字符串请求体进行缓存。

2. 具体方法

2.1 request body 只能读取一次问题

        在Gateway中通常会有一个过滤器链,而 request body 只能读取一次,也就是说,如果在过滤器A中已经读取一次,在后面的过滤器B是无法读取成功的,会抛出如下的报错:

java.lang.IllegalStateException: Only one connection receive subscriber allowed.
	at reactor.ipc.netty.channel.FluxReceive.startReceiver(FluxReceive.java:279)
	at reactor.ipc.netty.channel.FluxReceive.lambda$subscribe$2(FluxReceive.java:129)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute$$$capture(AbstractEventExecutor.java:163)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java)
	at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:446)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
	at java.lang.Thread.run(Thread.java:745)

大意就是netty的request body只能读取一次,第二次读取就报这个错误了。

问题原因

        翻查GitHub终于找到,spring boot在2.0.5版本如果使用了WebFlux就自动配置HiddenHttpMethodFilter过滤器。
查看源码发现,这个过滤器的作用是,针对当前的浏览器一般只支持GETPOST表单提交方法,如果想使用其他HTTP方法(如:PUT、DELETE、PATCH),就只能通过一个隐藏的属性如(_method=PUT)来表示,那么HiddenHttpMethodFilter的作用是将POST请求的_method参数里面的value替换掉http请求的方法。
        想法是很好的,用一种折中的方法来支持使浏览器支持restful方法。

        如果只是使用spring boot,一切都是没有问题的,因为使用的过程中,不需要我们自己解析request body,到controller这一层,这一切就已经完成的了。

        但是spring cloud gateway需要,因为它的做法就是拿到原始请求信息(包括request body),再重新封装一个request路由到下游微服务,所以上面的问题就在于:

  1. HiddenHttpMethodFilter读取了一次request body;

  2. gateway的封装自己的request时,去读取request body,就报错了。

所以这个是spring cloud gateway和spring boot开发者没协商好,都去读取request body的问题。

问题解决方案

  1. HiddenHttpMethodFilter是spring boot在2.0.5版本自动引入的,将版本降到2.0.4即可
  2. 在不降版本的前提下,增加一个缓存请求体过滤器 CacheBodyGlobalFilter ,将其执行优先级设置最大(order值最小),使其在过滤器链中最先执行。

 2.2 缓存请求体过滤器

实际工作中,post请求通常是分为两种,一种是json请求类型(ContentType=application/json),还有一种是上传文件类型的form表单(ContentType=multipart/form-data),可以根据请求类型的不同,分别缓存请求体body,所以这里先新建一个GatewayContext类对数据进行缓存

GatewayContext.java

package com.test.filter;

import lombok.Data;
import org.springframework.http.codec.multipart.Part;
import org.springframework.util.MultiValueMap;

@Data
public class GatewayContext {

    public static final String CACHE_GATEWAY_CONTEXT = "cacheGatewayContext";

    /**
     * cache json body
     */
    private String jsonBody;

    /**
     *--multipart/form表单参数
     */
    private MultiValueMap<String, Part> multiPartParams;
}

全局过滤器 CacheBodyGlobalFilter.java

package com.test.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.buffer.*;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.h
Spring Cloud Gateway 是一个基于 Spring Framework 的轻量级、高性能的 API 网关,它允许开发者路由、过滤、增强拦截网络流量,包括 HTTP 请求的 body 部分。 在 Spring Cloud Gateway 中,你可以通过 `WebFlux` 的响应拦截器机制来截获并处理请求的 body。具体步骤如下: 1. **创建拦截器**:首先,你需要自定义一个 `GlobalFilter` 或 `ExchangeFilterFunction` 类,这是 Gateway 提供的用于添加全局过滤功能的接口。 ```java import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterFunction; import org.springframework.stereotype.Component; @Component public class BodyLoggingFilter implements GatewayFilterFunction { @Override public GatewayFilter apply(String id) { return new LoggingBodyGatewayFilter(id); } } ``` 这里 `LoggingBodyGatewayFilter` 是一个具体的拦截器实现,它会记录请求的 body 内容。 2. **配置启用**:在 Gateway 的配置类(如 `application.yml` 或 `application.properties`)中注册这个过滤器,并指定需要应用的路径前缀。 ```yaml spring: cloud: gateway: filters: - Predicates: path=/api/** filter-factory: com.example.BodyLoggingFilter ``` 这表示对所有路径以 `/api` 开头的请求都会应用你的拦截器。 3. **拦截逻辑**:在 `LoggingBodyGatewayFilter` 中,你可以获取到 `ServerWebExchange` 对象,从中读取请求体的内容,然后进行处理、日志记录等操作。 ```java @Component public class LoggingBodyGatewayFilter extends ServerWebExchangeConverter { private final String id; public LoggingBodyGatewayFilter(String id) { this.id = id; } @Override protected Mono<Void> doFilter(ServerWebExchange exchange, WebFilterChain chain) { return chain.filter(exchange).flatMap(response -> { // 这里可以访问 request body Object requestBody = exchange.getAttributes().get(BodyExtractor.REQUEST_BODY_ATTRIBUTE); log.info("Received request with ID {} and body: {}", id, requestBody); return response; }); } } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值