java.net.http.HttpClient POST请求服务端读取永久阻塞问题
问题根因
代码存在两处核心错误,最终导致客户端与服务端形成死锁:
- 客户端错误:手动设置
Transfer-Encoding: chunked破坏了HttpClient的默认行为
JDK 11+内置的java.net.http.HttpClient会自动管理传输层相关的协议头,你使用的是固定长度的字节数组作为请求体,客户端默认会自动添加Content-Length头使用定长传输。你手动强制指定chunked编码后,客户端不会自动生成chunked协议要求的0长度终止块,服务端会认为还有后续请求数据没发完。 - 服务端错误:读取逻辑完全不符合HTTP协议规范
你直接调用InputStream.readAllBytes()读取Socket流,这个方法的逻辑是一直读到输入流结束(也就是TCP连接被对端关闭)才会返回,根本不会按HTTP协议规则判断请求体是否读取完成:不管是定长传输读到Content-Length声明的字节数就停止,还是chunked传输读到终止块就停止,当前逻辑都会无限制等待连接关闭。而HTTP/1.1默认启用长连接,客户端发完请求后不会断开连接,会一直等待服务端返回响应,最终形成死锁:客户端等服务端回响应,服务端等客户端关连接,两边永久阻塞。
修复方案
客户端修改
直接删除手动设置Transfer-Encoding头的代码即可,所有传输编码相关的头交给HttpClient自动处理,不需要手动干预。修正后的请求构造代码如下:
final Builder builder = HttpRequest.newBuilder(); builder.uri(uri); builder.version(Version.HTTP_1_1); // 删除 builder.setHeader("Transfer-Encoding", "chunked"); 这行 builder.setHeader("Content-Type", "application/ipp"); builder.setHeader("Accept-Encoding", "gzip,deflate"); builder.POST(publisher);
如果确实需要使用chunked分块传输,不要手动设置该头,使用流式的BodyPublisher实现,客户端会自动启用chunked编码并生成符合规范的分块结构。
服务端修改
不要直接对原始Socket流调用readAllBytes(),需要严格按照HTTP/1.1协议规范解析请求:
- 先读取并解析请求行、请求头,拿到
Content-Length或Transfer-Encoding字段值 - 如果是定长传输,精确读取
Content-Length指定长度的字节作为请求体 - 如果是分块传输,按chunked协议规则逐块读取,直到碰到0长度的终止块
- 读完请求后立刻按照HTTP协议格式返回响应,再根据
Connection头决定是关闭连接还是保持连接等待下一个请求
如果只是临时测试想快速验证逻辑,可以给客户端请求加上Connection: close头,服务端读完请求、返回响应后主动关闭连接,但这只是权宜之计,正式场景必须按协议规范解析请求。
内容的提问来源于stack exchange,提问作者Dave The Dane
相关产品推荐
相关产品推荐

