⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 urlify.cn/fuE73u 「弗雷德辛」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

经常会遇到需要处理 http 请求以及请求正文的情况。

而这里比较大的一个问题是 servlet 的 requestBody 或 responseBody 流被读取了就无法二次了。

针对这个问题,Spring本身提供了解决方案,即:

  • 内容缓存请求包装器
  • ContentCachingResponseWrapper。

我们写了一个过滤器:

public abstract class HttpBodyRecorderFilter extends OncePerRequestFilter {

private static final int DEFAULT_MAX_PAYLOAD_LENGTH = 1024 * 512;

private int maxPayloadLength = DEFAULT_MAX_PAYLOAD_LENGTH;

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

boolean isFirstRequest = !isAsyncDispatch(request);

HttpServletRequest requestToUse = request;

if (isFirstRequest && !(request instanceof ContentCachingRequestWrapper)
&& (request.getMethod().equals(HttpMethod.PUT.name())
|| request.getMethod().equals(HttpMethod.POST.name()))) {
requestToUse = new ContentCachingRequestWrapper(request);
}

HttpServletResponse responseToUse = response;

if (!(response instanceof ContentCachingResponseWrapper) && (request.getMethod().equals(HttpMethod.PUT.name())
|| request.getMethod().equals(HttpMethod.POST.name()))) {
responseToUse = new ContentCachingResponseWrapper(response);
}

boolean hasException = false;

try {
filterChain.doFilter(requestToUse, responseToUse);
} catch (final Exception e) {
hasException = true;
throw e;
} finally {
int code = hasException ? 500 : response.getStatus();

if (!isAsyncStarted(requestToUse) && (this.codeMatched(code, AdvancedHunterConfigManager.recordCode()))) {
recordBody(createRequest(requestToUse), createResponse(responseToUse));
} else {
writeResponseBack(responseToUse);
}

}

}

protected String createRequest(HttpServletRequest request) {
String payload = "";

ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);

if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
payload = genPayload(payload, buf, wrapper.getCharacterEncoding());
}

return payload;
}

protected String createResponse(HttpServletResponse resp) {
String response = "";

ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(resp, ContentCachingResponseWrapper.class);

if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();

try {
wrapper.copyBodyToResponse();
} catch (IOException e) {
e.printStackTrace();
}

response = genPayload(response, buf, wrapper.getCharacterEncoding());
}

return response;

}

protected void writeResponseBack(HttpServletResponse resp) {
ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(resp, ContentCachingResponseWrapper.class);

if (wrapper != null) {
try {
wrapper.copyBodyToResponse();
} catch (IOException e) {
LOG.error("Fail to write response body back", e);
}
}

}

private String genPayload(String payload, byte[] buf, String characterEncoding) {

if (buf.length > 0 && buf.length < getMaxPayloadLength()) {
try {
payload = new String(buf, 0, buf.length, characterEncoding);
} catch (UnsupportedEncodingException ex) {
payload = "[unknown]";
}
}

return payload;

}

public int getMaxPayloadLength() {
return maxPayloadLength;
}

private boolean codeMatched(int responseStatus, String statusCode) {

if (statusCode.matches("^[0-9,]*$")) {
String[] filteredCode = statusCode.split(",");
return Stream.of(filteredCode).map(Integer::parseInt).collect(Collectors.toList()).contains(responseStatus);
} else {
return false;
}

}

protected abstract void recordBody(String payload, String response);

protected abstract String recordCode();

}

自定义一个过滤器继承HttpBodyRecorderFilter,这样路径记录Body方法就可以自定义自己的处理逻辑了。

另外,可以用于定义在请求记录中为多少的请求记录主体,可以定义为只有发生事故的记录主体,例如000或50时记录错误代码的方法。

过滤器的规则比较简单,如果想要像我们匹配springmvc那样进行匹配,可以使用:AntPathMatcher。

class PatternMappingFilterProxy implements Filter {

private final Filter delegate;

private final List<String> pathUrlPatterns = new ArrayList();

private PathMatcher pathMatcher;

public PatternMappingFilterProxy(Filter delegate, String... urlPatterns) {
Assert.notNull(delegate, "A delegate Filter is required");
this.delegate = delegate;
int length = urlPatterns.length;
pathMatcher = new AntPathMatcher();
for (int index = 0; index < length; ++index) {
String urlPattern = urlPatterns[index];
this.pathUrlPatterns.add(urlPattern);
}

}

@Override

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {

HttpServletRequest httpRequest = (HttpServletRequest) request;

String path = httpRequest.getRequestURI();

if (this.matches(path)) {
this.delegate.doFilter(request, response, filterChain);
} else {
filterChain.doFilter(request, response);
}

}

private boolean matches(String requestPath) {

for (String pattern : pathUrlPatterns) {
if (pathMatcher.match(pattern, requestPath)) {
return true;
}
}

return false;
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.delegate.init(filterConfig);
}

@Override

public void destroy() {
this.delegate.destroy();
}

public List<String> getPathUrlPatterns() {
return pathUrlPatterns;
}

public void setPathUrlPatterns(List<String> urlPatterns) {
pathUrlPatterns.clear();
pathUrlPatterns.addAll(urlPatterns);
}

}

子子,MappingProxyFilter 了真正的 HttporderFilter,PatternB 支持下面的 React 样式,真正像springmvc 那样的这样的匹配。例如接口:

@PostMapping("/test/{id}")
public Object test(@PathVariable(value = "id",required = true) final Integer index) {

//do something

}

可以设置urlPattern为/test/{id:[0-9]+}

文章目录