Spring 读取请求体两次

Dan*_*zyk 6 logging spring spring-mvc

在春天,我有一个带有端点的控制器,如下所示:

@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
@ResponseBody
public OutputStuff createStuff(@RequestBody Stuff stuff) {
    //my logic here
}
Run Code Online (Sandbox Code Playgroud)

这样,如果在此端点上执行 POST,请求正文中的 JSON 将自动反序列化为我的模型 ( Stuff)。问题是,我只是需要在原始 JSON 传入时记录它!我尝试了不同的方法。

  1. 注射HttpServletRequestcreateStuff读体内有和日志:

代码:

@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
@ResponseBody
public OutputStuff createStuff(@RequestBody Stuff stuff, HttpServletRequest req) {
    StringBuilder sb = new StringBuilder();
    req.getReader().getLines().forEach(line -> {
        sb.append(line);
    });
    //log sb.toString();
    //my logic here
}
Run Code Online (Sandbox Code Playgroud)

问题在于,当我执行此操作时,读取器的 InputStream 已被执行以将 JSON 反序列化为Stuff. 所以我会得到一个错误,因为我不能两次读取相同的输入流。

  1. 使用HandlerInterceptorAdapter将在调用实际处理程序之前记录原始 JSON 的自定义。

代码(部分):

public class RawRequestLoggerInterceptor extends HandlerInterceptorAdapter {

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        StringBuilder sb = new StringBuilder();
        req.getReader().getLines().forEach(line -> {
            sb.append(line);
        });
        //log sb.toString();

        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

这个问题是,当反序列化stuff发生时,来自请求的 InputStream 已经被读取了!所以我会再次得到一个例外。

  1. 我考虑过但尚未实现的另一种选择是以某种方式迫使 Spring 使用我的自定义实现HttpServletRequest来缓存输入流并允许对其进行多次读取。我不知道这是否可行,而且我找不到任何文档或示例!

  2. 另一种选择是不在Stuff我的端点上读取,而是将请求正文读取为String,记录它然后将其反序列化为StuffusingObjectMapper或类似的东西。我也不喜欢这个想法。

有没有更好的解决方案,我没有提到和/或不知道?我将不胜感激。我正在使用最新版本的 SpringBoot。

isa*_*mez 8

多次读取请求正文,我们必须缓存初始负载。因为一旦原始的InputStream被消耗,我们就无法再次读取它。

首先,Spring MVC提供了ContentCachingRequestWrapper类来存储原始内容。因此,我们可以调用getContentAsByteArray()方法多次检索正文。

因此,在您的情况下,您可以在过滤器中使用此类:

@Component
public class CachingRequestBodyFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
  throws IOException, ServletException {
        HttpServletRequest currentRequest = (HttpServletRequest) servletRequest;
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(currentRequest);
        // Other details

        chain.doFilter(wrappedRequest, servletResponse);
    }
}
Run Code Online (Sandbox Code Playgroud)

或者,您可以在应用程序中注册CommonsRequestLoggingFilter 。此过滤器在幕后使用ContentCachingRequestWrapper,旨在记录请求

  • ContentCachingRequestWrapper 仅在您使用“application/x-www-form-urlencoded”作为内容类型时才有效。如果您使用“application/json”作为内容类型,则必须实现自己的包装器。 (2认同)

Nat*_*ate 6

正如这篇文章中所引用的:How to Log HttpRequest and HttpResponse in a file? ,spring提供了AbstractRequestLoggingFilter,你可以用它来记录请求。

AbstractRequestLoggingFilter API 文档,可在此处找到