title: Request Response 重復讀取
date: 2020-03-16 22:43:21
tags:
- http
categories: - 編程
背景
通常為了方便定位問題,我們需要記錄接口的入?yún)⒑统鰠ⅰ5捎?stream 不可重復讀的特性丈秩,會導致無法預期的各種問題拔莱。
Wrapper
作為 request动看、response 的包裝類菱皆,我們可以通過重寫 getInputStream 和 getOutputStream 控制數(shù)據(jù)的流轉(zhuǎn),從而達到數(shù)據(jù)的可重復讀取挨稿。
HttpServletRequestWrapper
package cn.caojiantao.spider;
import org.springframework.util.StreamUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
* @author caojiantao
*/
public class RequestWrapper extends HttpServletRequestWrapper {
private byte[] data;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
data = StreamUtils.copyToByteArray(request.getInputStream());
}
@Override
public ServletInputStream getInputStream() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
public byte[] toByteArray() throws IOException {
return data;
}
}
HttpServletResponseWrapper
package cn.caojiantao.spider;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* @author caojiantao
*/
public class ResponseWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream byteArrayOutputStream;
private ServletOutputStream servletOutputStream;
public ResponseWrapper(HttpServletResponse response) {
super(response);
byteArrayOutputStream = new ByteArrayOutputStream();
servletOutputStream = new ServletOutputStream() {
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
@Override
public void write(int b) throws IOException {
response.getOutputStream().write(b);
// 同時寫入字節(jié)數(shù)組
byteArrayOutputStream.write(b);
}
};
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return servletOutputStream;
}
public byte[] toByteArray() {
return byteArrayOutputStream.toByteArray();
}
}
response.getOutputStream() 和 response.getWriter() 互斥,不能同時使用奶甘。
實例 - 日志過濾器
package cn.caojiantao.spider.configuration;
import cn.caojiantao.spider.RequestWrapper;
import cn.caojiantao.spider.ResponseWrapper;
import cn.caojiantao.spider.util.LogContext;
import cn.caojiantao.spider.util.NetUtils;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
* @author caojiantao
*/
@Slf4j
@WebFilter(urlPatterns = {"/*"})
public class SpiderFilter implements Filter {
private List<String> excludePathList = Arrays.asList("/", "/favicon.ico", "/index.html", "/css/*", "/js/*");
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (excludePathList.contains(request.getRequestURI())) {
filterChain.doFilter(request, response);
return;
}
// 追蹤日志
LogContext.setTraceId();
// 包裝流,可重復讀取
RequestWrapper requestWrapper = new RequestWrapper(request);
ResponseWrapper responseWrapper = new ResponseWrapper(response);
// 請求參數(shù)
String traceId = LogContext.getTraceId();
String method = request.getMethod();
String uri = request.getRequestURI();
String data = new String(requestWrapper.toByteArray());
String query = request.getQueryString();
String ip = NetUtils.getIpAddress(request);
log.info("request traceId:{} method:{} uri:{} data:{} query:{} ip:{}", traceId, method, uri, data, query, ip);
long t = System.currentTimeMillis();
filterChain.doFilter(requestWrapper, responseWrapper);
// 響應(yīng)參數(shù)
String resp = new String(responseWrapper.toByteArray());
long cost = System.currentTimeMillis() - t;
log.info("response traceId:{} method:{} uri:{} data:{} query:{} ip:{} response:{} cost:{}", traceId, method, uri, data, query, ip, resp, cost);
LogContext.clear();
}
}
這里 LogContext 為日志跟蹤 traceId 管理讳苦,通過 ThreadLocal 來實現(xiàn)逮京,方便問題定位懒棉。
package cn.caojiantao.spider.util;
import java.util.UUID;
/**
* @author caojiantao
*/
public class LogContext {
private static ThreadLocal<String> traceIdLocal = new ThreadLocal<>();
public static void setTraceId() {
String traceId = UUID.randomUUID().toString().replaceAll("-", "");
setTraceId(traceId);
}
public static void setTraceId(String traceId) {
traceIdLocal.set(traceId);
}
public static String getTraceId() {
return traceIdLocal.get();
}
public static void clear() {
traceIdLocal.remove();
}
}