Spring Boot ContentCachingResponseWrapper生成空响应

sy5wg1nm  于 2024-01-06  发布在  Spring
关注(0)|答案(3)|浏览(329)

我正在尝试在Spring MVC应用程序中实现用于记录请求和响应的过滤器。
我使用以下代码:

  1. @Component
  2. public class LoggingFilter extends OncePerRequestFilter {
  3. private static final Logger LOGGER = LoggerFactory.getLogger(LoggingFilter.class);
  4. @Override
  5. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
  6. ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
  7. ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
  8. LOGGER.debug(REQUEST_MESSAGE_FORMAT, requestWrapper.getRequestURI(), requestWrapper.getMethod(), requestWrapper.getContentType(),
  9. new ServletServerHttpRequest(requestWrapper).getHeaders(), IOUtils.toString(requestWrapper.getInputStream(), UTF_8));
  10. filterChain.doFilter(requestWrapper, responseWrapper);
  11. LOGGER.debug(RESPONSE_MESSAGE_FORMAT, responseWrapper.getStatus(), responseWrapper.getContentType(),
  12. new ServletServerHttpResponse(responseWrapper).getHeaders(), IOUtils.toString(responseWrapper.getContentInputStream(), UTF_8));
  13. }
  14. }

字符串
因此,我的请求和响应按预期记录。以下是日志:

  1. 2016-10-08 19:10:11.212 DEBUG 11072 --- [qtp108982313-19] by.kolodyuk.logging.LoggingFilter
  2. ----------------------------
  3. ID: 1
  4. URI: /resources/1
  5. Http-Method: GET
  6. Content-Type: null
  7. Headers: {User-Agent=[curl/7.41.0], Accept=[*/*], Host=[localhost:9015]}
  8. Body:
  9. --------------------------------------
  10. 2016-10-08 19:10:11.277 DEBUG 11072 --- [qtp108982313-19] by.kolodyuk.logging.LoggingFilter
  11. ----------------------------
  12. ID: 1
  13. Response-Code: 200
  14. Content-Type: application/json;charset=UTF-8
  15. Headers: {}
  16. Body: {"id":"1"}
  17. --------------------------------------


然而,返回的是空响应。下面是curl的输出:

  1. $ curl http://localhost:9015/resources/1 --verbose
  2. * Trying ::1...
  3. * Connected to localhost (::1) port 9015 (#0)
  4. > GET /resources/1 HTTP/1.1
  5. > User-Agent: curl/7.41.0
  6. > Host: localhost:9015
  7. > Accept: */*
  8. >
  9. < HTTP/1.1 200 OK
  10. < Date: Sat, 08 Oct 2016 17:10:11 GMT
  11. < Content-Type: application/json;charset=UTF-8
  12. < Content-Length: 0
  13. <
  14. * Connection #0 to host localhost left intact


有什么想法吗?

envsm3lx

envsm3lx1#

经过几个小时的努力,我终于找到了解决办法。
简言之,应该在filter方法的最后调用ContentCachingResponseWrapper.copyBodyToResponse()
ContentCachingResponseWrapper通过从响应输出流中阅读响应体来缓存响应体。因此,流变为空。要将响应写回输出流,应使用ContentCachingResponseWrapper.copyBodyToResponse()

xfb7svmp

xfb7svmp2#

终于解决了这个问题。这里是完美的解决方案:

  1. import com.fasterxml.jackson.databind.JsonNode;
  2. import com.fasterxml.jackson.databind.ObjectMapper;
  3. import org.apache.commons.io.IOUtils;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.http.*;
  8. import org.springframework.stereotype.Component;
  9. import org.springframework.web.filter.OncePerRequestFilter;
  10. import org.springframework.web.util.ContentCachingRequestWrapper;
  11. import org.springframework.web.util.ContentCachingResponseWrapper;
  12. import javax.servlet.FilterChain;
  13. import javax.servlet.ServletException;
  14. import javax.servlet.http.HttpServletRequest;
  15. import javax.servlet.http.HttpServletResponse;
  16. import java.io.BufferedReader;
  17. import java.io.IOException;
  18. import java.io.InputStreamReader;
  19. import java.net.URI;
  20. import java.util.Enumeration;
  21. import java.util.Map;
  22. import static java.nio.charset.StandardCharsets.UTF_8;
  23. import static net.logstash.logback.marker.Markers.appendFields;
  24. @Component
  25. public class LoggingFilter extends OncePerRequestFilter {
  26. private static final Logger LOGGER = LoggerFactory.getLogger(LoggingFilter.class);
  27. @Autowired
  28. private ObjectMapper objectMapper;
  29. @Override
  30. protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
  31. ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(httpServletRequest);
  32. ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(httpServletResponse);
  33. filterChain.doFilter(requestWrapper, responseWrapper);
  34. String requestUrl = requestWrapper.getRequestURL().toString();
  35. HttpHeaders requestHeaders = new HttpHeaders();
  36. Enumeration headerNames = requestWrapper.getHeaderNames();
  37. while (headerNames.hasMoreElements()) {
  38. String headerName = (String) headerNames.nextElement();
  39. requestHeaders.add(headerName, requestWrapper.getHeader(headerName));
  40. }
  41. HttpMethod httpMethod = HttpMethod.valueOf(requestWrapper.getMethod());
  42. Map<String, String[]> requestParams = requestWrapper.getParameterMap();
  43. String requestBody = IOUtils.toString(requestWrapper.getInputStream(),UTF_8);
  44. JsonNode requestJson = objectMapper.readTree(requestBody);
  45. RequestEntity<JsonNode> requestEntity = new RequestEntity<>(requestJson,requestHeaders, httpMethod, URI.create(requestUrl));
  46. LOGGER.info(appendFields(requestEntity),"Logging Http Request");
  47. HttpStatus responseStatus = HttpStatus.valueOf(responseWrapper.getStatusCode());
  48. HttpHeaders responseHeaders = new HttpHeaders();
  49. for (String headerName : responseWrapper.getHeaderNames()) {
  50. responseHeaders.add(headerName, responseWrapper.getHeader(headerName));
  51. }
  52. String responseBody = IOUtils.toString(responseWrapper.getContentInputStream(), UTF_8);
  53. JsonNode responseJson = objectMapper.readTree(responseBody);
  54. ResponseEntity<JsonNode> responseEntity = new ResponseEntity<>(responseJson,responseHeaders,responseStatus);
  55. LOGGER.info(appendFields(responseEntity),"Logging Http Response");
  56. responseWrapper.copyBodyToResponse();
  57. }
  58. }

字符串

展开查看全部
ut6juiuv

ut6juiuv3#

我喜欢使用的模式是将其分为2个过滤器:一个用于提取原始主体,另一个用于记录日志。感觉更SRP

  1. @Slf4j // Lombok logging
  2. @Component // Spring loads filter into its filter chain
  3. @Order(1) // Best if this goes first (or early in filter chain)
  4. public class CachingBodyFilter implements Filter {
  5. @Override
  6. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
  7. ContentCachingRequestWrapper reqWrapper = new ContentCachingRequestWrapper((HttpServletRequest) req);
  8. ContentCachingResponseWrapper resWrapper = new ContentCachingResponseWrapper((HttpServletResponse) res);
  9. try {
  10. chain.doFilter(reqWrapper, resWrapper);
  11. resWrapper.copyBodyToResponse(); // Necessary (see answer by StasKolodyuk above)
  12. } catch (IOException | ServletException e) {
  13. log.error("Error extracting body", e);
  14. }
  15. }
  16. }

字符串
然后我们创建另一个过滤器来完成日志记录部分:

  1. @Slf4j
  2. @Component
  3. @Order(2) // This needs to come after `CachingBodyFilter`
  4. public class PayloadLogFilter implements Filter {
  5. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
  6. chain.doFilter(req, res);
  7. if (req instanceof ContentCachingRequestWrapper) {
  8. ContentCachingRequestWrapper reqWrapper = (ContentCachingRequestWrapper) req;
  9. String payload = new String (reqWrapper.getContentAsByteArray(), "UTF-8");
  10. log.debug("Request [ {} ] has payload [ {} ]", reqWrapper.getRequestURI(), payload);
  11. }
  12. }
  13. }


将它们分开的一个好处是其他类(例如Spring AOP拦截器或Spring控制器)也可以访问/使用HTTP主体。

展开查看全部

相关问题