package cn.com.xuxiaowei.filter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.util.ContentCachingRequestWrapper;
// Spring Boot 2 环境
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// Spring Boot 3 环境
//import jakarta.servlet.FilterChain;
//import jakarta.servlet.ServletException;
//import jakarta.servlet.http.HttpFilter;
//import jakarta.servlet.http.HttpServletRequest;
//import jakarta.servlet.http.HttpServletResponse;
/**
* 缓存请求体,将 {@link HttpServletRequest} 强制类型转换为 {@link ContentCachingRequestWrapper},
* 使用 {@link ContentCachingRequestWrapper#getContentAsByteArray()}、{@link ContentCachingRequestWrapper#getContentAsString()} 获取请求体。
* <p>
* 1. {@link RequestBody} 与 {@link ContentCachingRequestWrapper#getContentAsByteArray()}、{@link ContentCachingRequestWrapper#getContentAsString()} 相互不影响。
* <p>
* 2. 在 Spring Boot 2 中,只有 {@link ContentCachingRequestWrapper#getContentAsByteArray()},无 {@link ContentCachingRequestWrapper#getContentAsString()}
*
* @author xuxiaowei
* @since 0.0.1
*/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ContentCachingRequestBodyFilter extends HttpFilter {
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
ContentCachingRequestWrapper contentCachingRequestWrapper = new ContentCachingRequestWrapper(request);
super.doFilter(contentCachingRequestWrapper, response, chain);
}
}
package cloud.xuxiaowei.filter;
import cloud.xuxiaowei.utils.servlet.InputStreamHttpServletRequestWrapper;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
// Spring Boot 2 环境
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
// Spring Boot 3 环境
//import jakarta.servlet.FilterChain;
//import jakarta.servlet.ServletException;
//import jakarta.servlet.http.HttpFilter;
//import jakarta.servlet.http.HttpServletRequest;
//import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 重写请求流
*
* @author xuxiaowei
* @since 0.0.1
*/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HttpServletRequestInputStreamFilter extends HttpFilter {
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
InputStreamHttpServletRequestWrapper httpServletRequestWrapper = new InputStreamHttpServletRequestWrapper(request);
super.doFilter(httpServletRequestWrapper, response, chain);
}
}
package cloud.xuxiaowei.utils.servlet;
// 使用 org.apache.tomcat.embed:tomcat-embed-core 依赖
import org.apache.tomcat.util.http.fileupload.IOUtils;
// 使用 commons-io:commons-io 依赖
//import org.apache.commons.io.IOUtils;
// Spring Boot 2 环境
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
// Spring Boot 3 环境
//import jakarta.servlet.ReadListener;
//import jakarta.servlet.ServletInputStream;
//import jakarta.servlet.http.HttpServletRequest;
//import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.*;
/**
* 重写请求流,请求流支持多次获取 {@link HttpServletRequest}
*
* @author xuxiaowei
* @see <a href=
* "https://stackoverflow.com/questions/10210645/http-servlet-request-lose-params-from-post-body-after-read-it-once/17129256#17129256">http-servlet-request-lose-params-from-post-body-after-read-it-once</a>
* @since 0.0.1
*/
public class InputStreamHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* 用于缓存输入流
*/
private ByteArrayOutputStream cachedBytes;
public InputStreamHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null) {
// 首次获取流时,将流放入 缓存输入流 中
cacheInputStream();
}
// 从 缓存输入流 中获取流并返回
return new CachedServletInputStream(cachedBytes.toByteArray());
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
/**
* 首次获取流时,将流放入 缓存输入流 中
*/
private void cacheInputStream() throws IOException {
// 缓存输入流以便多次读取。为了方便, 我使用 org.apache.commons IOUtils
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}
/**
* 读取缓存的请求正文的输入流
* <p>
* 用于根据 缓存输入流 创建一个可返回的 {@link ServletInputStream}
*/
public static class CachedServletInputStream extends ServletInputStream {
private final ByteArrayInputStream input;
public CachedServletInputStream(byte[] buf) {
// 从缓存的请求正文创建一个新的输入流
input = new ByteArrayInputStream(buf);
}
@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 input.read();
}
}
}