背景

项目基于springboot开发,RestFull接口向外暴露的API需要进行参数日志记录,即在进入真正controller方法前,需要先记录处理的参数信息,所以需要对提交的POST流进行JSON读,并将相关参数打印记录。

问题

如果在Filter中使用request.getInputStream()来获取流来得到body中的信息,可以达到预期效果,但是流的获取只能获取一次,之后再获取就获取不到了,导致controller无法拿到参数而报错。

解决

参考相关资料发现实现一个类继承HttpServletRequestWrapper,重写其中的getInputStream方法,让其可以重复获取我们想要的流数据。
代码如下:

package com.wyx.gateway.config;

import org.springframework.util.StringUtils;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

public class RequestContextWrapper extends HttpServletRequestWrapper {

    private String requestBody = null;
    private HttpServletRequest req = null;

    public RequestContextWrapper(HttpServletRequest request, String requestBody) throws IOException {
        super(request);
        this.requestBody = requestBody;
        this.req = request;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new StringReader(requestBody));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new ServletInputStream() {

            private InputStream in = new ByteArrayInputStream(
                    requestBody.getBytes(StringUtils.isEmpty(req.getCharacterEncoding()) ? "UTF-8" : req.getCharacterEncoding()));

			// 读取 requestBody 中的数据
            @Override
            public int read() throws IOException {
                return in.read();
            }
        };
    }

}

日志记录Filter参考如下:

package com.wyx.gateway.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.slf4j.MDC;
import org.springframework.util.StringUtils;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;

@Slf4j
public class RequestLoggerFilter implements Filter {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

		// 记录链路id,没有的话可以去除
        String traceId = StringUtils.replace(UUID.randomUUID().toString(), "-", "");
        MDC.put("traceId", traceId);

        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        String uri = req.getRequestURI();
        String param = OBJECT_MAPPER.writeValueAsString(req.getParameterMap());
        String requestBody = IOUtils.toString(req.getInputStream(), "UTF-8");

        RequestContextWrapper requestWrapper = new RequestContextWrapper(req, requestBody);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(resp);

        long startTime = System.currentTimeMillis();
		// 跳过健康检查
        if (!uri.contains("health")) {
            log.info("request_in uri:{}, param: {}, body: {}", uri, param, requestBody);
        }

        chain.doFilter(requestWrapper, responseWrapper);

        String responseBody = IOUtils.toString(responseWrapper.getContentInputStream(), "UTF-8");
        responseWrapper.copyBodyToResponse();

        long procTime = System.currentTimeMillis() - startTime;
        if (!uri.contains("health")) {
            log.info("request_out proc_time: {}, response: {}", procTime, responseBody);
        }

        MDC.clear();
    }

    @Override
    public void destroy() {

    }
}

参考:https://cloud.tencent.com/developer/article/1963415

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐