JDK11 中的新特性——HTTP Client

北京时间 9 月 26 日,Oracle 官方宣布 Java 11 正式发布

一、JDK HTTP Client 介绍

JDK11 中的 17 个新特性

JDK11 中的 17 个新特性

JDK11 中引入 HTTP Client 的动机

既有的 HttpURLConnection 存在许多问题

  • 其基类 URLConnection 当初是设计为支持多协议,但其中大多已经成为非主流(ftp, gopher…)
  • API 的设计早于 HTTP/1.1,过度抽象
  • 难以使用,存在许多没有文档化的行为
  • 它只支持阻塞模式(每个请求 / 响应占用一个线程)

HTTP Client 发展史

HTTP Client 发展史

在 JDK11 HTTP Client 出现之前

在此之前,可以使用以下工具作为 Http 客户端

  • JDK HttpURLConnection
  • Apache HttpClient
  • Okhttp
  • Spring Rest Template
  • Spring Cloud Feign
  • 将 Jetty 用作客户端
  • 使用 Netty 库。还

初探 JDK HTTP Client

我们来看一段 HTTP Client 的常规用法的样例 ——
执行 GET 请求,然后输出响应体(Response Body)。

1
2
3
4
5
6
7
8
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/"))
.build();
client.sendAsync(request, asString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();

第一步:创建 HttpClient

一般使用 JDK 11 中的 HttpClient 的第一步是创建 HttpClient 对象并进行配置。

  • 指定协议(http/1.1 或者 http/2)
  • 转发(redirect)
  • 代理(proxy)
  • 认证(authenticator)
    1
    2
    3
    4
    5
    6
    HttpClient client = HttpClient.newBuilder()
    .version(Version.HTTP_2)
    .followRedirects(Redirect.SAME_PROTOCOL)
    .proxy(ProxySelector.of(new InetSocketAddress("www-proxy.com", 8080)))
    .authenticator(Authenticator.getDefault())
    .build();

第二步:创建 HttpRequest

从 HttpRequest 的 builder 组建 request

  • 请求 URI
  • 请求 method(GET, PUT, POST)
  • 请求体(request body)
  • Timeout
  • 请求头(request header)
    1
    2
    3
    4
    5
    6
    HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("http://openjdk.java.net/"))
    .timeout(Duration.ofMinutes(1))
    .header("Content-Type", "application/json")
    .POST(BodyPublisher.fromFile(Paths.get("file.json")))
    .build()

第三步:send

  • http client 可以用来发送多个 http request
  • 请求可以被以同步或异步方式发送

1. 同步发送

同步发送 API 阻塞直到 HttpResponse 返回

1
2
3
4
HttpResponse<String> response =
client.send(request, BodyHandler.asString());
System.out.println(response.statusCode());
System.out.println(response.body());

2. 异步发送

  • 异步发送 API 立即返回一个 CompletableFuture
  • 当它完成的时候会获得一个 HttpResponse
    1
    2
    3
    4
    5
    client.sendAsync(request, BodyHandler.asString())
    .thenApply(response -> { System.out.println(response.statusCode());
    return response; } )
    .thenApply(HttpResponse::body)
    .thenAccept(System.out::println);

※CompletableFuture 是在 java8 中加入的,支持组合式异步编程

二、从提升单机并发处理能力的技术来看 HttpClient 的实现细节

提升单机并发处理能力的技术

  • 多 CPU/ 多核
  • 多线程
  • 非阻塞(non-blocking)

Java NIO

Java NIO 为 Java 带来了非阻塞模型。
NIO

Lambda 表达式

  • Lambda 表达式可以方便地利用多 CPU。
  • Lambda 表达式让代码更加具有可读性

回调

假设以下代码是一个聊天应用服务器的一部分,该应用以 Vert.x 框架实现。
(Eclipse Vert.x is a tool-kit for building reactive applications on the JVM.)
向 connectHandler 方法输入一个 Lambda 表达式,每当有用户连接到聊天应用时,都会调用该 Lambda 表达式。这就是一个回调。
这种方式的好处是,应用不必控制线程模型——Vert.x 框架为我们管理线程,打理好一切相关复杂性,程序员只考虑和回调就够了。

1
2
3
4
vertx.createServer()
.connectHandler(socket -> {
socket.dataHandler(new User(socket, this));
}).listen(10_000);

注意,这种设计里,不共享任何状态。对象之间通过向事件总线发送消息通信,根本不需要在代码中添加锁或使用 synchronized 关键字。并发编程变得更加简单。

Future

大量的回调会怎样?请看以下伪代码

1
2
3
4
5
6
7
(1)->{
(2)->{
(3)->{
(4)->{}
}
}
}

大量回调会形成“末日金字塔”。

如何破解? 使用 Future

1
2
3
4
Future1=(1)->{}
Future2=(Future1.get())->{}
Future3=(Future2.get())->{}
Future4=(Future3.get())->{}

但这会造成原本期望的并行处理,变成了串行处理,带来了性能问题。
我们真正需要的是将 Future 和回调联合起来使用。下面将要讲的 CompletableFuture 就是结合了 Future 和回调,其要点是组合不同实例而无需担心末日金字塔问题。

CompletableFuture

1
2
3
4
5
(new CompletableFuture()).thenCompose((1)->{})
.thenCompose((2)->{})
.thenCompose((3)->{})
.thenCompose((4)->{})
.join()

Reactive Streams

Reactive Streams 是一个倡议,它提倡提供一种带有 非阻塞背压 的异步流处理的标准(Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure)。

JDK 9 中的 java.util.concurrent.Flow 中的概念,与 Reactive Streams 是一对一对等的。java.util.concurrent.Flow 是 Reactive Streams 标准的实现之一。

多 CPU 的并行机制让处理海量数据的速度更快,消息传递和响应式编程让有限的并行运行的线程执行更多的 I/O 操作。

HttpClient 的实现细节

请求响应的 body 与 reactive streams

  • 请求响应的 body 暴露为 reactive streams
  • http client 是请求的 body 的消费者
  • http client 是响应的 body 的生产者
    请求响应的 body 与 reactive streams

HttpRequest 内部

1
2
3
4
5
public abstract class HttpRequest {
...
public interface BodyPublisher
extends Flow.Publisher<ByteBuffer> { ... }
}

HttpResponse 的内部

1
2
3
4
5
6
7
8
9
public abstract class HttpResponse<T> {
...
public interface BodyHandler<T> {
BodySubscriber<T> apply(int statusCode, HttpHeaders responseHeaders);
}

public interface BodySubscriber<T>
extends Flow.Subscriber<List<ByteBuffer>> { ... }
}