JDK内置的HttpRequest有坑,请绕道!
大家好,我是猿java。
最近,使用了 Java 11内置的java.net.http.HttpRequest
请求外部服务,发现日志中出现了很多如下图的错误:
这篇文章,我们就来分析如何排查和解决这种错误,以及分析下HttpRequest
的工作原理。
排查过程:
遇到这种问题,首先google搜索下关键字:java.io.IOException: HTTP/1.1 header parser received no bytes
总结下 Google查询的结果,可以得到两个主要原因:
- 服务器返回空响应,导致解析 response异常
- 网络问题
针对第一种情况,到下游服务查看日志发现请求根本没有进来,于是把原因定位到网络问题。经过多次的测试后发现,错误是有规律性的出现,多年工作经验的直觉告诉我,这种http请求,一定会复用连接,会不会复用了一个失效的链接,于是把问题再次缩小。
那么,JDK内置的HttpRequest
链接存活的时间是多久呢?
对,找官方资料,如下链接和图片:
官方默认的keepalive
是1200s,是不是太大了,于是调整了 keepalive的时间,修改参数的方式:
1 | 方法1. 启动指令中增加如下参数 |
很奇怪,为什么JDK没有提供变量来设置这个参数,而是作为JVM 系统属性设置???不管怎样,经过一番验证之后,问题解决。
所以,如果有使用 JDK内置HttpRequest
的小伙伴,一定要注意这个坑。
既然讲到了HttpRequest
,不如顺道把它的工作原理也分析下。
1. JDK 内置 HttpRequest 的实现原理
1.1 基础架构
JDK 内置的 HTTP 客户端基于异步非阻塞 I/O(NIO)设计,采用了事件驱动的架构。这种设计使其能够高效地处理大量并发连接,同时保持较低的资源消耗。HttpClient
是核心类,负责创建和配置 HTTP 请求,而 HttpRequest
则用于定义具体的请求细节。
1.2 异步与同步请求
HttpClient
支持同步和异步两种请求方式:
同步请求:调用
send
方法,线程会被阻塞直到服务器响应返回。这种方式适用于简单的请求场景,但在高并发环境下可能导致线程阻塞问题。异步请求:调用
sendAsync
方法,返回一个CompletableFuture
对象,允许在请求进行时执行其他操作,提升应用的响应性和吞吐量。
1.3 支持的协议
内置 HTTP 客户端支持 HTTP/1.1 和 HTTP/2 协议。HTTP/2 的引入带来了多路复用、头部压缩和服务器推送等特性,显著提升了传输效率。客户端会根据服务器支持的协议自动选择最优协议,确保最佳的传输性能。
1.4 连接管理
HttpClient
内部维护着连接池,自动管理 HTTP 连接的复用和关闭。通过连接池机制,可以避免频繁建立和关闭连接带来的性能损耗。连接池根据请求的目标主机和协议进行分类管理,确保高效的资源利用。
1.5 安全与认证
内置客户端提供丰富的安全特性,包括 SSL/TLS 支持、证书验证和多种认证机制(如 Basic、Digest、Bearer 认证等)。开发者可以通过配置 SSLContext
和相关认证信息,确保请求的安全性。
1.6 中间件与过滤器
HttpClient
允许开发者添加自定义的过滤器和拦截器,对请求和响应进行预处理和后处理。这为实现日志记录、请求重试、错误处理等功能提供了灵活的扩展点。
2. 优缺点
2.1 优点
简化的 API:相比于传统的
HttpURLConnection
,HttpClient
提供了更现代化和简洁的 API,降低了使用难度和代码复杂度。异步支持:内置的异步请求机制允许更高效地处理并发请求,提升了应用的性能和响应性。
协议支持:自动支持 HTTP/2,使得应用能够利用更高效的传输协议,无需额外配置。
内置安全特性:丰富的安全配置选项让开发者能够轻松地实现安全的网络通信,包括 SSL/TLS 和多种认证方式。
连接池管理:自动的连接池管理减少了资源管理的负担,提升了连接的复用性和整体性能。
跨平台一致性:作为 JDK 的一部分,
HttpClient
在不同操作系统和环境下表现一致,减少了跨平台开发的难度。
2.2 缺点
功能限制:虽然
HttpClient
覆盖了大多数常见的 HTTP 功能,但在某些高级用例下,可能缺乏第三方库(如 Apache HttpClient 或 OkHttp)提供的特定功能。版本依赖:
HttpClient
是从 Java 11 开始引入的,对于使用更早版本 JDK 的项目,需要依赖外部库来实现相似功能。社区和生态:相比于成熟的第三方 HTTP 客户端,JDK 内置的
HttpClient
在社区支持和生态上仍有待发展,可能缺乏某些特定场景下的最佳实践和解决方案。性能优化:尽管
HttpClient
已经具备良好的性能,但在极端高并发或特定优化需求下,可能无法完全满足专业级别的性能调优需求。
3. 核心参数
在使用 HttpRequest
时,开发者需要配置多个参数以定义请求的行为和特性。以下是一些核心参数及其说明:
3.1 请求 URI
每个 HTTP 请求都需要一个目标 URI,指定资源的位置。例如:
1 | URI uri = URI.create("https://api.example.com/data"); |
3.2 HTTP 方法
HttpRequest
支持常见的 HTTP 方法,如 GET、POST、PUT、DELETE 等。可以通过 method
方法或专门的快捷方法设置:
1 | // 使用快捷方法设置 GET 请求 |
3.3 请求头
可以通过 headers
方法添加一个或多个请求头,或使用 header
方法逐个添加:
1 | HttpRequest request = HttpRequest.newBuilder() |
3.4 请求体
对于需要发送数据的请求(如 POST、PUT),需要配置请求体。HttpRequest.BodyPublisher
提供多种数据发布方式:
1 | HttpRequest postRequest = HttpRequest.newBuilder() |
支持的 BodyPublisher
包括:
ofString(String)
: 发送字符串数据ofFile(Path)
: 发送文件内容ofByteArray(byte[])
: 发送字节数组noBody()
: 无请求体(适用于 GET 请求)
3.5 超时设置
可以为请求设置超时时间,防止请求长时间挂起:
1 | HttpRequest request = HttpRequest.newBuilder() |
3.6 重定向策略
通过 HttpClient
的构建器可以设置重定向的策略,如跟随重定向、禁止重定向等:
1 | HttpClient client = HttpClient.newBuilder() |
3.7 优先级
可以为请求设置优先级,影响请求的调度顺序:
1 | HttpRequest request = HttpRequest.newBuilder() |
优先级值越高,表示请求越重要。
3.8 版本协议
可以指定使用的 HTTP 版本,如 HTTP/1.1 或 HTTP/2:
1 | HttpRequest request = HttpRequest.newBuilder() |
3.9 代理设置
HttpClient
支持通过代理服务器发送请求,可以在 HttpClient
构建器中配置:
1 | HttpClient client = HttpClient.newBuilder() |
3.10 身份认证
通过 Authenticator
配置认证信息,以便客户端在需要时自动提供认证凭证:
1 | Authenticator authenticator = new Authenticator() { |
4. 示例分析
为了更好地理解 HttpRequest
的使用,这里提供一个简单的示例:发送一个 POST 请求,并异步处理响应。
1 | import java.net.URI; |
代码解析:
HttpClient 创建:通过
HttpClient.newBuilder()
创建一个HttpClient
实例,配置了连接超时和自动跟随标准重定向。HttpRequest 构建:定义了一个 POST 请求,目标 URI 为
https://api.example.com/data
,设置了Content-Type
请求头,并通过BodyPublishers.ofString
发送 JSON 格式的请求体。发送异步请求:调用
sendAsync
方法发送请求,指定响应体处理器为ofString
,即将响应体转换为字符串。处理响应:使用
thenApply
和thenAccept
链式调用处理响应体,打印到控制台。如果请求失败,通过exceptionally
捕获并打印错误信息。主线程等待:由于请求是异步发送的,主线程需要等待一段时间以确保响应能够处理。实际应用中,可以使用更优雅的方式管理线程同步。
5. 总结
本文,我们从使用 JDK内置的HttpRequest
遇到的坑以及如何解决它,到工作原理的分析,HttpRequest
为 Java 开发者提供了一个强大且易用的 HTTP 客户端工具。但是,相比于一些成熟的第三方库(比如 Apache HttpClient)还是稍显不足。
因此,在使用一个工具或者框架时,一定要了解其实现原理、优缺点等,可以做到提前避免出现上面类似的问题,或者出现问题时能快速定位和解决问题。
6. 学习交流
如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注公众号:猿java,持续输出硬核文章。
