如何在HandlerInterceptor中读取请求体?

use*_*552 3 java spring spring-mvc spring-boot

我有 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;
}
}
Run Code Online (Sandbox Code Playgroud)

但根据这个答案Get RequestBody and ResponseBody at HandlerInterceptor "RequestBody can be read only Once",所以据我了解,我读取输入流,然后 Spring 尝试执行相同的操作,但流已被读取,并且我收到错误: “缺少所需的请求正文......”

所以我尝试了不同的方法来制作缓冲输入流,即:

HttpServletRequest httpServletRequest = new ContentCachingRequestWrapper(request);
new BufferedReader(new InputStreamReader(httpServletRequest.getInputStream())).lines().collect(Collectors.joining(System.lineSeparator()))
Run Code Online (Sandbox Code Playgroud)

或者

InputStream bufferedInputStream = new BufferedInputStream(request.getInputStream());
Run Code Online (Sandbox Code Playgroud)

但没有任何帮助我也尝试使用

@ControllerAdvice
public class UserActionRequestBodyAdviceAdapter extends RequestBodyAdviceAdapter {
Run Code Online (Sandbox Code Playgroud)

但它只有正文,没有 URL 或请求参数等请求信息 也尝试使用过滤器,但结果相同。

因此,我需要一种好方法来从请求中获取信息,如用户、URL、参数、正文(如果存在)并将其写入数据库。

Zaw*_* oo 7

要记录 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

上面AdviceAdapter无法处理该GET请求。所以,你可以使用HandlerInterceptor.

CustomWebConfigurerAdapter.java

@Component
public class CustomWebConfigurerAdapter implements WebMvcConfigurer {

   @Autowired
   private CustomLogInterceptor httpServiceInterceptor;

   @Override
   public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(httpServiceInterceptor);
   }
}
Run Code Online (Sandbox Code Playgroud)

CustomLogInterceptor.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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里你可以参考我的 git 中的完整源代码。

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

+功能=>它已经与ELK集成(Elasticsearch,Logstash,Kibana)


Har*_*hit 7

您可以用来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");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这里要注意的主要事情是,您必须ContentCachingRequestWrapper在过滤器链中传递对象,否则您将无法在其中获取请求内容。

在上面的示例中,如果您使用chain.doFilter(req, res)chain.doFilter(request, res)那么您将不会在wrappedRequest对象中获得请求正文。

  • 这确实有效,但仅当您在“chain.doFilter(wrappedRequest, res)”调用后登录时才有效。原因是因为`ContentCachingRequestWrapper`仅在读取InputStream时才填充缓存。如果您想在处理请求之前记录请求负载,那么您需要使用您自己的包装器扩展“ContentCachingRequestWrapper”。 (3认同)