Undertow中Handler读取后HttpServerExchange请求体为空的解决方法
解决Undertow中LoggingHandler读取请求体后后续Endpoint无法获取的问题
这个问题我之前也碰到过,核心原因很明确:Undertow的HttpServerExchange请求体本质是单向的InputStream流,默认只能读取一次。当你在LoggingHandler里把请求体读完之后,流的指针就走到了末尾,后续的TestEndpoint再尝试读取时,自然就拿不到内容了。
给你两个实用的解决方案,优先推荐第一种,省心又靠谱:
方案一:使用Undertow内置的RequestBufferingHandler
Undertow官方已经提供了专门处理请求体重复读取的Handler——RequestBufferingHandler,它会自动把请求体缓存到内存(或大请求时的临时文件),让后续所有Handler都能重复读取请求体,完全不需要修改你现有的LoggingHandler和TestEndpoint代码。
修改你的Server类代码即可:
package com.undertow.server; import com.undertow.server.endpoints.TestEndpoint; import io.undertow.Undertow; import io.undertow.server.handlers.RequestBufferingHandler; import io.undertow.server.handlers.logging.LoggingHandler; import io.undertow.server.handlers.logging.DefaultAccessLogReceiver; public class Server { public static void main(String[] args) { TestEndpoint testEndpoint = new TestEndpoint(); // 构建Handler链:先缓冲请求体,再执行日志逻辑,最后到业务端点 RequestBufferingHandler bufferingHandler = new RequestBufferingHandler( new LoggingHandler(new DefaultAccessLogReceiver()) ); Undertow server = Undertow.builder() .addHttpListener(8080, "localhost") .setHandler(bufferingHandler.setNext(testEndpoint)) .build(); server.start(); } }
只需要把RequestBufferingHandler放在Handler链的最前端,它会自动帮你处理请求体的缓存,后续的LoggingHandler和TestEndpoint都能正常读取请求体。
方案二:手动缓存请求体到Exchange的Attachment中
如果你不想用官方的缓冲Handler,也可以手动在LoggingHandler里把读取到的请求体缓存到Exchange的Attachment中,后续TestEndpoint直接从Attachment取数据,或者重置输入流让它能重新读取。
第一步:修改自定义LoggingHandler的逻辑
import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; import io.undertow.util.AttachmentKey; import java.nio.charset.StandardCharsets; public class CustomLoggingHandler implements HttpHandler { private final HttpHandler next; // 定义AttachmentKey用来存储缓存的请求体 private static final AttachmentKey<String> CACHED_REQUEST_BODY = AttachmentKey.create(String.class); public CustomLoggingHandler(HttpHandler next) { this.next = next; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if (exchange.getRequestContentLength() > 0) { // 读取请求体 byte[] bodyBytes = exchange.getInputStream().readAllBytes(); String requestBody = new String(bodyBytes, StandardCharsets.UTF_8); // 记录日志 System.out.println("Logging Request Body: " + requestBody); // 把请求体缓存到Exchange的Attachment中 exchange.putAttachment(CACHED_REQUEST_BODY, requestBody); // 重置输入流,让后续Handler可以重新读取 exchange.getRequestReceiver().receiveFullBytes((ex, bytes) -> { ex.startBlocking(); try { ex.getInputStream().reset(); } catch (Exception e) { e.printStackTrace(); } next.handleRequest(ex); }); return; } // 没有请求体时直接传递给下一个Handler next.handleRequest(exchange); } // 提供静态方法让其他Handler获取缓存的请求体 public static String getCachedRequestBody(HttpServerExchange exchange) { return exchange.getAttachment(CACHED_REQUEST_BODY); } }
第二步:在TestEndpoint中获取缓存的请求体
import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; public class TestEndpoint implements HttpHandler { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { // 直接从Attachment中获取缓存的请求体 String requestBody = CustomLoggingHandler.getCachedRequestBody(exchange); // 或者如果重置了输入流,也可以继续用原方式读取 // String requestBody = new String(exchange.getInputStream().readAllBytes(), StandardCharsets.UTF_8); // 处理你的业务逻辑 exchange.getResponseSender().send("TestEndpoint Received Body: " + requestBody); } }
这种方式灵活性更高,但需要自己处理流的重置和缓存逻辑,代码量也更大,适合有特殊定制需求的场景。
总的来说,优先用方案一,官方封装的组件已经考虑了各种边界情况(比如大请求的临时文件处理),不用自己造轮子。
内容的提问来源于stack exchange,提问作者FDB
相关产品推荐
相关产品推荐

