2023-11-28
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/series/article/1131244380

Java 9 提供了新的HTTP 客户端(HttpClient)来处理HTTP调用,但是那时还处于孵化器阶段,经历了 Java 9 、Java 10 ,终于在Java11版本正式“转正”。

为什么要提供全新的 HTTP 客户端

Java 11 引入新的 HTTP 客户端的原因是为了解决旧的HttpURLConnection类存在的多个问题和局限性。HttpURLConnection 存在如下几个局限性:

  1. 不直观的API设计HttpURLConnection 的 API 设计过于复杂,对于新手比较难上手,例如我们必须手动处理输入和输出流,以及错误处理。
  2. 不支持 HTTP/2HttpURLConnection仅支持 HTTP/1.1,不支持 HTTP/2。HTTP/2支持多路复用、服务器推送和头部压缩等多项特性,这些都能提高网络通信的效率。
  3. 性能问题HttpURLConnection 在高并发场景下性能表现不是很好,尤其是同步阻塞的特性更是性能的瓶颈。
  4. 缺乏现代Web特性的支持:随着Web技术的发展,如 WebSocket 等技术变得越来越重要,而HttpURLConnection并不支持这些。
  5. 缺乏异步处理能力:在处理HTTP请求时,HttpURLConnection不支持异步模式。
  6. 错误处理繁琐HttpURLConnection 的错误处理比较麻烦。我们需要检查特定的HTTP错误代码,并对不同的响应代码进行不同的处理。
  7. 连接管理不足HttpURLConnection 不支持连接池、不支持自动重试。
  8. 不可变对象和线程安全HttpURLConnection不是不可变的,也不是线程安全的。

基于上述几点,Java 11 引入 HttpClient 来替代HttpURLConnection。相比HttpURLConnectionHttpClient 具有如下优势:

  1. 支持 HTTP/2HttpClient 支持 HTTP/2 协议的所有特性,包括同步和异步编程模型。
  2. 支持** WebSocket**:支持WebSocket,允许建立持久的连接,并进行全双工通信。
  3. 更简洁的APIHttpClient提供了更简洁、更易于使用的 API,我们能够非常方便地发送 HTTP 请求。
  4. 支持同步和异步:提供了同步和异步的两种模式。这意味着我们既可以等待HTTP响应,也可以使用回调机制处理HTTP响应。
  5. 链式调用:新的HttpClient 允许链式调用,使得构建和发送请求变得更简单。
  6. 更好的错误处理机制:新的HttpClient 提供了更好的错误处理机制,当HTTP请求失败时,可以通过异常机制更清晰地了解到发生了什么。

核心类介绍

HttpClient的主要类有下面三个:

  • java.net.http.HttpClient
  • java.net.http.HttpRequest
  • java.net.http.HttpResponse

HttpClient

HttpClient是用于发送请求和获取响应的客户端。它可以发送同步和异步的HTTP请求,并支持HTTP/1.1、HTTP/2 以及WebSocket。

由于HttpClient是不可变的,所以它是线程安全的,可以重用以发送多个请求。

利用HttpClient.newBuilder() 可以创建HttpClient的实例,并可以配置各种属性,如:

priority 配置属性
version() 指定HTTP协议的版本(HTTP/1.1或HTTP/2)
followRedirects() 设置重定向策略
proxy() 设置代理服务器
authenticator() 设置HTTP身份验证
connectTimeout() 置建立HTTP连接的超时时间
executor() 设置自定义的Executor,该Executor用于异步任务
cookieHandler() 设置Cookie处理器
sslParameters() 设置SSL/TLS的配置
priority() 设置请求的优先级

这些方法可以使用链式的方式配置在一起,一旦配置完成,调用build()方法就会创建一个新的HttpClient实例,该实例包含所有配置的设置。例如:

HttpClient client = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)
                .followRedirects(HttpClient.Redirect.NEVER)
                .proxy(ProxySelector.of(new InetSocketAddress("127.0.0.1", 8808)))
                .connectTimeout(Duration.ofMinutes(1))
                .build();

HttpClient

HttpRequest

HttpRequest 表示一个HTTP请求,它也是不可变的,所以一旦创建,就不能更改它的配置和数据。使用 HttpRequest.newBuilder() 来创建 HttpRequest 实例,然后链式调用设置方法来配置请求,配置项如下:

方法名 配置项
uri() 设置请求的统一资源标识符 (URI)
timeout() 设置请求的超时时间
header() 添加单个请求头
headers() 批量添加请求头
version() 指定 HTTP 协议的版本
expectContinue() 设置 Expect: 100-Continue 请求头
setHeader() 设置特定的请求头,如认证头

HttpRequest 支持 GET、POST、DELETE 等方法,对应的方法如下:

方法名 描述
GET() 创建一个 GET 请求
POST(HttpRequest.BodyPublisher body) 创建一个带有请求体的 POST 请求
PUT(HttpRequest.BodyPublisher body) 创建一个带有请求体的 PUT 请求
DELETE() 创建一个 DELETE 请求
method(String method, HttpRequest.BodyPublisher body) 创建一个自定义方法的请求

下面代码是创建一个 GET 请求:

HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://www.skjava.com"))
                .version(HttpClient.Version.HTTP_2)
                .timeout(Duration.ofSeconds(10))
                .GET()
                .build();

对于需要请求体的请求,例如 POST、PUT,我们可以使用HttpRequest.BodyPublishers来提供请求体的内容。

HttpResponse

HttpResponse 表示一个 HTTP 响应,它包含HTTP响应的所有信息,包括状态码、响应头和响应体。与HttpClientHttpRequest一样,HttpResponse也是不可变的。

当我们利用 HttpClient 发送 HTTP 请求时,需要指定一个HttpResponse.BodyHandler来处理响应体。这个处理器决定了如何处理响应数据,比如将其作为String,如下:

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

HttpResponse 方法如下:

方法名 描述
statusCode() 获取HTTP响应的状态码
body() 获取HTTP响应体
headers() 获取HTTP响应头
uri() 获取请求的URI
version() 获取响应的HTTP协议版本
request() 获取生成此响应的HttpRequest对象
previousResponse() 获取重定向之前的响应,如果有的话
sslSession() 获取SSL会话信息,如果在SSL连接上获得响应,则为Optional<SSLSession>
trailers() 异步获取HTTP尾部头,返回CompletableFuture<HttpHeaders>

示例

HttpClient 可以使用同步发送和异步发送。

同步发送

HttpClient 提供了 send() 方法,发送请求后会一直阻塞到收到response为止。

    @Test
    public void sendTest() {
        // 构建 HttpClient 对象
        HttpClient httpClient = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)     //HTTP版本为HTTP/2
                .connectTimeout(Duration.ofSeconds(10))  // 连接超时为10秒
                .build();

        // 构建 HttpRequest 对象
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://skjava.com/article/list?page=3")) // uri
                .timeout(Duration.ofSeconds(5))     // 超时时间为 5 秒
                .GET().build();

        HttpResponse<String> response = null;
        try {
            response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() == 200) {
                // 请求成功
                System.out.println("Response Body: " +  response.body());
            } else {
                // 请求不成功
                System.err.println("Response Status Code: " + response.statusCode());
                System.err.println("Response Body: " + response.body());
            }
        } catch (IOException e) {
            // 捕获并处理网络I/O错误
            System.err.println("IOException occurred: " + e.getMessage());
        } catch (InterruptedException e) {
            // 捕获并处理请求过程中的中断异常
            System.err.println("InterruptedException occurred: " + e.getMessage());
            // 中断当前线程
            Thread.currentThread().interrupt();
        }
    }

首先构建 HttpClientHttpRequest 实例对象,调用 HttpClient 的 send() 方法发送 HTTP 同步请求,结果如下:

发送同步请求处理比较简单,我们只需要注意处理 IOExceptionInterruptedException 两个异常。

异步发送

HttpClient 提供了 sendAsync() 方法用于发送异步请求,发送请求后会立刻返回 CompletableFuture,然后我们可以使用CompletableFuture中的方法来设置异步处理器。

    @Test
    public void sendAsyncTest() {
        // 构建 HttpClient 对象
        HttpClient httpClient = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)     //HTTP版本为HTTP/2
                .connectTimeout(Duration.ofSeconds(10))  // 连接超时为10秒
                .build();

        // 构建 HttpRequest 对象
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://skjava.com/article/list?page=1")) // uri
                .timeout(Duration.ofSeconds(5))     // 超时时间为 5 秒
                .GET().build();

        // 发送异步请求
        CompletableFuture<HttpResponse<String>> futureResponse = httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString());

        // 处理响应
        futureResponse.thenApply(HttpResponse::body)    // 获取响应体
                .thenAccept(System.out::println)        // 打印响应体
                .join();                // 等待所有的操作完成
    }

注意,异步请求 sendAsync() 返回的是一个 CompletableFuture,关于 可以看这篇文章:Java 8 新特性—深入理解 CompletableFuture

阅读全文