Lei*_*ang 4 java servlets spring-mvc servlet-filters spring-boot
在 RestController 的 @ExceptionHandler 中记录请求正文字符串。
默认情况下,当 request 是无效的 json 时,springboot 会抛出一个HttpMessageNotReadableException,但该消息非常通用,不包含具体的请求正文。这使得调查变得困难。另一方面,我可以使用过滤器记录每个请求字符串,但这样日志将被太多成功的字符串淹没。我只想在请求无效时记录该请求。我真正想要的是@ExceptionHandler我将得到该字符串(以前在某个地方得到过)并记录为错误。
为了说明这个问题,我在github上创建了一个demo项目。
@RestController
public class GreetController {
    protected static final Logger log = LogManager.getLogger();
    @PostMapping("/")
    public String greet(@RequestBody final WelcomeMessage msg) {
        // if controller successfully returned (valid request),
        // then don't want any request body logged
        return "Hello " + msg.from;
    }
    @ExceptionHandler({HttpMessageNotReadableException.class})
    public String addStudent(HttpMessageNotReadableException e) {
        // this is what I really want!
        log.error("{the request body string got somewhere, such as Filters }"); 
        return "greeting from @ExceptionHandler";
    }
}
Run Code Online (Sandbox Code Playgroud)
有效请求
curl  -H "Content-Type: application/json" http://localhost:8080 --data '{"from":"jim","message":"nice to meet you!"}'
无效请求(无效 json)
curl  -H "Content-Type: application/json" http://localhost:8080 --data '{"from":"jim","message""nice to meet you!"}' 
我曾经尝试过HandlerInterceptor,但会收到一些错误,例如
“java.lang.IllegalStateException:在当前请求已调用 getReader() 后无法调用 getInputStream()”。
经过一番搜索1  2后,我决定使用Filterwith ContentCachingRequestWrapper。
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        ContentCachingRequestWrapper cachedRequest = new ContentCachingRequestWrapper(httpServletRequest);
        chain.doFilter(cachedRequest, response);
        String requestBody = IOUtils.toString(cachedRequest.getContentAsByteArray(), cachedRequest.getCharacterEncoding());
        log.info(requestBody);
    }
Run Code Online (Sandbox Code Playgroud)
该代码运行良好,只是日志位于RestController. 如果我改变顺序:
        String requestBody = IOUtils.toString(cachedRequest.getReader());
        log.info(requestBody);
        chain.doFilter(cachedRequest, response);
Run Code Online (Sandbox Code Playgroud)
适用于无效请求,但当请求有效时,出现以下异常:
com.example.demo.GreetController :缺少必需的请求正文:public java.lang.String com.example.demo.GreetController.greet(com.example.demo.WelcomeMessage)
我还尝试了getContentAsByteArray,getInputStream和getReader方法,因为一些教程说框架会检查特定方法调用。
CommonsRequestLoggingFilter按照@M的建议尝试过。德努姆。
但一切都是徒劳。
现在我有点困惑。谁能解释一下请求有效和无效时RestController和的执行顺序吗?Filter有没有更简单的方法(更少的代码)来实现我的最终目标?谢谢!
我使用的是springboot 2.6.3,jdk11。
创建一个过滤器,将您的请求包装在 a 中ContentCachingRequestWrapper(仅此而已)。
在异常处理方法中使用HttpServletRequest作为参数作为参数
检查实例是否ContentCachingRequestWrapper
使用getContentAsByteArray来获取内容。
像这样的东西。
public class CachingFilter extends OncePerRequestFilter {
protected abstract void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
  filterChain.doFilter(new ContentCachingRequestWrapper(request), new ContentCachingResponseWrapper(response));
}
Run Code Online (Sandbox Code Playgroud)
注意:我也包装了回复,以防万一您也想要这样。
现在,在您的异常处理方法中,使用HttpServletRequest作为参数,并利用它来发挥您的优势。
@ExceptionHandler({HttpMessageNotReadableException.class})
public String addStudent(HttpMessageNotReadableException e, HttpServletRequest req) {
  if (req instanceof ContentCachingRequestWrapper) {
    ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) req;
    log.error(new String(wrapper.getContentAsByteArray()));
  }
  return "greeting from @ExceptionHandler";
}
Run Code Online (Sandbox Code Playgroud)
可能是多个过滤器添加了一个包装器,HttpServletRequest因此您可能需要迭代这些包装器,您也可以使用这个
private Optional<ContentCachingRequestWrapper> findWrapper(ServletRequest req) {
    ServletRequest reqToUse = req;
    while (reqToUse instanceof ServletRequestWrapper) {
        if (reqToUse instanceof ContentCachingRequestWrapper) {
            return Optional.of((ContentCachingRequestWrapper) reqToUse);
        }
        reqToUse = ((ServletRequestWrapper) reqToUse).getRequest();
    }
    return Optional.empty();
}
Run Code Online (Sandbox Code Playgroud)
然后你的异常处理程序看起来像这样
@ExceptionHandler({HttpMessageNotReadableException.class})
public String addStudent(HttpMessageNotReadableException e, HttpServletRequest req) {
  Optional<ContentCachingRequestWrapper) wrapper = findWrapper(req);
  wrapper.ifPresent(it -> log.error(new String(it.getContentAsByteArray())));
 
  return "greeting from @ExceptionHandler";
}
Run Code Online (Sandbox Code Playgroud)
但这可能取决于您的过滤器顺序以及是否有多个过滤器添加包装器。
|   归档时间:  |  
           
  |  
        
|   查看次数:  |  
           3897 次  |  
        
|   最近记录:  |