JDK11 升级之 HTTP 客户端

214 阅读11分钟

旧的 HttpURLConnection 示例代码

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class HttpURLConnectionExample {
    public static void main(String[] args) {
        String urlString = "https://jsonplaceholder.typicode.com/posts/1"; // 请求的 URL
        HttpURLConnection connection = null;

        try {
            // 创建 URL 对象
            URL url = new URL(urlString);

            // 打开连接
            connection = (HttpURLConnection) url.openConnection();

            // 设置请求方法为 GET
            connection.setRequestMethod("GET");

            // 设置请求头(可选)
            connection.setRequestProperty("User-Agent", "Mozilla/5.0");

            // 获取响应码
            int responseCode = connection.getResponseCode();
            System.out.println("Response Code: " + responseCode);

            // 如果响应码为 200(HTTP_OK),读取响应内容
            if (responseCode == HttpURLConnection.HTTP_OK) {
                BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String inputLine;
                StringBuilder response = new StringBuilder();

                while ((inputLine = in.readLine()) != null) {
                    response.append(inputLine);
                }
                in.close();

                // 打印响应内容
                System.out.println("Response: " + response.toString());
            } else {
                System.out.println("GET request failed.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 断开连接
            if (connection != null) {
                connection.disconnect();
            }
        }
    }
}

新的 HTTP 客户端示例代码

  1. 同步请求
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpRequest.BodyPublishers;

public class HttpClientSyncPostExample {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();

        // 构建 POST 请求
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
                .POST(BodyPublishers.ofString("{"title":"foo","body":"bar","userId":1}"))
                .header("Content-Type", "application/json")
                .build();

        // 同步发送请求并获取响应
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

        // 打印响应
        System.out.println("Status Code: " + response.statusCode());
        System.out.println("Response Body: " + response.body());
    }
}
  1. 异步请求
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpRequest.BodyPublishers;
import java.util.concurrent.CompletableFuture;

public class HttpClientAsyncPostExample {
    public static void main(String[] args) {
        HttpClient client = HttpClient.newHttpClient();

        // 构建 POST 请求
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
                .POST(BodyPublishers.ofString("{"title":"foo","body":"bar","userId":1}"))
                .header("Content-Type", "application/json")
                .build();

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

        // 处理响应
        futureResponse.thenAccept(response -> {
            System.out.println("Status Code: " + response.statusCode());
            System.out.println("Response Body: " + response.body());
        });

        // 主线程继续执行其他任务
        System.out.println("Request sent asynchronously!");

        // 等待异步任务完成(仅用于示例,实际应用中应避免阻塞)
        futureResponse.join();
    }
}

代码说明

同步 POST
  • 使用 HttpClient.send() 方法。
  • 当前线程会阻塞,直到请求完成并返回响应。
  • 适合简单的请求场景。
异步 POST
  • 使用 HttpClient.sendAsync() 方法。
  • 返回一个 CompletableFuture,允许通过回调机制处理响应。
  • 主线程不会阻塞,可以继续执行其他任务。
  • 适合高并发或需要非阻塞操作的场景。

总结

  • 同步:适合简单的请求场景,代码逻辑更直观。
  • 异步:适合高并发或需要非阻塞的场景,代码更灵活,但需要处理回调逻辑。

选择同步还是异步取决于你的具体需求。如果是简单的任务,使用同步即可;如果需要高性能或并发处理,建议使用异步。

AsyncHttpClient

org.asynchttpclient.AsyncHttpClient 是一个开源的第三方库,是由开源社区开发和维护的。该库的目标是提供一个高性能、异步的 HTTP 客户端,适合高并发场景。

Github: github.com/AsyncHttpCl…

<dependency>
    <groupId>org.asynchttpclient</groupId>
    <artifactId>async-http-client</artifactId>
    <version>2.12.3</version> <!-- 请根据需要选择最新版本 -->
</dependency>

AsyncHttpClient 的主要特点

  1. 异步非阻塞

    • 使用 NIO(非阻塞 IO)实现,适合高并发场景。
  2. 高性能

    • 支持大量并发连接,性能优于传统的同步 HTTP 客户端。
  3. 支持多种协议

    • 支持 HTTP/1.1、WebSocket 等协议。
  4. 灵活的 API

    • 提供简单易用的 API,支持异步回调和 Future
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.Dsl;
import org.asynchttpclient.Response;

public class AsyncHttpClientPostExample {
    public static void main(String[] args) throws Exception {
        try (AsyncHttpClient client = Dsl.asyncHttpClient()) {
            // 异步 POST 请求
            client.preparePost("https://jsonplaceholder.typicode.com/posts")
                  .setHeader("Content-Type", "application/json")
                  .setBody("{"title":"foo","body":"bar","userId":1}")
                  .execute()
                  .toCompletableFuture()
                  .thenAccept(response -> {
                      System.out.println("Status Code: " + response.getStatusCode());
                      System.out.println("Response Body: " + response.getResponseBody());
                  })
                  .join(); // 等待异步任务完成(仅用于示例)
        }
    }
}

与 Java 自带的 HTTP 客户端对比

特性AsyncHttpClientJava 自带的 HTTP 客户端(JDK 11+)
异步支持原生支持,基于 NIO 实现原生支持,基于 NIO 实现
性能高性能,适合高并发场景性能较好,适合大多数场景(支持 HTTP/2)
依赖需要引入第三方库无需额外依赖
协议支持支持 HTTP/1.1、WebSocket 等支持 HTTP/1.1 和 HTTP/2
复杂性提供更灵活的 API,适合复杂场景API 简单,适合常规场景
  1. 异步支持

    • AsyncHttpClient 和 Java 自带的 HTTP 客户端 都是基于 NIO 实现的异步支持。
    • Java 自带的 HttpClient 使用 CompletableFuture 提供异步编程模型,底层基于 java.nio
  2. 性能

    • AsyncHttpClient 在高并发场景下可能表现更优,因为它经过专门优化,支持更多高级特性(如 WebSocket)。
    • Java 自带的 HTTP 客户端 性能也非常好,尤其是在支持 HTTP/2 的情况下,但它的设计目标是满足大多数常规场景,而不是极端高并发。
  3. 依赖

    • AsyncHttpClient 是第三方库,需要额外引入依赖。
    • Java 自带的 HTTP 客户端 是 JDK 11+ 的内置功能,无需额外依赖。
  4. 协议支持

    • AsyncHttpClient 支持 HTTP/1.1 和 WebSocket,但不支持 HTTP/2。
    • Java 自带的 HTTP 客户端 支持 HTTP/1.1 和 HTTP/2,但不支持 WebSocket。
  5. 复杂性

    • AsyncHttpClient 提供了更灵活的 API,适合复杂的场景(如需要自定义连接池、超时策略等)。
    • Java 自带的 HTTP 客户端 API 更简单,适合常规的 HTTP 请求场景。

总结

  • 异步支持:两者都基于 NIO 实现异步支持。

  • 性能:AsyncHttpClient 在高并发和复杂场景下可能更优,但 Java 自带的 HTTP 客户端性能也非常好,尤其是在支持 HTTP/2 的情况下。

  • 选择建议

    • 如果你需要一个简单、无额外依赖的 HTTP 客户端,Java 自带的 HTTP 客户端是首选。
    • 如果你需要更灵活的功能(如 WebSocket 支持)或极高的并发性能,可以选择 AsyncHttpClient。

AsyncHttpClient 是经过了什么专门优化在高并发场景下可能表现更优

AsyncHttpClient 在高并发场景下表现更优,主要得益于以下专门的优化和设计:


1. 基于 Netty 的高性能网络框架

  • AsyncHttpClient 的底层实现基于 Netty,这是一个高性能的异步事件驱动网络框架。

  • Netty 的优势

    • 高效的线程模型:使用少量线程处理大量连接,充分利用多核 CPU。
    • 零拷贝(Zero-Copy):通过直接内存(Direct Memory)和 ByteBuf 优化数据传输。
    • 高效的事件循环:基于 Reactor 模式,避免了传统阻塞式 IO 的性能瓶颈。

这种底层优化使得 AsyncHttpClient 在处理大量并发 HTTP 请求时具有显著的性能优势。


2. 多路复用和连接池优化

  • 连接池管理

    • AsyncHttpClient 提供了高效的连接池管理,支持复用 TCP 连接,减少连接建立的开销。
    • 连接池支持自定义配置(如最大连接数、空闲超时时间等),适合不同的高并发场景。
  • 多路复用

    • AsyncHttpClient 支持 HTTP/1.1 的持久连接(Keep-Alive)和 HTTP/2 的多路复用(Multiplexing),允许多个请求共享同一个 TCP 连接。

3. 高效的异步编程模型

  • AsyncHttpClient 提供了完全异步的 API,基于回调和 Future 模式,避免了线程阻塞。

  • 异步模型的优势:

    • 在高并发场景下,线程不会因为等待响应而被阻塞,极大地提高了线程利用率。
    • 通过事件驱动机制处理请求和响应,减少了线程上下文切换的开销。

4. 支持 WebSocket 和流式处理

  • AsyncHttpClient 原生支持 WebSocket 协议,适合实时通信场景(如聊天应用、实时数据流)。
  • 提供流式处理能力,可以高效处理大文件上传和下载。

5. 可定制性和扩展性

  • AsyncHttpClient 提供了丰富的配置选项,允许开发者根据具体需求优化性能:

    • 自定义线程池。
    • 配置超时时间(连接超时、请求超时等)。
    • 自定义 SSL/TLS 配置。
  • 这种灵活性使得 AsyncHttpClient 能够适应各种复杂的高并发场景。


6. 更高效的资源管理

  • AsyncHttpClient 使用了高效的内存管理机制(如 Netty 的 ByteBuf),减少了垃圾回收的频率和开销。
  • 通过非阻塞 IO 和事件驱动模型,避免了传统阻塞式 IO 的资源浪费。

对比 Java 自带的 HTTP 客户端

虽然 Java 自带的 HTTP 客户端(java.net.http.HttpClient)在 JDK 11 中引入了异步支持和 HTTP/2,但它的设计目标是满足大多数常规场景,而不是极端高并发场景。相比之下,AsyncHttpClient 的专门优化使其在以下场景中表现更优:

  1. 超高并发:需要处理数千甚至数万的并发请求。
  2. 实时通信:需要支持 WebSocket 或长连接。
  3. 复杂配置:需要自定义连接池、线程池或其他高级配置。

总结

AsyncHttpClient 在高并发场景下表现更优,主要得益于以下几点:

  1. 基于 Netty 的高性能网络框架。
  2. 高效的连接池和多路复用支持。
  3. 完全异步的编程模型,避免线程阻塞。
  4. 支持 WebSocket 和流式处理。
  5. 灵活的配置和扩展能力。
  6. 高效的资源管理和内存优化。

这些优化使得 AsyncHttpClient 成为高并发和复杂 HTTP 场景的首选工具。

Java 自带的 HTTP 客户端(java.net.http.HttpClient)不是基于 Netty 的吗?是基于什么的?

Java 自带的 HTTP 客户端(java.net.http.HttpClient)并不是基于 Netty 的。它是 Java 官方实现的 HTTP 客户端,完全独立于第三方库,基于 Java 自己的 NIO(非阻塞 IO)框架 和 内部实现


Java 自带 HTTP 客户端的底层实现

  1. 基于 Java NIO(非阻塞 IO)

    • HttpClient 使用了 Java 的 java.nio 包(如 AsynchronousSocketChannel 和 Selector)来实现非阻塞 IO。
    • 它通过事件驱动模型处理网络请求和响应,避免了传统阻塞式 IO 的性能瓶颈。
  2. HTTP/2 支持

    • HttpClient 原生支持 HTTP/2 协议,并通过多路复用(Multiplexing)优化了连接的使用。
    • HTTP/2 的实现也是基于 Java 的 NIO,完全由 Java 官方开发。
  3. 线程模型

    • HttpClient 使用了共享的线程池(ForkJoinPool)来处理异步任务和回调。
    • 线程池的使用避免了为每个请求创建新线程的开销,从而提高了性能。
  4. TLS/SSL 支持

    • HttpClient 使用 Java 自带的 javax.net.ssl 包来处理 TLS/SSL 加密连接。
    • 它支持现代加密协议和安全特性。

与 Netty 的区别

虽然 HttpClient 和 Netty 都是基于 NIO 的非阻塞 IO,但它们的设计目标和实现方式有所不同:

特性Java 自带的 HTTP 客户端(HttpClientNetty(AsyncHttpClient 的底层框架)
实现来源Java 官方实现,基于 java.nio第三方框架,独立实现
协议支持HTTP/1.1 和 HTTP/2HTTP/1.1、WebSocket 等
扩展性提供简单的 API,适合常规场景高度可扩展,适合复杂场景
线程模型使用共享线程池(ForkJoinPool使用事件循环(Reactor 模式)
性能优化针对 HTTP 请求优化针对多种协议和高并发场景优化

为什么不是基于 Netty?

Java 自带的 HttpClient 是 Java 官方实现的一部分,设计目标是提供一个 轻量级、标准化的 HTTP 客户端,无需依赖第三方库。选择独立实现而不是基于 Netty,可能出于以下原因:

  1. 独立性

    • Java 官方库需要尽量减少对第三方库的依赖,以保持独立性和一致性。
  2. 标准化

    • HttpClient 是 Java 标准库的一部分,必须符合 Java 的设计哲学和 API 风格。
  3. 轻量化

    • 相比 Netty,HttpClient 的功能更专注于 HTTP 协议,避免了额外的复杂性。
  4. 与 Java NIO 的深度集成

    • HttpClient 直接使用了 Java 的 NIO 框架,充分利用了 Java 平台的特性。

总结

  • Java 自带的 HTTP 客户端(java.net.http.HttpClient)不是基于 Netty 的,而是基于 Java 自己的 NIO(非阻塞 IO)  实现。
  • 它的设计目标是提供一个轻量级、标准化的 HTTP 客户端,适合大多数常规场景。
  • 如果需要更复杂的功能(如 WebSocket 支持)或更高的性能,可以选择基于 Netty 的第三方库(如 AsyncHttpClient)。

AsyncHttpClient 不支持 HTTP2吗?

AsyncHttpClient 支持 HTTP/2,但它的支持是有限的,并且需要明确配置才能启用。


AsyncHttpClient 对 HTTP/2 的支持

  1. 默认行为

    • AsyncHttpClient 默认使用 HTTP/1.1 协议。
    • 如果需要使用 HTTP/2,需要显式启用,并且需要依赖特定的配置。
  2. HTTP/2 的支持方式

    • AsyncHttpClient 的 HTTP/2 支持是通过 Netty 提供的,因为 AsyncHttpClient 的底层实现基于 Netty,而 Netty 支持 HTTP/2。
    • 需要确保使用的 Netty 版本支持 HTTP/2。
  3. 限制

    • AsyncHttpClient 的 HTTP/2 支持并不如 Java 自带的 HttpClient 那样开箱即用。
    • 它的 HTTP/2 支持可能需要额外的配置和依赖。

如何启用 HTTP/2

要在 AsyncHttpClient 中启用 HTTP/2,需要进行以下配置:

1. 添加依赖

确保使用支持 HTTP/2 的 AsyncHttpClient 和 Netty 版本。例如:

<dependency>
    <groupId>org.asynchttpclient</groupId>
    <artifactId>async-http-client</artifactId>
    <version>2.12.3</version> <!-- 请根据需要选择最新版本 -->
</dependency>
2. 配置 HTTP/2

通过 DefaultAsyncHttpClientConfig.Builder 启用 HTTP/2:

import org.asynchttpclient.*;

public class AsyncHttpClientHttp2Example {
    public static void main(String[] args) throws Exception {
        // 配置支持 HTTP/2
        DefaultAsyncHttpClientConfig.Builder configBuilder = new DefaultAsyncHttpClientConfig.Builder()
                .setHttp2Enabled(true); // 启用 HTTP/2

        try (AsyncHttpClient client = Dsl.asyncHttpClient(configBuilder)) {
            // 发送 HTTP/2 请求
            client.prepareGet("https://http2.golang.org/").execute()
                  .toCompletableFuture()
                  .thenAccept(response -> {
                      System.out.println("Status Code: " + response.getStatusCode());
                      System.out.println("Response Body: " + response.getResponseBody());
                  })
                  .join();
        }
    }
}

与 Java 自带的 HTTP 客户端对比

特性AsyncHttpClientJava 自带的 HTTP 客户端(JDK 11+)
HTTP/2 支持支持,但需要显式启用默认支持,自动协商
实现复杂度需要额外配置开箱即用
依赖需要引入 AsyncHttpClient 和 Netty无需额外依赖

总结

  • AsyncHttpClient 支持 HTTP/2,但默认情况下是禁用的,需要通过配置显式启用。
  • 它的 HTTP/2 支持依赖于 Netty 的实现,因此需要确保使用支持 HTTP/2 的 Netty 版本。
  • 如果你需要开箱即用的 HTTP/2 支持,Java 自带的 HttpClient 是更好的选择;如果你需要更灵活的功能(如 WebSocket 支持或自定义配置),AsyncHttpClient 是一个不错的选择。