使用GlobalFilter修改请求和返回数据

549 阅读3分钟

本次和大家分享在使用gateway作为网关时比如在日志采集、请求结构修改、返回数据结构修改时,使用filter进行请求数据和返回数据的修改。针对不同的请求类型 application/json 、application/x-www-form-urlencoded 、multipart/form-data。其中针对文件上传的请求修改时比较麻烦。解决了post请求请求体不能多次读取的问题。下面的例子为通过网关采集日志。本文章基于 springboot3.x说明,针对2.x的使用有区别请自行调整。

1. 过滤器使用

@Slf4j
@Configuration
@RequiredArgsConstructor
public class LogGlobalFilter implements GlobalFilter, Ordered {

    private final ServerCodecConfigurer codecConfigurer;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        ServerHttpResponseDecorator responseDecorator = new CustomServerHttpResponseDecorator(exchange, exchange.getResponse().bufferFactory(), logResFunction(exchange));
       ServerWebExchange serverWebExchange = exchange.mutate().response(responseDecorator).build();
        ServerHttpRequest request = exchange.getRequest();
        ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
        if (request.getMethod() == HttpMethod.POST || request.getMethod() == HttpMethod.PUT) {
            MediaType contentType = request.getHeaders().getContentType();
            //针对json请求和表单请求
            if (contentType.isCompatibleWith(MediaType.APPLICATION_JSON) || contentType.isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED)) {
                Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(logReqFunction(route, request));
                BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
                return FilterUtils.writeModifyMessage(serverWebExchange, chain, bodyInserter);
            } else if (contentType.isCompatibleWith(MediaType.MULTIPART_FORM_DATA)) {
                return FilterUtils.readFormMultiPartData(exchange, chain, readFormMultiPartData(exchange, route));
            }
        } else {
            MultiValueMap<String, String> queryParams = request.getQueryParams();
            String params = HttpUtil.toParams(queryParams);
            logReqFunction(route, request).apply(params);
        }
        return chain.filter(serverWebExchange);
    }

    @Override
    public int getOrder() {
        return -10;
    }



    /**
     * 解析文件表单
     *
     * @param exchange
     * @param route
     * @return
     */
    private Function<MultiValueMap<String, Part>, MultiValueMap<String, Part>> readFormMultiPartData(ServerWebExchange exchange, Route route) {
        return multiValueMap -> {
            Map<String, String> data = new HashMap<>();
            multiValueMap.forEach((key, value) -> {
                List<String> collect = value.stream()
                        .filter(val -> val instanceof FormFieldPart)
                        .map(val -> ((FormFieldPart) val).value())
                        .toList();
                if (CollUtil.isNotEmpty(collect)) {
                    data.put(key, collect.size() == 1 ? collect.get(0) : JSON.toJSONString(collect));
                }
            });
            logReqFunction(route, exchange.getRequest()).apply(JSON.toJSONString(data));
            return multiValueMap;
        };
    }


    /**
     * 处理返回数据
     *
     * @param exchange
     * @return
     */
    private Function<byte[], byte[]> logResFunction(ServerWebExchange exchange) {
        return responseData -> {
            ServerHttpRequest request = exchange.getRequest();
            Route route = exchange.getAttribute("org.springframework.cloud.gateway.support.ServerWebExchangeUtils.gatewayRoute");
            log.info("网关返回日志 转发服务:" + route.getUri() + "请求方式:" + request.getMethod() + " Content-Type: " + request.getHeaders().getContentType() + " 请求地址:" + request.getURI().getRawPath() + " 返回数据:" + responseData);
            return responseData;
        };

    }

    /**
     * 进行请求数据记录
     *
     * @param route
     * @param request
     * @return
     */
    private Function<String, ? extends Mono<String>> logReqFunction(Route route, ServerHttpRequest request) {
        return requestData -> {
            log.info("网关请求日志 转发服务: " + route.getUri() + "  请求方式: " + request.getMethod() + " Content-Type: " + request.getHeaders().getContentType() + " " + "  请求地址:" + request.getURI().getRawPath() + " 请求数据:" + requestData);
            return Mono.just(requestData);
        };

    }

2. 构建请求缓存和修改类

这块主要处理请求数据的缓存和请求头的中ContentLength的修改

public class CustomServerHttpRequestDecorator extends ServerHttpRequestDecorator {

    private final ServerWebExchange exchange;
    private final HttpHeaders headers;
    private final CachedBodyOutputMessage outputMessage;


    public CustomServerHttpRequestDecorator(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) {
        super(exchange.getRequest());
        this.exchange = exchange;
        this.headers = headers;
        this.outputMessage = outputMessage;
    }

    @Override
    public HttpHeaders getHeaders() {
        long contentLength = headers.getContentLength();
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.putAll(super.getHeaders());
        if (contentLength > 0) {
            httpHeaders.setContentLength(contentLength);
        } else {
            httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
        }
        return httpHeaders;
    }

    @Override
    public Flux<DataBuffer> getBody() {
        return outputMessage.getBody();
    }
}

3. 构建返回修改类

public class CustomServerHttpResponseDecorator extends ServerHttpResponseDecorator {

    private  final DataBufferFactory bufferFactory;

    private final Function<byte[],byte[]> handler;

    private final ServerWebExchange exchange;

    public CustomServerHttpResponseDecorator(ServerWebExchange exchange,  DataBufferFactory bufferFactory,Function<byte[],byte[]> handler) {
        super(exchange.getResponse());
        this.bufferFactory = bufferFactory;
        this.handler = handler;
        this.exchange = exchange;
    }

    @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);
            content =  handler.apply(content);
            return bufferFactory.wrap(content);
        }));
    }

    @Override
    public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
        return writeWith(Flux.from(body).flatMapSequential(p -> p));
    }
}

4. 写出修改后的请求体

    /**
     * 写出消息
     *
     * @param exchange
     * @param chain
     * @return
     */
    public static Mono<Void> writeModifyMessage(ServerWebExchange exchange, GatewayFilterChain chain, BodyInserter bodyInserter) {
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        headers.remove(HttpHeaders.CONTENT_LENGTH);
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
        ServerHttpRequest requestDecorator = new CustomServerHttpRequestDecorator(exchange, headers, outputMessage);
        return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> chain.filter(exchange.mutate().request(requestDecorator).build())));
    }

4. 针对文件上传的请求体缓存

针对文件上传这种类型的请求,编码和解码会比较麻烦这里扩展webflux中的ClientHttpRequest来对请求体进行编码


public class MultipartCachedBodyOutputMessage implements ClientHttpRequest {


    private ServerWebExchange serverWebExchange;

    private ServerHttpRequest serverRequest;

    private HttpHeaders httpHeaders;

    private boolean cached = false;
   
    private Flux<DataBuffer> body = Flux
            .error(new IllegalStateException("数据异常"));

    public MultipartCachedBodyOutputMessage(ServerWebExchange exchange, HttpHeaders httpHeaders) {
        this.serverWebExchange = exchange;
        this.serverRequest = exchange.getRequest();
        this.httpHeaders = httpHeaders;
    }

    @Override
    public HttpMethod getMethod() {
        return serverRequest.getMethod();
    }

    @Override
    public URI getURI() {
        return serverRequest.getURI();
    }

    @Override
    public MultiValueMap<String, HttpCookie> getCookies() {
        return serverRequest.getCookies();
    }

    @Override
    public <T> T getNativeRequest() {
        return (T) serverRequest;
    }

    @Override
    public DataBufferFactory bufferFactory() {
        return serverWebExchange.getResponse().bufferFactory();
    }

    @Override
    public void beforeCommit(Supplier<? extends Mono<Void>> action) {

    }

    @Override
    public boolean isCommitted() {
        return false;
    }

    @Override
    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
        this.body = Flux.from(body);
        this.cached = true;
        return Mono.empty();
    }

    @Override
    public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
        return writeWith(Flux.from(body).flatMap(p -> p));
    }

    @Override
    public Mono<Void> setComplete() {
        return writeWith(Flux.empty());
    }

    @Override
    public HttpHeaders getHeaders() {
        return this.httpHeaders;
    }

   public boolean isCached() {
        return this.cached;
    }

    /**
     * @return body as {@link Flux}
     */
    public Flux<DataBuffer> getBody() {
        return this.body;
    }

5. 针对文件上传的请求体包装


public class MultipartServerHttpRequestDecorator extends ServerHttpRequestDecorator {

    private HttpHeaders headers;
    private MultipartServerHttpRequestDecorator outputMessage;

    public MultipartServerHttpRequestDecorator(ServerWebExchange exchange, HttpHeaders headers, MultipartCachedBodyOutputMessage outputMessage) {
        super(exchange.getRequest());
        this.headers= headers;
        this.outputMessage = outputMessage;

    }

    @Override
    public HttpHeaders getHeaders() {
        long contentLength = headers.getContentLength();
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.putAll(headers);
        if (contentLength > 0) {
            httpHeaders.setContentLength(contentLength);
        } else {
            httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
        }
        return httpHeaders;
    }

    @Override
    public Flux<DataBuffer> getBody() {
        return outputMessage.getBody();
    }
}

6. 读取文件上传请求和转换写出

  public static Mono<Void> readFormMultiPartData(ServerWebExchange exchange, GatewayFilterChain chain,
                                                   Function<MultiValueMap<String, Part>, MultiValueMap<String, Part>> function) {
        return exchange.getMultipartData()
                .map(data -> function.apply(data))
                .map(data -> BodyInserters.fromMultipartData(data))
                .flatMap(bodyInserter -> {
                    HttpHeaders headers = new HttpHeaders();
                    headers.putAll(exchange.getRequest().getHeaders());
                    headers.remove(HttpHeaders.CONTENT_LENGTH);
                    MultipartCachedBodyOutputMessage outputMessage = new MultipartCachedBodyOutputMessage(exchange, headers);
                    return bodyInserter.insert(outputMessage, new BodyInserterContext())
                            .then(Mono.defer(() -> chain.filter(exchange.mutate().request(new MultipartServerHttpRequestDecorator(exchange, headers, outputMessage)).build())))
                            .onErrorResume((Function<Throwable, Mono<Void>>) throwable -> {
                                if (outputMessage.isCached()) {
                                    return outputMessage.getBody().map(DataBufferUtils::release).then(Mono.error(throwable));
                                }
                                return Mono.error(throwable);
                            });
                });
    }