You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:31:17