ContentCachingResponseWrapper生成空响应

时间:2016-10-08 17:20:04

标签: spring-mvc servlets spring-boot servlet-filters

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

Spring MVC

所以,我得到了我的请求并按预期记录了。这是日志:

@Component
public class LoggingFilter extends OncePerRequestFilter {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoggingFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

        LOGGER.debug(REQUEST_MESSAGE_FORMAT, requestWrapper.getRequestURI(), requestWrapper.getMethod(), requestWrapper.getContentType(),
                new ServletServerHttpRequest(requestWrapper).getHeaders(), IOUtils.toString(requestWrapper.getInputStream(), UTF_8));

        filterChain.doFilter(requestWrapper, responseWrapper);

        LOGGER.debug(RESPONSE_MESSAGE_FORMAT, responseWrapper.getStatus(), responseWrapper.getContentType(),
                new ServletServerHttpResponse(responseWrapper).getHeaders(), IOUtils.toString(responseWrapper.getContentInputStream(), UTF_8));
    }
}

但是,返回空响应。这是2016-10-08 19:10:11.212 DEBUG 11072 --- [qtp108982313-19] by.kolodyuk.logging.LoggingFilter ---------------------------- ID: 1 URI: /resources/1 Http-Method: GET Content-Type: null Headers: {User-Agent=[curl/7.41.0], Accept=[*/*], Host=[localhost:9015]} Body: -------------------------------------- 2016-10-08 19:10:11.277 DEBUG 11072 --- [qtp108982313-19] by.kolodyuk.logging.LoggingFilter ---------------------------- ID: 1 Response-Code: 200 Content-Type: application/json;charset=UTF-8 Headers: {} Body: {"id":"1"} -------------------------------------- 的输出:

curl

有什么想法吗?

由于

3 个答案:

答案 0 :(得分:35)

经过几个小时的挣扎,我终于找到了解决方案。

简而言之,应该在过滤方法的末尾调用ContentCachingResponseWrapper.copyBodyToResponse()

ContentCachingResponseWrapper通过从响应输出流中读取响应主体来缓存响应主体。因此,流变空。要将响应写回输出流ContentCachingResponseWrapper.copyBodyToResponse(),应该使用。

答案 1 :(得分:3)

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

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.Enumeration;
import java.util.Map;

import static java.nio.charset.StandardCharsets.UTF_8;
import static net.logstash.logback.marker.Markers.appendFields;

@Component
public class LoggingFilter extends OncePerRequestFilter {

  private static final Logger LOGGER = LoggerFactory.getLogger(LoggingFilter.class);

  @Autowired
  private ObjectMapper objectMapper;

  @Override
  protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {

    ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(httpServletRequest);
    ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(httpServletResponse);

    filterChain.doFilter(requestWrapper, responseWrapper);

    String requestUrl = requestWrapper.getRequestURL().toString();
    HttpHeaders requestHeaders = new HttpHeaders();
    Enumeration headerNames = requestWrapper.getHeaderNames();
    while (headerNames.hasMoreElements()) {
      String headerName = (String) headerNames.nextElement();
      requestHeaders.add(headerName, requestWrapper.getHeader(headerName));
    }
    HttpMethod httpMethod = HttpMethod.valueOf(requestWrapper.getMethod());
    Map<String, String[]> requestParams = requestWrapper.getParameterMap();

    String requestBody = IOUtils.toString(requestWrapper.getInputStream(),UTF_8);
    JsonNode requestJson = objectMapper.readTree(requestBody);

    RequestEntity<JsonNode> requestEntity = new RequestEntity<>(requestJson,requestHeaders, httpMethod, URI.create(requestUrl));
    LOGGER.info(appendFields(requestEntity),"Logging Http Request");


    HttpStatus responseStatus = HttpStatus.valueOf(responseWrapper.getStatusCode());
    HttpHeaders responseHeaders = new HttpHeaders();
    for (String headerName : responseWrapper.getHeaderNames()) {
      responseHeaders.add(headerName, responseWrapper.getHeader(headerName));
    }
    String responseBody = IOUtils.toString(responseWrapper.getContentInputStream(), UTF_8);
    JsonNode responseJson = objectMapper.readTree(responseBody);
    ResponseEntity<JsonNode> responseEntity = new ResponseEntity<>(responseJson,responseHeaders,responseStatus);
    LOGGER.info(appendFields(responseEntity),"Logging Http Response");
    responseWrapper.copyBodyToResponse();
  }
}

答案 2 :(得分:0)

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

@Slf4j        // lombok logging
@Component    // spring loads filter into it's filter chain
@Order(1)     // Best if this goes first (or early in filter chain)
public class CachingBodyFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
        ContentCachingRequestWrapper reqWrapper = new ContentCachingRequestWrapper((HttpServletRequest) req);
        ContentCachingResponseWrapper resWrapper = new ContentCachingResponseWrapper((HttpServletResponse) res);
        try {
            chain.doFilter(reqWrapper, resWrapper);
            resWrapper.copyBodyToResponse();  // Necessary (see answer by StasKolodyuk above)
        } catch (IOException | ServletException e) {
            log.error("Error extracting body", e);
        }
    }

}

然后我们创建另一个过滤器来执行日志记录部分。

@Slf4j
@Component
@Order(2)     // This needs to come after `CachingBodyFilter`
public class PayloadLogFilter implements Filter {

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

        if (req instanceof ContentCachingRequestWrapper) {
            ContentCachingRequestWrapper reqWrapper = (ContentCachingRequestWrapper) req;
            String payload = new String (reqWrapper.getContentAsByteArray(), "utf-8");
            log.debug("Request [ {} ] has payload [ {} ]", reqWrapper.getRequestURI(), payload);
        }
    }

}

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