JDK11 HttpClient无法读取完整响应体字符串的问题求助
问题描述
使用JDK11 HttpClient向Netty实现的服务器发送HTTP请求时,只能获取到部分响应体(服务器返回加密后的十六进制字符串),但用OkHttpClient可以拿到完整内容。
服务器响应完整示例:
dfb3132c37f32cb93c78760749a6d9b0024e1 b0c5aa1dcd0bb1f02d611b72f0f5a073059475e659dd7aca7aad48e50f151e0ac215ab35eb8e041e569d779bd1a359007e0269dce3f3968ae070514d5ffd1591dc8b1b3e643c72888c9f95f9021071c3d8b7c8e540cc54df4762cb6a2086facac86e17cf023879936f725dfda5c60d97097785e2a62885cf6342a0d616ad632d7769518cc68720dd1d4bc9e20560e831a3eb6140747ae1f35e7fa136303b0df2fd7820fb2d1d1e467
JDK11 HttpClient获取到的内容:
dfb3132c37f32cb93c78760749a6d9b0024e1b0c5aa1dcd0bb1f02d611b72f0f5a073059475e659dd7aca7aad48e50f151e0ac215ab35eb8e041e569d779bd1a359007e0269dce3f3968ae070514d5f
原因分析
从服务器Netty处理器代码可知,当响应内容超过splitThreshold时,会将响应拆分为初始HttpResponse+多个HttpContent分块+LastHttpContent结尾的形式发送,但存在关键问题:
- 拆分响应时,未给初始响应设置
Transfer-Encoding: chunked头,也未移除Content-Length头(如果存在)。
HTTP协议规定,分块传输必须通过Transfer-Encoding: chunked头告知客户端,否则客户端会错误认为响应已结束(比如根据错误的Content-Length值截断)。OkHttpClient对这种不符合规范的分块响应有容错处理,而JDK11 HttpClient严格按协议处理,导致仅读取第一部分内容。
解决方案
方案1:修复服务器端代码(推荐)
在分块传输时,正确设置分块传输头并移除Content-Length头,修改createPart方法:
private HttpResponse createPart(FullHttpResponse rep) { DefaultHttpResponse part = new DefaultHttpResponse(rep.protocolVersion(), rep.status(), rep.headers()); // 设置分块传输编码 part.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED); // 移除Content-Length,分块传输不能同时存在这两个头 part.headers().remove(HttpHeaderNames.CONTENT_LENGTH); return part; }
也可直接在encode方法的分块分支中处理:
else { HttpResponse part = createPart(rep); part.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED); part.headers().remove(HttpHeaderNames.CONTENT_LENGTH); out.add(part); ByteBuf content = rep.content(); while (content.readableBytes() > splitThreshold) { out.add(new DefaultHttpContent(content.readRetainedSlice(splitThreshold))); } out.add(new DefaultLastHttpContent(content.readRetainedSlice(content.readableBytes()))); }
方案2:客户端侧临时适配(无法修改服务器时)
使用JDK HttpClient的流式处理方式,手动读取完整响应流,替代BodyHandlers.ofString():
HttpClient httpClient = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_1_1) .connectTimeout(Duration.ofSeconds(1)) .build(); HttpRequest request = HttpRequest.newBuilder() .uri(new URI("xxx")) .POST(HttpRequest.BodyPublishers.ofString(encryptBody)) .headers(xxx) .build(); // 使用InputStream读取完整响应 HttpResponse<InputStream> response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); try (InputStream inputStream = response.body()) { String fullBody = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); System.out.println("body : " + fullBody); } catch (IOException e) { e.printStackTrace(); }
流式读取会直接读取所有到达的数据直到流关闭,不受响应头的影响。
内容的提问来源于stack exchange,提问作者shanfeng
相关产品推荐
相关产品推荐

