> ## Content Index
> Fetch the complete content index at: https://lucent.blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# SpringCloudGateway读取、修改请求体(modify SpringCloudGateway request body)
- URL: https://lucent.blog/springcloudgatewaydu-qu-xiu-gai-qing-qiu-ti-modifyspringcloudgatewayrequestbody/
- Published: 2022-01-13T06:50:49.000Z
- Updated: 2026-08-21T00:49:57.000Z
- Author: Lucent

在Spring Cloud Gateway中，读取Get请求的参数并非一件很难的事，但是读取Post请求的请求体(body)也并非一件简单事。

### 说明

Spring官方虽然提供了预言类：  
ReadBodyPredicateFactory (谓词工厂)、ModifyRequestBodyGatewayFilterFactory (过滤器工厂)，  
但仍然是 bate 版，并没有直接实现获取 request body 的 filter，所以如果想要读取request body，需要参考以上两个预言类，自行实现filter。

### 实现方式

首先，实现一个CacheRequestBodyFilter用来将request body读取出来并存到exchange的自定义属性中:

```java
package ltd.lemontech.contractor.gateway.filter;

import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import ltd.lemontech.contractor.gateway.constant.FilterConstant;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 *
 * 作用: 此过滤器主要作用是将请求body读取出来并存到exchange的自定义属性中，等待后续过滤器(ModifyRequestBodyFilter)处理
 * 使用: 使用本过滤器应该配合ModifyRequestBodyFilter一起使用，并且两个过滤器执行顺序必须CacheRequestBodyFilter在前
 *      ModifyRequestBodyFilter在后，否则后续业务将无法获得body中的参数
 * @author lucent
 */
@Slf4j
@Component
@Order(value = -100)
public class CacheRequestBodyFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 将 request body 中的内容复制一份，记录到 exchange 的一个自定义属性中
        Object cachedRequestBodyObject = exchange.getAttributeOrDefault(FilterConstant.CACHED_REQUEST_BODY_OBJECT_KEY, null);
        // 如果已经缓存过，略过
        if (ObjectUtil.isNotNull(cachedRequestBodyObject)) {
            return chain.filter(exchange);
        }
        // 如果没有缓存过，获取字节数组存入 exchange 的自定义属性中
        return DataBufferUtils.join(exchange.getRequest().getBody())
                .map(dataBuffer -> {
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);
                    return bytes;
                }).defaultIfEmpty(new byte[0])
                .doOnNext(bytes -> exchange.getAttributes().put(FilterConstant.CACHED_REQUEST_BODY_OBJECT_KEY, bytes))
                .then(chain.filter(exchange));
    }

}

```

然后，实现一个ModifyRequestBodyFilter，用来修改请求体，本例中是将原请求的body从exchange中取出来放到一个新请求中继续向下传递，因为每个请求的body只能被读取一次，上面的CacheRequestBodyFilter读取之后如果不用新请求替换旧请求，后面的业务将获取不到request body。  
代码:

```java
package ltd.lemontech.contractor.gateway.filter;

import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import ltd.lemontech.contractor.gateway.constant.FilterConstant;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * 自定义请求体过滤器
 * 主要作用：将被CacheRequestBodyFilter 读取过body的请求换成一个新的请求继续向下传递
 * 原因：每个请求body只能被读取一次，当body被将被CacheRequestBodyFilter读取后，后续业务将无法正常收到body，
 *      所以用一个新的请求继续,也因此，本过滤器执行顺序(order)必须在CacheRequestBodyFilter之后
 * 扩展：这里也可以对原body进行修改，但目前不需要修改，只需要将原body从exchange自定义属性中取出来放到新请求中即可
 * @author lucent
 */
@Slf4j
@Component
@Order(value = -90)
public class ModifyRequestBodyFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 尝试从 exchange 的自定义属性中取出缓存到的 body
        Object cachedRequestBodyObject = exchange.getAttributeOrDefault(FilterConstant.CACHED_REQUEST_BODY_OBJECT_KEY, null);
        if (ObjectUtil.isNotNull(cachedRequestBodyObject)) {
            byte[] body = (byte[]) cachedRequestBodyObject;
            DataBufferFactory dataBufferFactory = exchange.getResponse().bufferFactory();
            ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public Flux<DataBuffer> getBody() {
                    if (body.length > 0) {
                        return Flux.just(dataBufferFactory.wrap(body));
                    }
                    return Flux.empty();
                }
            };
            return chain.filter(exchange.mutate().request(decorator).build());
        }
        // 为空，说明已经读过，或者 request body 原本即为空，不做操作，传递到下一个过滤器链
        return chain.filter(exchange);
    }

}

```

**这两个过滤器执行顺序必须是CacheRequestBodyFilter在前ModifyRequestBodyFilter在后，否则后续业务将无法获得body中的参数**

### 应用

请求通过以上两个filter之后，后续的filter如果想要读取request body，就可以直接从exchange的自定义属性中获取了，这样并不会影响后续业务。

下面实现请求信息打印到日志的功能,LoggerFilter:

```java
package ltd.lemontech.contractor.gateway.filter;

import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;
import ltd.lemontech.contractor.gateway.constant.FilterConstant;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 请求日志打印过滤器
 * 作用: 打印指定模块的请求内容,其中请求body是从exchange自定义属性中取出来的，
 *      所以本过滤器的执行顺序（order）应该在CacheRequestBodyFilter和ModifyRequestBodyFilter之后
 * @author lucent
 */
@Slf4j
@Component
@Order(value = 0)
public class LoggerFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String now = DateUtil.now();
        StringBuffer text = new StringBuffer();
        text.append("================请求日志-" + now + "-开始================\n");
        text.append("请求ID: ").append(request.getId()).append("\n");
        text.append("请求方式: ").append(request.getMethodValue()).append("\n");
        text.append("请求Token: ").append(request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION)).append("\n");
        text.append("请求路径: ").append(request.getPath()).append("\n");
        text.append("路径参数: ").append(request.getQueryParams()).append("\n");

        Object cachedRequestBodyObject = exchange.getAttributes().get(FilterConstant.CACHED_REQUEST_BODY_OBJECT_KEY);
        if (cachedRequestBodyObject != null) {
            byte[] body = (byte[]) cachedRequestBodyObject;
            String string = new String(body);
            text.append("请求体: ").append("\n").append(string).append("\n");
        }
        text.append("================请求日志-").append(now).append("-结束================\n");
        System.out.println(text);
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();

            HttpStatus statusCode = response.getStatusCode();
            HttpHeaders responseHeaders = response.getHeaders();

            StringBuilder res = new StringBuilder();
            String time = DateUtil.now();
            res.append("================响应日志-").append(time).append("-开始================\n");
            res.append("请求ID: ").append(request.getId()).append("\n");
            if (statusCode != null) {
                res.append("响应状态: ").append(statusCode.value()).append("\n");
            }
            res.append("响应头: ").append(responseHeaders).append("\n");
            res.append("================响应日志-").append(time).append("-结束================\n");
            System.out.println(res);
        }));
    }

}

```

#### 效果

POST请求:  

![image.png](https://img-1251540275.cos.ap-shanghai.myqcloud.com/blog/image_1642058886716.png)

  
GET请求:  

![image.png](https://img-1251540275.cos.ap-shanghai.myqcloud.com/blog/image_1642058956108.png)

至此就实现了请求日志记录