Spring Cloud Gateway 记录请求应答数据日志

Spring Cloud Gateway 记录请求应答数据日志

public class GatewayContext {

    public static final String CACHE_GATEWAY_CONTEXT = "cacheGatewayContext";

    /**
     * cache json body
     */
    private String cacheBody;
    /**
     * cache formdata
     */
    private MultiValueMap<String, String> formData;
    /**
     * cache reqeust path
     */
    private String path;


    public String getCacheBody() {
        return cacheBody;
    }

    public void setCacheBody(String cacheBody) {
        this.cacheBody = cacheBody;
    }

    public MultiValueMap<String, String> getFormData() {
        return formData;
    }

    public void setFormData(MultiValueMap<String, String> formData) {
        this.formData = formData;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }
}
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;

import io.netty.buffer.ByteBufAllocator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

// https://segmentfault.com/a/1190000017898354

@Component
public class LogRequestGlobalFilter
        implements GlobalFilter {

    /**
     * default HttpMessageReader
     */
    private static final List<HttpMessageReader<?>> messageReaders =
            HandlerStrategies.withDefaults().messageReaders();

    private Logger log = LoggerFactory.getLogger(LogRequestGlobalFilter.class);

    @Override
    public Mono<Void> filter(
            ServerWebExchange exchange,
            GatewayFilterChain chain) {
        /**
         * save request path and serviceId into gateway context
         */
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().pathWithinApplication().value();
        GatewayContext gatewayContext = new GatewayContext();
        gatewayContext.setPath(path);
        /**
         * save gateway context into exchange
         */
        exchange.getAttributes().put(GatewayContext.CACHE_GATEWAY_CONTEXT,
                gatewayContext);
        HttpHeaders headers = request.getHeaders();
        MediaType contentType = headers.getContentType();
        log.info("start-------------------------------------------------");
        log.info("HttpMethod:{},Url:{}", request.getMethod(),
                request.getURI().getRawPath());
        log.info("Headers token: {}", headers.getFirst("token"));
        if (request.getMethod() == HttpMethod.GET) {
            log.info("end-------------------------------------------------");
        }
        if (request.getMethod() == HttpMethod.POST) {
            Mono<Void> voidMono = null;
            if (MediaType.APPLICATION_JSON.equals(contentType)
                    || MediaType.APPLICATION_JSON_UTF8.equals(contentType)) {
                voidMono =
                        readBody(exchange, chain, gatewayContext);
            }
            if (MediaType.APPLICATION_FORM_URLENCODED.equals(contentType)) {
                voidMono =
                        readFormData(exchange, chain, gatewayContext);
            }

            return voidMono;
        }
        /* log.debug(
            "[GatewayContext]ContentType:{},Gateway context is set with {}",
            contentType, gatewayContext);*/
        return chain.filter(exchange);

    }

    /**
     * ReadFormData
     *
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> readFormData(
            ServerWebExchange exchange,
            GatewayFilterChain chain,
            GatewayContext gatewayContext) {
        final ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();

        return exchange.getFormData()
                .doOnNext(multiValueMap -> {
                    gatewayContext.setFormData(multiValueMap);
                    log.info("Post x-www-form-urlencoded:{}",
                            multiValueMap);
                    log.info(
                            "end-------------------------------------------------");
                })
                .then(Mono.defer(() -> {
                    Charset charset = headers.getContentType().getCharset();
                    charset = charset == null ? StandardCharsets.UTF_8 : charset;
                    String charsetName = charset.name();
                    MultiValueMap<String, String> formData =
                            gatewayContext.getFormData();
                    /**
                     * formData is empty just return
                     */
                    if (null == formData || formData.isEmpty()) {
                        return chain.filter(exchange);
                    }
                    StringBuilder formDataBodyBuilder = new StringBuilder();
                    String entryKey;
                    List<String> entryValue;
                    try {
                        /**
                         * repackage form data
                         */
                        for (Map.Entry<String, List<String>> entry : formData
                                .entrySet()) {
                            entryKey = entry.getKey();
                            entryValue = entry.getValue();
                            if (entryValue.size() > 1) {
                                for (String value : entryValue) {
                                    formDataBodyBuilder.append(entryKey).append("=")
                                            .append(
                                                    URLEncoder.encode(value, charsetName))
                                            .append("&");
                                }
                            } else {
                                formDataBodyBuilder
                                        .append(entryKey).append("=").append(URLEncoder
                                        .encode(entryValue.get(0), charsetName))
                                        .append("&");
                            }
                        }
                    } catch (UnsupportedEncodingException e) {
                        // ignore URLEncode Exception
                    }
                    /**
                     * substring with the last char '&'
                     */
                    String formDataBodyString = "";
                    if (formDataBodyBuilder.length() > 0) {
                        formDataBodyString = formDataBodyBuilder.substring(0,
                                formDataBodyBuilder.length() - 1);
                    }
                    /**
                     * get data bytes
                     */
                    byte[] bodyBytes = formDataBodyString.getBytes(charset);
                    int contentLength = bodyBytes.length;
                    ServerHttpRequestDecorator decorator =
                            new ServerHttpRequestDecorator(
                                    request) {
                                /**
                                 * change content-length
                                 *
                                 * @return
                                 */
                                @Override
                                public HttpHeaders getHeaders() {
                                    HttpHeaders httpHeaders = new HttpHeaders();
                                    httpHeaders.putAll(super.getHeaders());
                                    if (contentLength > 0) {
                                        httpHeaders.setContentLength(contentLength);
                                    } else {
                                        httpHeaders.set(HttpHeaders.TRANSFER_ENCODING,
                                                "chunked");
                                    }
                                    return httpHeaders;
                                }

                                /**
                                 * read bytes to Flux<Databuffer>
                                 *
                                 * @return
                                 */
                                @Override
                                public Flux<DataBuffer> getBody() {
                                    return DataBufferUtils
                                            .read(new ByteArrayResource(bodyBytes),
                                                    new NettyDataBufferFactory(
                                                            ByteBufAllocator.DEFAULT),
                                                    contentLength);
                                }
                            };
                    ServerWebExchange mutateExchange =
                            exchange.mutate().request(decorator).build();
                /*   log.info("[GatewayContext]Rewrite Form Data :{}",
                       formDataBodyString);*/

                    return chain.filter(mutateExchange);
                }));
    }
    /**
     * ReadJsonBody
     *
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> readBody(
            ServerWebExchange exchange,
            GatewayFilterChain chain,
            GatewayContext gatewayContext) {
        /**
         * join the body
         */
        return DataBufferUtils.join(exchange.getRequest().getBody())
                .flatMap(dataBuffer -> {
                    /*
                     * read the body Flux<DataBuffer>, and release the buffer
                     * //TODO when SpringCloudGateway Version Release To G.SR2,this can be update with the new version's feature
                     * see PR https://github.com/spring-cloud/spring-cloud-gateway/pull/1095
                     */
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);
                    Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                        DataBuffer buffer =
                                exchange.getResponse().bufferFactory().wrap(bytes);
                        DataBufferUtils.retain(buffer);
                        return Mono.just(buffer);
                    });
                    /**
                     * repackage ServerHttpRequest
                     */
                    ServerHttpRequest mutatedRequest =
                            new ServerHttpRequestDecorator(exchange.getRequest()) {
                                @Override
                                public Flux<DataBuffer> getBody() {
                                    return cachedFlux;
                                }
                            };
                    /**
                     * mutate exchage with new ServerHttpRequest
                     */
                    ServerWebExchange mutatedExchange =
                            exchange.mutate().request(mutatedRequest).build();
                    /**
                     * read body string with default messageReaders
                     */
                    return ServerRequest.create(mutatedExchange, messageReaders)
                            .bodyToMono(String.class)
                            .doOnNext(objectValue -> {
                                log.info("PostBody:{}", objectValue);
                                log.info(
                                        "end-------------------------------------------------");
                                gatewayContext.setCacheBody(objectValue);
                        /*  log.debug("[GatewayContext]Read JsonBody:{}",
                              objectValue);*/
                            }).then(chain.filter(mutatedExchange));
                });
    }

}
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.http.HttpHeaders;
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.net.InetSocketAddress;
import java.net.URI;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
@Component
@Slf4j
public class LogResponseGlobalFilter implements GlobalFilter, Ordered {

    private static final String REQUEST_PREFIX = "Request Info [ ";

    private static final String REQUEST_TAIL = " ]";

    private static final String RESPONSE_PREFIX = "Response Info [ ";

    private static final String RESPONSE_TAIL = " ]";

    private StringBuilder normalMsg = new StringBuilder();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        ServerHttpResponse response = exchange.getResponse();

        DataBufferFactory bufferFactory = response.bufferFactory();
        normalMsg.append(RESPONSE_PREFIX);
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
            @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.map(dataBuffer -> {
                        // probably should reuse buffers
                        byte[] content = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(content);
                        DataBufferUtils.release(dataBuf);
                        String responseResult = new String(content, Charset.forName("UTF-8"));
                        normalMsg.append("status=").append(this.getStatusCode());
                        normalMsg.append(";header=").append(this.getHeaders());
                        normalMsg.append(";responseResult=").append(responseResult);
                        normalMsg.append(RESPONSE_TAIL);
                        log.info(normalMsg.toString());
                        return bufferFactory.wrap(content);
                    }));
                }
                return super.writeWith(body); // if body is not a flux. never got there.
            }
        };

        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }

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

}
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Spring Cloud Gateway是一个基于Spring Cloud的微服务网关,它提供了一系列过滤器、路由和负载均衡等功能,可以用于构建分布式系统中的API网关。 要记录缓存请求的body和form表单,可以通过自定义过滤器来实现。首先,需要创建一个实现了GatewayFilter和Ordered接口的自定义过滤器类。在这个过滤器中,可以获取到请求的body和form表单,并将其缓存起来。 下面是一个简单的实现示例: ```java import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Component public class RequestLoggingFilter implements GatewayFilter, Ordered { private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject"; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取请求的body和form表单 String body = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY); String form = exchange.getRequest().getHeaders().getFirst("Content-Type"); // TODO: 在这里记录缓存body和form表单 return chain.filter(exchange); } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } } ``` 在这个过滤器中,我们通过`exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY)`方法获取到了请求的body,并通过`exchange.getRequest().getHeaders().getFirst("Content-Type")`方法获取到了请求的form表单。 接下来,可以在`filter`方法中实现对缓存的记录逻辑,比如将body和form表单保存到数据库或日志中。 最后,将自定义的过滤器类添加到Spring Cloud Gateway的路由中,以便生效。 以上是一个简单的示例,实际情况可能会更复杂,需要根据具体的业务需求进行适当的调整和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值