diff --git a/continew-starter-core/src/main/java/top/continew/starter/core/wrapper/RepeatReadRequestWrapper.java b/continew-starter-core/src/main/java/top/continew/starter/core/wrapper/RepeatReadRequestWrapper.java index 34b77c14..bc2180b8 100644 --- a/continew-starter-core/src/main/java/top/continew/starter/core/wrapper/RepeatReadRequestWrapper.java +++ b/continew-starter-core/src/main/java/top/continew/starter/core/wrapper/RepeatReadRequestWrapper.java @@ -16,89 +16,216 @@ package top.continew.starter.core.wrapper; -import cn.hutool.core.io.IoUtil; import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; +import jakarta.servlet.ServletRequest; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; +import org.springframework.http.MediaType; +import org.springframework.util.FastByteArrayOutputStream; +import org.springframework.util.StreamUtils; +import top.continew.starter.core.constant.StringConstants; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStreamReader; +import java.io.*; +import java.net.URLEncoder; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; /** - * 可重复读取请求体的包装器 - * 支持文件流直接透传,非文件流可重复读取 + * 可重复读取请求体的包装器 支持文件流直接透传,非文件流可重复读取 + *
+ * 虽然这里可以多次读取流里面的数据, 但是建议还是调用getContentAsString()/getCachedContent() 方法, 已经把内容缓存在内存中了。 + *
* - * @author echo - * @since 2.10.0 + * @author Jasmine + * @since 2.12.1 */ public class RepeatReadRequestWrapper extends HttpServletRequestWrapper { - private byte[] cachedBody; - private final HttpServletRequest originalRequest; + /** + * 缓存内容 + */ + private final FastByteArrayOutputStream cachedContent; + /*** 用于缓存输入流 */ + private ContentCachingInputStream contentCachingInputStream; + + /** + * 字符编码 + */ + private final String characterEncoding; + + // private BufferedReader reader; + + /** + * Constructs a request object wrapping the given request. + * + * @param request the {@link HttpServletRequest} to be wrapped. + * @throws IllegalArgumentException if the request is null + */ public RepeatReadRequestWrapper(HttpServletRequest request) throws IOException { super(request); - this.originalRequest = request; - + this.characterEncoding = request.getCharacterEncoding() != null + ? request.getCharacterEncoding() + : StandardCharsets.UTF_8.name(); + int contentLength = super.getRequest().getContentLength(); + cachedContent = (contentLength > 0) + ? new FastByteArrayOutputStream(contentLength) + : new FastByteArrayOutputStream(); // 判断是否为文件上传请求 if (!isMultipartContent(request)) { - this.cachedBody = IoUtil.readBytes(request.getInputStream(), false); + if (isFormRequest()) { + writeRequestParametersToCachedContent(); + } else { + StreamUtils.copy(request.getInputStream(), cachedContent); + } + contentCachingInputStream = new ContentCachingInputStream(cachedContent.toByteArray()); } } @Override public ServletInputStream getInputStream() throws IOException { // 如果是文件上传,直接返回原始输入流 - if (isMultipartContent(originalRequest)) { - return originalRequest.getInputStream(); + if (isMultipartContent(super.getRequest())) { + return super.getRequest().getInputStream(); + } + synchronized (this) { + contentCachingInputStream.reset(); + return contentCachingInputStream; } - - // 非文件上传,返回可重复读取的输入流 - final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody); - - return new ServletInputStream() { - @Override - public boolean isFinished() { - return byteArrayInputStream.available() == 0; - } - - @Override - public boolean isReady() { - return true; - } - - @Override - public void setReadListener(ReadListener readListener) { - // 非阻塞I/O,这里可以根据需要实现 - } - - @Override - public int read() { - return byteArrayInputStream.read(); - } - }; } @Override public BufferedReader getReader() throws IOException { // 如果是文件上传,直接返回原始Reader - if (isMultipartContent(originalRequest)) { - new BufferedReader(new InputStreamReader(originalRequest.getInputStream(), StandardCharsets.UTF_8)); + if (isMultipartContent(super.getRequest())) { + return super.getRequest().getReader(); } - return new BufferedReader(new InputStreamReader(getInputStream())); + + // BufferedReader不支持多次reset()(除非手动调用 mark() 并控制其生命周期),最安全的方式是每次调用getReader()时基于缓存内容重新创建一个新的BufferedReader实例。 + synchronized (this) { + return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding())); + } + } + + private void writeRequestParametersToCachedContent() { + try { + if (this.cachedContent.size() == 0) { + Map