簡體   English   中英

如何讀取 HandlerInterceptor 中的請求體?

[英]How to read request body in HandlerInterceptor?

我有 Spring Boot,我需要在數據庫中記錄用戶操作,所以我寫了 HandlerInterceptor:

@Component
public class LogInterceptor implements HandlerInterceptor {
@Autovired
private LogUserActionService logUserActionService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
throws IOException {
    String userName = SecurityContextHolder.getContext().getAuthentication().getName();
    String url = request.getRequestURI();
    String queryString = request.getQueryString() != null ? request.getQueryString() : "";
    String body = "POST".equalsIgnoreCase(request.getMethod()) ? new BufferedReader(new InputStreamReader(request.getInputStream())).lines().collect(Collectors.joining(System.lineSeparator())) : queryString;
    logUserActionService.logUserAction(userName, url, body);
    return true;
}
}

但是根據這個答案Get RequestBody and ResponseBody at HandlerInterceptor "RequestBody can be read only once",據我所知,我讀取了輸入 stream 然后 Spring 嘗試做同樣的事情,但是 stream 已經被讀取並且我收到一個錯誤: “缺少所需的請求正文……”

所以我嘗試了不同的方法來制作緩沖輸入 stream 即:

HttpServletRequest httpServletRequest = new ContentCachingRequestWrapper(request);
new BufferedReader(new InputStreamReader(httpServletRequest.getInputStream())).lines().collect(Collectors.joining(System.lineSeparator()))

或者

InputStream bufferedInputStream = new BufferedInputStream(request.getInputStream());

但沒有任何幫助我也嘗試使用

@ControllerAdvice
public class UserActionRequestBodyAdviceAdapter extends RequestBodyAdviceAdapter {

但它只有主體,沒有請求信息,如 URL 或請求參數也嘗試使用過濾器,但結果相同。

因此,我需要一種從用戶、URL、參數、正文(如果存在)等請求中獲取信息並將其寫入數據庫的好方法。

您可以使用Filter來記錄請求正文。

public class LoggingFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
        try {
            chain.doFilter(wrappedRequest, res);
        } finally {
            logRequestBody(wrappedRequest);
        }
    }

    private static void logRequestBody(ContentCachingRequestWrapper request) {

        byte[] buf = request.getContentAsByteArray();
        if (buf.length > 0) {
            try {
                String requestBody = new String(buf, 0, buf.length, request.getCharacterEncoding());
                System.out.println(requestBody);
            } catch (Exception e) {
                System.out.println("error in reading request body");
            }
        }
    }
}

這里要注意的主要事情是您必須在過濾器鏈中傳遞ContentCachingRequestWrapper對象,否則您將無法在其中獲取請求內容。

在上面的例子中,如果你使用chain.doFilter(req, res)chain.doFilter(request, res)那么你將不會在wrappedRequest對象中得到請求體。

要記錄 HTTP 請求和響應,您可以使用RequestBodyAdviceAdapterResponseBodyAdvice 在這里,它以我的方式使用。

CustomRequestBodyAdviceAdapter.java

@ControllerAdvice
public class CustomRequestBodyAdviceAdapter extends RequestBodyAdviceAdapter {

    @Autowired
    HttpServletRequest httpServletRequest;

    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
            Class<? extends HttpMessageConverter<?>> converterType) {

        // here you can full log httpServletRequest and body.

        return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
    }
}

CustomResponseBodyAdviceAdapter.java

@ControllerAdvice
public class CustomResponseBodyAdviceAdapter implements ResponseBodyAdvice<Object> {

    @Autowired
    private LoggingService loggingService;

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
            Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        if (serverHttpRequest instanceof ServletServerHttpRequest && serverHttpResponse instanceof ServletServerHttpResponse) {

            // here you can full log httpServletRequest and body.
        }
        return o;
    }
}

以上AdviceAdapter無法處理GET請求。 因此,您可以使用HandlerInterceptor

自定義WebConfigurerAdapter.java

@Component
public class CustomWebConfigurerAdapter implements WebMvcConfigurer {

   @Autowired
   private CustomLogInterceptor httpServiceInterceptor;

   @Override
   public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(httpServiceInterceptor);
   }
}

自定義日志攔截器.java

@Component
public class CustomLogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (DispatcherType.REQUEST.name().equals(request.getDispatcherType().name()) && request.getMethod().equals(HttpMethod.GET.name())) {

            // here you can full log httpServletRequest and body for GET Request.

        }
        return true;
    }
}

在這里你可以在我的 git 中引用完整的源代碼。

springboot-http-request-response-logging-with-json-logger

+Feature => 它已經與ELK(Elasticsearch、Logstash、Kibana)集成

您可以使用RequestBodyAdviceAdapter獲取 POST/PUT 請求的請求正文數據。 您可以使用HandlerInterceptorAdapter進行 GET 調用。 這是一個工作示例 - https://frandorado.github.io/spring/2018/11/15/log-request-response-with-body-spring.html

@ControllerAdvice
public class CustomRequestBodyAdviceAdapter extends RequestBodyAdviceAdapter
{

@Autowired
HttpServletRequest httpServletRequest;

private static final Log LOGGER = LogFactory.getLog(CustomRequestBodyAdviceAdapter.class);

private static final Charset DEFAULT_CHARSET = ISO_8859_1;


@Override
public boolean supports(MethodParameter methodParameter, Type type, 
                        Class<? extends HttpMessageConverter<?>> aClass)
{
    return true;
}

@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage,
                            MethodParameter parameter, Type targetType,
        Class<? extends HttpMessageConverter<?>> converterType) 
{
    Instant startTime = Instant.now();
    StringBuilder stringBuilder = new StringBuilder();

    
    stringBuilder.append("REQUEST call Starts :: Start Time : %s ").append(startTime);

    try
    {
        logRequest(httpServletRequest, body);

    } 
    catch (IOException e) 
    {
        LOGGER.info("Exception getting the Request Body into the Log: {}" + e.getMessage());
    }
    

public void logRequest(HttpServletRequest httpServletRequest, Object body) throws IOException
{
    StringBuilder stringBuilder = new StringBuilder();
    Map<String, String> parameters = buildParametersMap(httpServletRequest);
    
    
    stringBuilder.append("REQUEST ");
    stringBuilder.append("method=[").append(httpServletRequest.getMethod()).append("] ");
    stringBuilder.append("path=[").append(httpServletRequest.getRequestURI()).append("] ");
    stringBuilder.append("headers=[").append(buildHeadersMap(httpServletRequest)).append("] ");
    
    if (!parameters.isEmpty())
    {
        stringBuilder.append("parameters=[").append(parameters).append("] ");
    }
    
    if (body != null)
    {
        stringBuilder.append("body=[" + body + "]");
    }

    ObjectMapper objectMapper = new ObjectMapper();
    
    String jsonInString = null;
    try 
    {
        jsonInString = objectMapper.writer().writeValueAsString(body);
    } 
    catch (JsonProcessingException e) 
    {
        throw new RestApiException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
    }
    
    stringBuilder.append("REQUEST Body = [").append(jsonInString).append("] ");
    
    LOGGER.info("BODY DATA >>>> " + jsonInString);
    LOGGER.info("Body - : {}" + stringBuilder);
}



private Map<String, String> buildParametersMap(HttpServletRequest httpServletRequest)
{
    Map<String, String> resultMap = new HashMap<>();
    Enumeration<String> parameterNames = httpServletRequest.getParameterNames();
    
    while (parameterNames.hasMoreElements())
    {
        String key = parameterNames.nextElement();
        String value = httpServletRequest.getParameter(key);
        resultMap.put(key, value);
    }
    
    return resultMap;
}

private Map<String, String> buildHeadersMap(HttpServletRequest request)
{
    Map<String, String> map = new HashMap<>();
    
    Enumeration<String> headerNames = request.getHeaderNames();
    while (headerNames.hasMoreElements()) 
    {
        String key = headerNames.nextElement();
        String value = request.getHeader(key);
        map.put(key, value);
    }
    
    return map;
}
}

我在這里使用了 ObjectMapper,因為我需要將主體響應作為原始 JSON 對象,但是afterBodyRead()在主體轉換為 Java 對象后被調用。

我發現這解決了我復制 application/json 內容類型的請求緩沖區的問題。 它還展示了如何擴展包裝器,正如對 Harshit 解決方案的評論中提到的那樣。

https://levelup.gitconnected.com/how-to-log-the-request-body-in-a-spring-boot-application-10083b70c66

重要的是您需要一個過濾器來將新請求傳遞給服務器。

@Component
public class LoggingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        if (Arrays.asList("POST", "PUT").contains(httpRequest.getMethod())) {
            CustomHttpRequestWrapper requestWrapper = new CustomHttpRequestWrapper(httpRequest);
            requestWrapper.setAttribute("input", requestWrapper.getBodyInStringFormat());
            filterChain.doFilter(requestWrapper, servletResponse);
            return;
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

記錄器需要一個自定義包裝器,而 spring 啟動提供的一個似乎不足以用於 application/json 類型的消息。

public class CustomHttpRequestWrapper extends HttpServletRequestWrapper {

    public String getBodyInStringFormat() {
        return bodyInStringFormat;
    }

    private final String bodyInStringFormat;

    public CustomHttpRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        bodyInStringFormat = readInputStreamInStringFormat(request.getInputStream(), Charset.forName(request.getCharacterEncoding()));
    }

    private String readInputStreamInStringFormat(InputStream stream, Charset charset) throws IOException {
        return getString(stream, charset);
    }

    static String getString(InputStream stream, Charset charset) throws IOException {
        final int MAX_BODY_SIZE = 1024;
        final StringBuilder bodyStringBuilder = new StringBuilder();
        if (!stream.markSupported()) {
            stream = new BufferedInputStream(stream);
        }

        stream.mark(MAX_BODY_SIZE + 1);
        final byte[] entity = new byte[MAX_BODY_SIZE + 1];
        final int bytesRead = stream.read(entity);

        if (bytesRead != -1) {
            bodyStringBuilder.append(new String(entity, 0, Math.min(bytesRead, MAX_BODY_SIZE), charset));
            if (bytesRead > MAX_BODY_SIZE) {
                bodyStringBuilder.append("...");
            }
        }
        stream.reset();

        return bodyStringBuilder.toString();
    }

    @Override
    public BufferedReader getReader()  {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream ()  {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bodyInStringFormat.getBytes());

        return new ServletInputStream() {
            private boolean finished = false;

            @Override
            public boolean isFinished() {
                return finished;
            }

            @Override
            public int available()  {
                return byteArrayInputStream.available();
            }

            @Override
            public void close() throws IOException {
                super.close();
                byteArrayInputStream.close();
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
                throw new UnsupportedOperationException();
            }

            public int read ()  {
                int data = byteArrayInputStream.read();
                if (data == -1) {
                    finished = true;
                }
                return data;
            }
        };
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM