SpringCloudGateway 读取Request请求参数

4,141 阅读4分钟

采用 ModifyRequestBodyGatewayFilterFactory读取对请求参数

自定义 getRewriteFunction 方法, 获取 body 信息

private RewriteFunction<Object, Object> getRewriteFunction() {
    return (serverWebExchange, body) -> {
        // 这里的body就是请求体参数
        try {
            requestBody.set(JSON.toJSONString(body));
        } catch (Exception e) {
            log.error("body 转换异常", e);
        }
        return Mono.just(body);
    };
}

定义ModifyRequestBodyGatewayFilterFactory配置

private ModifyRequestBodyGatewayFilterFactory.Config getConfig() {
    ModifyRequestBodyGatewayFilterFactory.Config cf = new ModifyRequestBodyGatewayFilterFactory.Config();
    cf.setRewriteFunction(Object.class, Object.class, getRewriteFunction());
    return cf;
}

初始化

@PostConstruct
public void init() {
    this.delegate = new ModifyRequestBodyGatewayFilterFactory().apply(this.getConfig());
}

读取响应消息内容

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

    ServerHttpRequest serverHttpRequest = exchange.getRequest();
    ServerHttpResponse originalResponse = exchange.getResponse();

    //如果是post请求,将请求体取出来,再写入
    HttpMethod method = serverHttpRequest.getMethod();

    DataBufferFactory bufferFactory = originalResponse.bufferFactory();
    ServerHttpResponseDecorator decorator = new ServerHttpResponseDecorator(originalResponse) {
        @Override
        public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
            Flux<? extends DataBuffer> fluxBody = Flux.from(body);
            return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                DataBuffer join = dataBufferFactory.join(dataBuffers);
                byte[] content = new byte[join.readableByteCount()];
                join.read(content);
                // 释放掉内存
                DataBufferUtils.release(join);
                String responseData = new String(content, StandardCharsets.UTF_8);

                log.info("响应内容: {}", responseData);

                byte[] uppedContent = new String(responseData.getBytes(), StandardCharsets.UTF_8).getBytes();
                return bufferFactory.wrap(uppedContent);
            }));
        }
    };

   
    // GET 请求直接读取
    if (HttpMethod.GET.equals(method)) {
        return chain.filter(exchange.mutate().response(decorator).build());
    }
    	
    return delegate.filter(exchange.mutate().response(decorator).build(), chain);
}

版本环境

Spring Boot 2.1.5.RELEASE

Spring Cloud Gateway 2.1.3.RELEASE

HttpServletRequest 的输入流只能读取一次的原因

我们先来看看为什么HttpServletRequest的输入流只能读一次,当我们调用getInputStream()方法获取输入流时得到的是一个InputStream对象,而实际类型是ServletInputStream,它继承于InputStream。

InputStreamread()方法内部有一个postion,标志当前流被读取到的位置,每读取一次,该标志就会移动一次,如果读到最后,read()会返回-1,表示已经读取完了。如果想要重新读取则需要调用reset()方法,position就会移动到上次调用mark的位置,mark默认是0,所以就能从头再读了。调用reset()方法的前提是已经重写了reset()方法,当然能否reset也是有条件的,它取决于markSupported()方法是否返回true。

InputStream默认不实现reset(),并且markSupported()默认也是返回 false

SpringCloudGateway 读取 Request 参数解决方案

增加一个全局过滤器,重写getBody方法,并把包装后的请求放到过滤器链中传递下去。这样后面的过滤器中再使用exchange.getRequest().getBody()来获取body时,实际上就是调用的重载后的getBody方法,获取的最先已经缓存了的body数据。这样就能够实现body的多次读取了。

这个过滤器的order设置的是Ordered.HIGHEST_PRECEDENCE,即最高优先级的过滤器。优先级设置这么高的原因是某些系统内置的过滤器可能也会去读body,这样就会导致我们自定义过滤器中获取body的时候报body只能读取一次这样的错误如下:

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)

CacheBodyGlobalFilter


import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
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;

/**
 * 解决 POST 请求 body 只能读取一次问题
 * 将原有的 body 数据读取出来
 * 使用 ServerHttpRequestDecorator 包装 Request
 * 重写 getBody() 方法,将包装后的请求放入过滤链传递下去
 * 此后过滤器通过 ServerWebExchange#getRequest()#getBody() 时,实际调用重写后的 getBody()
 * 获取到的 body 数据为缓存的数据
 * <p>
 * 过滤器优先级必须为 HIGHEST_PRECEDENCE 最高级,避免某些过滤器提前读取 body
 *
 * @author author
 * @date 2021-12-07
 * {@see <a href="https://www.codenong.com/j5e82a0f451882573a13/"/>}
 */
@Component
public class CacheBodyGlobalFilter implements Ordered, GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        if (exchange.getRequest().getHeaders().getContentType() == null) {
            return chain.filter(exchange);
        } else {
            return DataBufferUtils.join(exchange.getRequest().getBody())
                    .flatMap(dataBuffer -> {
                        DataBufferUtils.retain(dataBuffer);
                        Flux<DataBuffer> cachedFlux = Flux
                                .defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
                        ServerHttpRequest	 mutatedRequest = new ServerHttpRequestDecorator(
                                exchange.getRequest()) {
                            @Override
                            public Flux<DataBuffer> getBody() {
                                return cachedFlux;
                            }
                        };
                        return chain.filter(exchange.mutate().request(mutatedRequest).build());
                    });
        }
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

ReadBodyFilter.java


import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author author
 * @version 1.0.0
 * @Description 读取 Request 请求参数
 * @date 2021-10-12 14:34:00
 */
@Slf4j
@Component
public class ReadBodyFilter implements GlobalFilter, Ordered {

    @Override
    public int getOrder() {
        // order 排序需小于-1 才可读取到 response body 内容
        return -2;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return read(exchange, chain);
    }

    @SuppressWarnings({"unchecked", "NullableProblems"})
    private Mono<Void> read(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        ServerHttpResponse originalResponse = exchange.getResponse();

        //如果是post请求,将请求体取出来,再写入
        HttpMethod method = serverHttpRequest.getMethod();
        //请求参数,post从请求里获取请求体
        String requestBodyStr = HttpMethod.POST.equals(method) ? resolveBodyFromRequest(serverHttpRequest) : null;
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();

        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;

                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {//解决返回体分段传输
                        StringBuffer stringBuffer = new StringBuffer();
                        dataBuffers.forEach(dataBuffer -> {
                            byte[] content = new byte[dataBuffer.readableByteCount()];
                            dataBuffer.read(content);
                            DataBufferUtils.release(dataBuffer);
                            try {
                                stringBuffer.append(new String(content, StandardCharsets.UTF_8));
                            } catch (Exception e) {
                                log.error("--list.add--error", e);
                            }
                        });
                        String result = stringBuffer.toString();
                        // result就是response的值,想修改、查看就随意而为了
                        Map<String, String> urlParams = serverHttpRequest.getQueryParams().toSingleValueMap();

                        log.info("请求地址:【{}】请求参数:GET【{}】|POST:【\n{}\n】,响应数据:【\n{}\n】", serverHttpRequest.getURI(), urlParams, requestBodyStr, result);

                        byte[] uppedContent = new String(result.getBytes(), StandardCharsets.UTF_8).getBytes();
                        originalResponse.getHeaders().setContentLength(uppedContent.length);
                        return bufferFactory.wrap(uppedContent);
                    }));

                }
                // if body is not a flux. never got there.
                return super.writeWith(body);
            }
        };
        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }

    /**
     * 从Flux<DataBuffer>中获取字符串的方法
     *
     * @return 请求体
     */
    private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
        //获取请求体
        Flux<DataBuffer> body = serverHttpRequest.getBody();
        AtomicReference<String> bodyRef = new AtomicReference<>();
        body.subscribe(buffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            DataBufferUtils.release(buffer);
            bodyRef.set(charBuffer.toString());
        });
        //获取request body
        return bodyRef.get();
    }
}

参考: 原理分析 解决方案 ModifyRequestBodyGatewayFilterFactory