java - 使用 spring 过滤器在 Spring Boot 中记录原始 HTTP 请求和响应
问题描述
我正在尝试使用过滤器(比如说LoggingFilter
)在 Spring Boot 中记录请求和响应,并尝试从请求正文中获取一些特定属性以登录 MDC。由于getInputStream
请求中的方法只能调用一次,因此需要使用请求和响应包装器。为了做到这一点,spring 分别提供了包装类ContentCachingRequestWrapper
和ContentCachingResponseWrapper
请求和响应。但是在使用ContentCachingRequestWrapper
in 过滤器时,请求数据仅在filterChain.doInternalFilter
调用 in 方法后记录LoggingFilter
。
为了在之前记录请求filterChain.doInternalFilter
,我通过对代码进行一些修改,制作了一个自定义的请求包装类,ContentCachingRequestWrapper
现在一切正常。但我想知道这样做是否有任何问题(性能影响、不需要的内存初始化、Java 堆内存在某些时候耗尽等),因为获得一些专家意见总是好的。:)
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.util.FastByteArrayOutputStream;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.net.URLEncoder;
import java.util.*;
/**
* this file is a copy of Spring's ContentCachingRequestWrapper with few modifications.
*/
@Slf4j
public class MDCContentCachingRequestWrapper extends HttpServletRequestWrapper {
private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";
private final ByteArrayOutputStream cachedContent;
private final Integer contentCacheLimit;
private ServletInputStream inputStream;
private BufferedReader reader;
private FastByteArrayOutputStream fastByteArrayOutputStream;
public String getPayload() {
// return payload;
return new String(fastByteArrayOutputStream.toByteArray());
}
private String payload;
public MDCContentCachingRequestWrapper(HttpServletRequest request) {
super(request);
int contentLength = request.getContentLength();
if (isFormPost()) {
this.cachedContent = new ByteArrayOutputStream(contentLength >= 0 ? contentLength : 1024);
} else {
this.cachedContent = new ByteArrayOutputStream();
}
this.contentCacheLimit = null;
try {
this.getParameterMap();
} catch (Exception e) {
log.error("Unable to get parameters map.");
}
// StringBuilder stringBuilder = new StringBuilder();
// try {
// byte[] buffer = new byte[1024];
// InputStream inputStream = request.getInputStream();
// int i = 0;
// while (-1 != (i = inputStream.read(buffer))) {
// stringBuilder.append(new String(buffer, 0, i));
// }
// inputStream.close();
// } catch (IOException e) {
// log.error("can't clone input stream: ", e);
//// throw new RuntimeException(e);
// }
// payload = stringBuilder.toString();
fastByteArrayOutputStream = new FastByteArrayOutputStream(contentLength >= 0 ? contentLength : 1024);
try {
byte[] buffer = new byte[1024];
InputStream inputStream = request.getInputStream();
int i = 0;
while (-1 != (i = inputStream.read(buffer))) {
fastByteArrayOutputStream.write(buffer, 0, i);
}
inputStream.close();
} catch (IOException e) {
log.error("can't clone input stream to byteOutputStream: ", e);
// throw new RuntimeException(e);
}
}
public MDCContentCachingRequestWrapper(HttpServletRequest request, int contentCacheLimit) {
super(request);
this.cachedContent = new ByteArrayOutputStream(contentCacheLimit);
this.contentCacheLimit = contentCacheLimit;
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (this.inputStream == null) {
// InputStream byteArrayInputStream = IOUtils.toInputStream(this.getPayload(), this.getCharacterEncoding());
InputStream byteArrayInputStream = fastByteArrayOutputStream.getInputStream();
this.inputStream = new ServletInputStream() {
boolean overflow = false;
public int read() throws IOException {
int ch = byteArrayInputStream.read();
if (ch != -1 && !overflow) {
if (contentCacheLimit != null && cachedContent.size() == contentCacheLimit) {
overflow = true;
handleContentOverflow(contentCacheLimit);
} else {
cachedContent.write(ch);
}
} else {
byteArrayInputStream.close();
}
return ch;
}
@Override
public boolean isFinished() {
try {
return byteArrayInputStream.available() == 0;
} catch (IOException e) {
return false;
}
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
log.info("setReadListener method called.");
}
};
}
return this.inputStream;
}
public String getCharacterEncoding() {
String enc = super.getCharacterEncoding();
return enc != null ? enc : "ISO-8859-1";
}
@Override
public BufferedReader getReader() throws IOException {
if (this.reader == null) {
this.reader = new BufferedReader(new InputStreamReader(this.getInputStream(), this.getCharacterEncoding()));
}
return this.reader;
}
public String getParameter(String name) {
if (this.cachedContent.size() == 0 && this.isFormPost()) {
this.writeRequestParametersToCachedContent();
}
return super.getParameter(name);
}
public Map<String, String[]> getParameterMap() {
if (this.cachedContent.size() == 0 && this.isFormPost()) {
this.writeRequestParametersToCachedContent();
}
return super.getParameterMap();
}
public Enumeration<String> getParameterNames() {
if (this.cachedContent.size() == 0 && this.isFormPost()) {
this.writeRequestParametersToCachedContent();
}
return super.getParameterNames();
}
public String[] getParameterValues(String name) {
if (this.cachedContent.size() == 0 && this.isFormPost()) {
this.writeRequestParametersToCachedContent();
}
return super.getParameterValues(name);
}
private boolean isFormPost() {
String contentType = this.getContentType();
return contentType != null && contentType.contains("application/x-www-form-urlencoded") && HttpMethod.POST.matches(this.getMethod());
}
private void writeRequestParametersToCachedContent() {
try {
if (this.cachedContent.size() == 0) {
String requestEncoding = this.getCharacterEncoding();
Map<String, String[]> form = super.getParameterMap();
Map<String, Object> resultMap = new HashMap<>();
Iterator nameIterator = form.keySet().iterator();
while (nameIterator.hasNext()) {
String name = (String) nameIterator.next();
List<String> values = Arrays.asList((String[]) form.get(name));
Iterator valueIterator = values.iterator();
while (valueIterator.hasNext()) {
String value = (String) valueIterator.next();
this.cachedContent.write(URLEncoder.encode(name, requestEncoding).getBytes());
if (value != null) {
this.cachedContent.write(61);
this.cachedContent.write(URLEncoder.encode(value, requestEncoding).getBytes());
if (valueIterator.hasNext()) {
this.cachedContent.write(38);
}
}
}
if (nameIterator.hasNext()) {
this.cachedContent.write(38);
}
}
}
} catch (IOException e) {
throw new IllegalStateException("Failed to write request parameters to cached content", e);
}
}
public byte[] getContentAsByteArray() {
return this.cachedContent.toByteArray();
}
protected void handleContentOverflow(int contentCacheLimit) {
}
}
另外我想知道使用哪种方法来复制 inputStream 数据:字符串方法或 usingFastByteArrayOutputStream
方法,或者我应该使用什么方法。
解决方案
推荐阅读
- python - 合并后删除不必要的列
- r - 用R中的最后一个非空值填充空行
- amazon-web-services - 获取自某个日期以来创建的 EC2 实例列表
- node.js - NodeJs JSON.parse - SyntaxError:JSON 中位置 0 处的意外标记 u
- python - Hwo 在 Python > 3.6 中从 Simulink 模型生成代码
- r - R Studio:如何将空白值替换为第二列的相应行值
- php - 多维数组减1的算法复杂度
- pandas - 熊猫数据框中满足条件的最小值
- php - Laravel 子域
- bash - Ubuntu 16 Gnome 终端 .bashrc 源问题