Spring Cloud Gateway 2.x print log

Scenes

At the service gateway level, you need to print out the user's request body and other parameters each time. The gateway uses Reactor responsive programming, which is somewhat different from the Zuul gateway's way of obtaining streams.

However, the basic idea is the same. The body stream is read in the filter and then cached back. Because the body stream, the framework only allows to read it once by default.

Ideas

1. Add a filter to intercept a request

GatewayConfig.java

Add a configuration class, configure a high-priority filter, and inject a PayloadServerWebExchangeDecoratorclass that wraps request and response.

package com.demo.gateway2x.config;

import com.demo.gateway2x.decorator.PayloadServerWebExchangeDecorator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.server.WebFilter;

@Configuration
public class GatewayConfig {

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE) //过滤器顺序
    public WebFilter webFilter() {
        return (exchange, chain) -> chain.filter(new PayloadServerWebExchangeDecorator(exchange));
    }

}

PayloadServerWebExchangeDecorator.java

In this class, we have implemented the framework ServerWebExchangeDecoratorclass and injected two custom classes at the same time, PartnerServerHttpRequestDecoratorand PartnerServerHttpResponseDecorator,

These two classes are used to intercept requests and responses later.

package com.demo.gateway2x.decorator;

import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebExchangeDecorator;

public class PayloadServerWebExchangeDecorator extends ServerWebExchangeDecorator {

    private PartnerServerHttpRequestDecorator requestDecorator;

    private PartnerServerHttpResponseDecorator responseDecorator;

    public PayloadServerWebExchangeDecorator(ServerWebExchange delegate) {
        super(delegate);
        requestDecorator = new PartnerServerHttpRequestDecorator(delegate.getRequest());
        responseDecorator = new PartnerServerHttpResponseDecorator(delegate.getResponse());
    }

    @Override
    public ServerHttpRequest getRequest() {
        return requestDecorator;
    }

    @Override
    public ServerHttpResponse getResponse() {
        return responseDecorator;
    }

}

2. Intercept the request once when the request enters

PartnerServerHttpRequestDecorator.java

This class is implemented ServerHttpRequestDecorator, and in the constructor, reactive programming is used, and the method of printing log is called. Pay attention Mono<DataBuffer> mono = DataBufferUtils.join(flux);,

Here, Flux is merged into a Mono, because if you don’t do this, the body content will be too much and will be printed in sections. This is an important point.

After printing and RequestParamsHandle.chainprinting the log, we returned another one dataBufferfor downward transfer, otherwise it dataBuffercannot be used after being read once.

package com.demo.gateway2x.decorator;

import lombok.extern.slf4j.Slf4j;
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 reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import static reactor.core.scheduler.Schedulers.single;

@Slf4j
public class PartnerServerHttpRequestDecorator extends ServerHttpRequestDecorator {

    private Flux<DataBuffer> body;

    public PartnerServerHttpRequestDecorator(ServerHttpRequest delegate) {
        super(delegate);
        Flux<DataBuffer> flux = super.getBody();
        if (ParamsUtils.CHAIN_MEDIA_TYPE.contains(delegate.getHeaders().getContentType())) {
            Mono<DataBuffer> mono = DataBufferUtils.join(flux);
            body = mono.publishOn(single()).map(dataBuffer -> RequestParamsHandle.chain(delegate, log, dataBuffer)).flux();
        } else {
            body = flux;
        }
    }

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

}

RequestParamsHandle.java

This class is mainly used to read dataBufferand do log printing processing, and it can also be used for other purposes such as parameter verification.

package com.demo.gateway2x.decorator;

import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.Map;

public class RequestParamsHandle {

    public static <T extends DataBuffer> T chain(ServerHttpRequest delegate, Logger log, T buffer) {
        ParamsUtils.BodyDecorator bodyDecorator = ParamsUtils.buildBodyDecorator(buffer);
        // 参数校验 和 参数打印
        log.info("Payload: {}", JSON.toJSONString(validParams(getParams(delegate, bodyDecorator.getBody()))));
        return (T) bodyDecorator.getDataBuffer();
    }

    public static Map<String,Object> getParams(ServerHttpRequest delegate, String body) {
        // 整理参数
        Map<String,Object> params = new HashMap<>();
        if (delegate.getQueryParams() != null) {
            params.putAll(delegate.getQueryParams());
        }
        if (!StringUtils.isEmpty(body)) {
            params.putAll(JSON.parseObject(body));
        }
        return params;
    }

    public static Map<String,Object> validParams(Map<String,Object> params) {
        // todo 参数校验
        return params;
    }

}

3. When the result is returned, intercept the response

PartnerServerHttpResponseDecorator.java

This class is the same as the request above, intercepting the response stream and recording it.

package com.demo.gateway2x.decorator;

import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import static reactor.core.scheduler.Schedulers.single;

@Slf4j
public class PartnerServerHttpResponseDecorator extends ServerHttpResponseDecorator {

    PartnerServerHttpResponseDecorator(ServerHttpResponse delegate) {
        super(delegate);
    }

    @Override
    public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
        return super.writeAndFlushWith(body);
    }

    @Override
    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
        final MediaType contentType = super.getHeaders().getContentType();
        if (ParamsUtils.CHAIN_MEDIA_TYPE.contains(contentType)) {
            if (body instanceof Mono) {
                final Mono<DataBuffer> monoBody = (Mono<DataBuffer>) body;
                return super.writeWith(monoBody.publishOn(single()).map(dataBuffer -> ResponseParamsHandle.chain(log, dataBuffer)));
            } else if (body instanceof Flux) {
                Mono<DataBuffer> mono = DataBufferUtils.join(body);
                final Flux<DataBuffer> monoBody = mono.publishOn(single()).map(dataBuffer -> ResponseParamsHandle.chain(log, dataBuffer)).flux();
                return super.writeWith(monoBody);
            }
        }
        return super.writeWith(body);
    }

}

ResponseParamsHandle.java

Log printing of response stream

package com.demo.gateway2x.decorator;

import org.slf4j.Logger;
import org.springframework.core.io.buffer.DataBuffer;

public class ResponseParamsHandle {

    public static <T extends DataBuffer> T chain(Logger log, T buffer) {
        ParamsUtils.BodyDecorator bodyDecorator = ParamsUtils.buildBodyDecorator(buffer);
        // 参数校验 和 参数打印
        log.info("Payload: {}", bodyDecorator.getBody());
        return (T) bodyDecorator.getDataBuffer();
    }

}

The following is the actual operation, sending an http request:

Console log results:

github source address: https://github.com/qiaomengnan16/gateway-2x-log-demo

to sum up

The way gateway and zuul print parameters are the same, but the gateway uses reactor, which is slightly different from zuul's direct reading stream. What you need to know here is that Flux needs to be converted to Mono. If you don’t convert it, it’s easy to divide it. Batch printing.

Refer to the following blogs:

Customize the Spring Webflux filter to solve the problem that the request body can only be obtained once: https://my.oschina.net/junjunyuanyuankeke/blog/2253493

SpringCloud Gateway obtains the post request body (request body): https://blog.51cto.com/thinklili/2329184

How to get string/object from Mono stream in Reactive Java? : Https://www.bilibili.com/read/cv5787745

How to correctly read Flux and convert it to a single inputStream:
https://stackoverflow.com/questions/46460599/how-to-correctly-read-fluxdatabuffer-and-convert-it-to-a-single-inputstream

Only one connection receive subscriber allowed solution ideas: https://blog.csdn.net/weixin_40899682/article/details/82784242

Guess you like

Origin blog.csdn.net/cainiao1412/article/details/108847792