ContentCachingResponseWrapper가 빈 응답을 생성함
로깅 요청 및 응답에 대한 필터를 구현하려고 합니다.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:
$ curl http://localhost:9015/resources/1 --verbose
*   Trying ::1...
* Connected to localhost (::1) port 9015 (#0)
> GET /resources/1 HTTP/1.1
> User-Agent: curl/7.41.0
> Host: localhost:9015
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sat, 08 Oct 2016 17:10:11 GMT
< Content-Type: application/json;charset=UTF-8
< Content-Length: 0
<
* Connection #0 to host localhost left intact
좋은 생각 있어요?
감사해요.
몇 시간 동안 고군분투한 끝에 드디어 해결책을 찾았어요.
요컨대,ContentCachingResponseWrapper.copyBodyToResponse()필터 메서드의 마지막에 호출됩니다.
ContentCachingResponseWrapper는 응답 출력 스트림에서 응답 본문을 읽어냄으로써 응답 본문을 캐시합니다.그래서 개울이 텅 비게 됩니다.출력 스트림에 응답을 다시 쓰려면ContentCachingResponseWrapper.copyBodyToResponse()사용해야 합니다.
드디어 문제를 해결했다.다음은 완벽한 솔루션입니다.
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개의 필터로 분할하는 것을 좋아합니다.하나는 Raw Body 추출용이고, 다른 하나는 Logging을 하기 위한 것으로, 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 본문에 액세스/사용할 수 있다는 것입니다.
언급URL : https://stackoverflow.com/questions/39935190/contentcachingresponsewrapper-produces-empty-response
'programing' 카테고리의 다른 글
| 리액트 메모는 언제 사용하면 안 되나요? (0) | 2023.03.28 | 
|---|---|
| 릴레이에서 노드인터페이스와 글로벌 ID 사양은 어떤 역할을 합니까? (0) | 2023.03.28 | 
| Node.js/Express의 모든 요청을 로깅하고 있습니다. (0) | 2023.03.28 | 
| 화살표 본문을 둘러싼 예기치 않은 블록 문 (0) | 2023.03.28 | 
| useState 훅 세터가 상태를 잘못 덮어씁니다. (0) | 2023.03.28 |