|
@@ -8,22 +8,44 @@ import org.springframework.web.util.ContentCachingRequestWrapper;
|
|
|
import org.springframework.web.util.ContentCachingResponseWrapper;
|
|
import org.springframework.web.util.ContentCachingResponseWrapper;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
import java.io.IOException;
|
|
|
|
|
+import java.util.Set;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 包装 request/response 以支持重复读取 body,
|
|
* 包装 request/response 以支持重复读取 body,
|
|
|
* 并记录请求开始时间供 OperationLogInterceptor 使用
|
|
* 并记录请求开始时间供 OperationLogInterceptor 使用
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>★ 只包装 {@link #WRAPPED_METHODS} —— 那是 {@code OperationLogInterceptor} 唯一会去读 body
|
|
|
|
|
+ * 的方法集合(见其 {@code RECORD_METHODS})。收窄的原因不是省内存,而是
|
|
|
|
|
+ * {@link ContentCachingResponseWrapper} 与**异步/流式响应不兼容**(6.1.6 字节码实读):
|
|
|
|
|
+ * 它的响应流 {@code write} 只写进内存 byte 数组、{@code flushBuffer()} 是空实现,
|
|
|
|
|
+ * 唯一回写真实响应的时机是本 filter 退出时的 {@code copyBodyToResponse()} —— 而异步响应的帧
|
|
|
|
|
+ * 全部产生于那一刻之后,等于永远发不出去;同时那次回写会
|
|
|
|
|
+ * {@code setContentLength(缓存字节数)},把 Content-Length 钉死在拷贝瞬间的长度上。
|
|
|
|
|
+ * 2026-10-05 实测:SSE 首帧 {@code retry:3000}(12 字节)⇒ 响应头 {@code content-length: 12},
|
|
|
|
|
+ * 客户端读满即 EOF,连接 233ms 就结束,心跳与广播帧全部丢失。GET 上还挂着文件下载
|
|
|
|
|
+ * (整包先进内存)。**不要**把 GET 加回本集合:SSE 端点是 GET。
|
|
|
*/
|
|
*/
|
|
|
@Component
|
|
@Component
|
|
|
public class RequestCachingFilter implements Filter {
|
|
public class RequestCachingFilter implements Filter {
|
|
|
|
|
|
|
|
|
|
+ /** 与 {@code OperationLogInterceptor.RECORD_METHODS} 一致:只有这些方法的 body 会被读。 */
|
|
|
|
|
+ private static final Set<String> WRAPPED_METHODS =
|
|
|
|
|
+ Set.of("POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS");
|
|
|
|
|
+
|
|
|
@Override
|
|
@Override
|
|
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
|
|
throws IOException, ServletException {
|
|
throws IOException, ServletException {
|
|
|
- ContentCachingRequestWrapper reqWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
|
|
|
|
|
- ContentCachingResponseWrapper respWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);
|
|
|
|
|
-
|
|
|
|
|
|
|
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
|
|
|
request.setAttribute("requestStartTime", System.currentTimeMillis());
|
|
request.setAttribute("requestStartTime", System.currentTimeMillis());
|
|
|
|
|
|
|
|
|
|
+ if (!WRAPPED_METHODS.contains(httpRequest.getMethod())) {
|
|
|
|
|
+ chain.doFilter(request, response);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ContentCachingRequestWrapper reqWrapper = new ContentCachingRequestWrapper(httpRequest);
|
|
|
|
|
+ ContentCachingResponseWrapper respWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
chain.doFilter(reqWrapper, respWrapper);
|
|
chain.doFilter(reqWrapper, respWrapper);
|
|
|
} finally {
|
|
} finally {
|