From eb7dfd4ed706ed6b72364e316c4278364a4d4af4 Mon Sep 17 00:00:00 2001 From: liquor <958142070@qq.com> Date: Mon, 9 Jun 2025 03:54:49 +0000 Subject: [PATCH] =?UTF-8?q?fix(core):=20=E4=BF=AE=E5=A4=8D=20application/x?= =?UTF-8?q?-www-form-urlencoded=20=E8=AF=B7=E6=B1=82=E4=BD=93=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=97=A0=E6=B3=95=E5=9C=A8=20Controller=20=E5=B1=82?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wrapper/RepeatReadRequestWrapper.java | 225 ++++++++++++++---- 1 file changed, 176 insertions(+), 49 deletions(-) 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