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 會沒有資料輸出。
參考資料:
沒有留言 :
張貼留言