JavaEE 中,如果 HttpServletResponse 的內容一經使用 OutputStream
等方式被讀取的話,
因為資料流只能被讀取一次,
會讓之後在輸入內容到前端時發生不預期的錯誤,
所以不能只接讀取,
如果要獲得
HttpServletRequest, HttpServletResponse 的內容的話,例如拿來做 log
紀錄等用途時會需要,
這時可以使用 Spring 的
ContentCachingRequestWrapper
和
ContentCachingResponseWrapper
這兩個工具來幫忙,
以下用一個
Filter 實作來做範例:
package xxx.api.filter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
@WebFilter(filterName = "apiFilter", urlPatterns = "/xxx/xxx.do")
public class ApiFilter extends OncePerRequestFilter {
private Logger logger = LoggerFactory.getLogger("xxxLogger");
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Instant tic = Instant.now();
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
filterChain.doFilter(requestWrapper, responseWrapper); //不用原來的 Request 和 Response,改把用 Wrapper 封裝的 Request 和 Response 放到 filterChain 中繼續傳遞
long durationMillis = Duration.between(tic, Instant.now()).toMillis();
String requestBody = new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
byte[] responseByteArr = responseWrapper.getContentAsByteArray();
String responseStr = new String(responseByteArr, StandardCharsets.UTF_8);
String loggerMsg = requestWrapper.getRemoteAddr() +
"|" + requestWrapper.getMethod() +
"|" + requestWrapper.getRequestURI() +
"|" + StringUtils.defaultIfBlank(requestWrapper.getQueryString(), "") +
"|" + responseWrapper.getStatus() +
"|" + durationMillis +
"|" + requestBody +
"|" + responseStr +
"|" + responseByteArr.length;
logger.info(loggerMsg);
responseWrapper.copyBodyToResponse(); //要記得執行這段,不然 HttpServletResponse 會沒有資料輸出
}
}
說明:
ContentCachingRequestWrapper 和 ContentCachingResponseWrapper
會把
HttpServletRequest 和 HttpServletResponse 包起來,內部用了
ByteArrayInputStream 和
ByteArrayOutputStream 等取得 HttpServletRequest
和 HttpServletResponse 的內容,
可以重覆取得而不用怕影響到
HttpServletRequest 和 HttpservletResponse 的輸入、輸出流。
這邊要注意的是,我們在 Filter 的最後要記得呼叫
responseWrapper.copyBodyToResponse()
不然 HttpServletResponse 會沒有資料輸出。
參考資料:
沒有留言 :
張貼留言