java11新特性—标准HttpClient

HTTP/1.1和HTTP/2最主要的区别是客户端和服务器之间传输数据的方式。HTTP/1.1依赖于请求和响应周期。HTTP/2允许服务器向客户端”push”数据,它可以向客户端发送比客户端请求更多的数据。这使得它可以优先处理发送对于首先加载网页至关重要的数据。

从Java9提供了新的API来处理HTTP调用,JDK中新的HTTP客户端(HttpClient)在Java9中还属于孵化器模块,经历了JEP110,到Java11 的JEP321,在Java11版本正式”转正”。

  • 在Java9之前,Java提供的HTTP客户端是HttpURLConnection,

它是基于URLConnectionAPI设计的,URLConnection设计之初本意是可以支持多种协议,例如ftp,gopher等等,但是目前这些协议都已经失效了,
HttpURLConnection的API是早于HTTP/1.1发布前就设计了,它的API太过抽象。
HttpURLConnection也是基于阻塞模式(blocking mode)开发的,要求每个就请求/响应都有一个单独的线程
继续维护下去变得十分困难。

开发JDK的大叔因此设计了全新的HTTP客户端用于替换HttpURLConnection。新的Http客户端不仅支持HTTP/1.1,还提供HTTP/2以及WebSocket的支持。在性能方面也有了大幅提升,按照JEP中的描述,新的HTTP客户端有着不输于Apache HttpClient库以及Netty和Jetty的性能。

API

Http Client的主要类有下面三个

  1. java.net.http.HttpClient
  2. java.net.http.HttpRequest
  3. java.net.http.HttpResponse

HttpClient

HttpClient是一个抽象类,在HttpClient中仅定义获取配置的抽象方法,构建HttpClient可以通过下面两个方法

  1. httpClient()
  2. newBuilder()
  • 源码分析
// java.net.http
// 抽象类,不能直接new
public abstract class HttpClient {
// protected
protected HttpClient() {}
// 通过newHttpClient直接构建Http Client
public static HttpClient newHttpClient() {
return newBuilder().build();
}
// 通过Builder的方式构建HTTP Client
public static Builder newBuilder() {
return new HttpClientBuilderImpl();
}
}

通过源码可以看到HttpClient其实是通过HttpClientBuilderImpl构建的

// jdk.internal.net.http
public class HttpClientBuilderImpl implements HttpClient.Builder {
// CookieHandler对象提供了一种回调机制,用于将HTTP状态管理策略实现连接到HTTP协议处理程序中
CookieHandler cookieHandler;
Duration connectTimeout;
// 跳转策略
HttpClient.Redirect followRedirects;
// 代理Selector
ProxySelector proxy;
Authenticator authenticator;
// HTTP版本枚举,1.1和2
HttpClient.Version version;
// 线程池,异步调用时使用
Executor executor;
// ssl安全参数
SSLContext sslContext;
SSLParameters sslParams;
int priority = -1;
@Override
public HttpClient build() {
return HttpClientImpl.create(this);
}
}

HttpClientBuilderImpl属性配置完成后调用build()方法构建HttpClient,可以看到HttpClient的实现类其实是HttpClientImpl

// jdk.internal.net.http
public class HttpClientBuilderImpl implements HttpClient.Builder {
// CookieHandler对象提供了一种回调机制,用于将HTTP状态管理策略实现连接到HTTP协议处理程序中
CookieHandler cookieHandler;
Duration connectTimeout;
// 跳转策略
HttpClient.Redirect followRedirects;
// 代理Selector
ProxySelector proxy;
Authenticator authenticator;
// HTTP版本枚举,1.1和2
HttpClient.Version version;
// 线程池,异步调用时使用
Executor executor;
// ssl安全参数
SSLContext sslContext;
SSLParameters sslParams;
int priority = -1;
@Override
public HttpClient build() {
return HttpClientImpl.create(this);
}
}

HttpClientImpl源码简单分析,我们截取一些关键代码

构建HttpClientImpl构造器里面的逻辑还是很复杂的,在构造犯法中,HttpClientImpl默认会创建核心线程数为0的缓存线程池
同步发送请求方法底层其实用的是异步请求方法
异步发送请求使用构造函数中生成的代理线程池发送Http请求

// jdk.internal.net.http
final class HttpClientImpl extends HttpClient implements Trackable {
// HttpClient构造方法,
private HttpClientImpl(HttpClientBuilderImpl builder,
SingleFacadeFactory facadeFactory) {
// ...
// 默认给Builder中的线程池
Executor ex = builder.executor;
if (ex == null) {
// 构建缓存线程池
ex = Executors.newCachedThreadPool(new DefaultThreadFactory(id));
isDefaultExecutor = true;
} else {
isDefaultExecutor = false;
}
// 代理线程池
delegatingExecutor = new DelegatingExecutor(this::isSelectorThread, ex);
// ...
}

// 同步发送请求
@Override
public <T> HttpResponse<T>
send(HttpRequest req, BodyHandler<T> responseHandler)
throws IOException, InterruptedException
{
CompletableFuture<HttpResponse<T>> cf = null;
try {
cf = sendAsync(req, responseHandler, null, null);
return cf.get();
} catch (InterruptedException ie) {
if (cf != null )
cf.cancel(true);
throw ie;
} catch (ExecutionException e) {
// ...
}
}
// 异步返送请求
private <T> CompletableFuture<HttpResponse<T>>
sendAsync(HttpRequest userRequest,
BodyHandler<T> responseHandler,
PushPromiseHandler<T> pushPromiseHandler,
Executor exchangeExecutor/*异步推送默认给是delegatingExecutor.delegate*/) {

Objects.requireNonNull(userRequest);
Objects.requireNonNull(responseHandler);

AccessControlContext acc = null;
if (System.getSecurityManager() != null)
acc = AccessController.getContext();

// 为了保证HttpRequest的安全,会克隆HttpRequest
HttpRequestImpl requestImpl = new HttpRequestImpl(userRequest, proxySelector);
if (requestImpl.method().equals("CONNECT"))
throw new IllegalArgumentException("Unsupported method CONNECT");

long start = DEBUGELAPSED ? System.nanoTime() : 0;
reference();
try {
if (debugelapsed.on())
debugelapsed.log("ClientImpl (async) send %s", userRequest)
// 默认使用代理线程池
Executor executor = exchangeExecutor == null
? this.delegatingExecutor : exchangeExecutor;
MultiExchange<T> mex = new MultiExchange<>(userRequest,
requestImpl,
this,
responseHandler,
pushPromiseHandler,
acc);
// 异步获取HttpResponse
CompletableFuture<HttpResponse<T>> res =
mex.responseAsync(executor).whenComplete((b,t) -> unreference());
// ...
if (exchangeExecutor != null) {
res = res.whenCompleteAsync((r, t) -> { /* do nothing */}, ASYNC_POOL);
}
return res;
} catch(Throwable t) {
unreference();
debugCompleted("ClientImpl (async)", start, userRequest);
throw t;
}
}

}

HttpRequest

与HttpClient类似,HttpRequest也是一个抽象类,它不能通过构造方法直接创建,需要通过newBuilder()生成HttpRequest.Builder

// java.net.http
public abstract class HttpRequest {
// protected构造方法
protected HttpRequest() {}
// 带URI的newBuilder方法
public static HttpRequest.Builder newBuilder(URI uri) {
return new HttpRequestBuilderImpl(uri);
}
// 不带URI的newBuilder方法
public static HttpRequest.Builder newBuilder() {
return new HttpRequestBuilderImpl();
}
}

HttpRequest.Builder是一个接口,从源码可以看出它的实现类是HttpRequestBuilderImpl

// jdk.internal.net.http
public class HttpRequestBuilderImpl implements HttpRequest.Builder {
// HttpHeaders建造者
private HttpHeadersBuilder headersBuilder;
// http URI
private URI uri;
// http method名称
private String method;
private boolean expectContinue;
// BodyPublisher将高级Java对象转换为适合作为请求体发送的字节缓冲区流。BodyPublishers类提供了许多常见发布程序的实现。BodyPublisher接口扩展了Flow。Publisher<ByteBuffer>,这意味着BodyPublisher充当字节缓冲区的发布者
private BodyPublisher bodyPublisher;
// http版本
private volatile Optional<HttpClient.Version> version;
// http超时时间
private Duration duration;
// 创建HttpRequest
@Override
public HttpRequest build() {
if (uri == null)
throw new IllegalStateException("uri is null");
assert method != null;
return new ImmutableHttpRequest(this);
}
}

通过HttpRequestBuilderImpl的build()方法构建的HttpRequest的实现类是ImmutableHttpRequest,ImmutableHttpRequest中的参数与HttpRequestBuilderImpl相同

// jdk.internal.net.http
final class ImmutableHttpRequest extends HttpRequest {
private final String method;
private final URI uri;
private final HttpHeaders headers;
private final Optional<BodyPublisher> requestPublisher;
private final boolean expectContinue;
private final Optional<Duration> timeout;
private final Optional<Version> version;

ImmutableHttpRequest(HttpRequestBuilderImpl builder) {
this.method = Objects.requireNonNull(builder.method());
this.uri = Objects.requireNonNull(builder.uri());
this.headers = HttpHeaders.of(builder.headersBuilder().map(), ALLOWED_HEADERS);
this.requestPublisher = Optional.ofNullable(builder.bodyPublisher());
this.expectContinue = builder.expectContinue();
this.timeout = Optional.ofNullable(builder.timeout());
this.version = Objects.requireNonNull(builder.version());
}
}

HttpResponse

HttpResponse是HTTP请求结果的接口,我们不需要手动去创建它,HttpResponse在HttpClient发送请求,HttpResponse在接收到响应状态代码和标头时可用,通常在完全接收到响应主体之后可用。在完全接收到响应主体之前,HttpResponse是否可用取决于发送HttpRequest时提供的BodyHandler

public interface HttpResponse<T> {
// 获取HTTP statusCode
public int statusCode();
// 获取HTTP Response关联的request
public HttpRequest request();
// 返回前一个response
public Optional<HttpResponse<T>> previousResponse();
// 返回前一个HttpHeaders
public HttpHeaders headers();
// 返回HTTP response body
public T body();
// 获取sslSession
public Optional<SSLSession> sslSession();
// 获取HTTP URI
public URI uri();
// 获取HTTP 版本
public HttpClient.Version version();
// 初始化返回信息给BodyHandler,它创建的时机是已经获取到了HTTP statusCode和HTTP headers,处理Http response body前
public interface ResponseInfo {
// 获取http statusCode
public int statusCode();
// 获取http headers
public HttpHeaders headers();
// 获取http版本
public HttpClient.Version version();
}

@FunctionalInterface
public interface BodyHandler<T> {
// ,返回一个CompleteStage
public BodySubscriber<T> apply(ResponseInfo responseInfo);
}

// BodyHandler的静态类,提供了不同的BodyHandler实现的实现方法
public static class BodyHandlers {

private BodyHandlers() { }
// ...
// 将
public static BodyHandler<String> ofString(Charset charset) {
Objects.requireNonNull(charset);
return (responseInfo) -> BodySubscribers.ofString(charset);
}
// ...
}
// push承诺请求handler,用于HTTP/2
public interface PushPromiseHandler<T> {
public void applyPushPromise(
HttpRequest initiatingRequest,
HttpRequest pushPromiseRequest,
Function<HttpResponse.BodyHandler<T>,CompletableFuture<HttpResponse<T>>> acceptor
);

public static <T> PushPromiseHandler<T>
of(Function<HttpRequest,BodyHandler<T>> pushPromiseHandler,
ConcurrentMap<HttpRequest,CompletableFuture<HttpResponse<T>>> pushPromisesMap) {
return new PushPromisesHandlerWithMap<>(pushPromiseHandler, pushPromisesMap);
}
}

// BodySubscriber用于消费response body bytes并将其转成更高级的java类型,例如String等
public interface BodySubscriber<T>
extends Flow.Subscriber<List<ByteBuffer>> {
public CompletionStage<T> getBody();
}

// 生成BodySubscriber的静态工具方法,它提供了许多BodySubscriber的通用实现
public static class BodySubscribers {
private BodySubscribers() { }
// ...
public static BodySubscriber<String> ofString(Charset charset) {
Objects.requireNonNull(charset);
return new ResponseSubscribers.ByteArraySubscriber<>(
bytes -> new String(bytes, charset)
);
}
// ...
}
}

示例

使用新的Http Client发送Http请求只需要遵循下面4个步骤

  1. 通过HttpClient.newHttpClient()创建HttpClient
  2. 通过HttpRequest.newBuilder()创建HttpRequest
  3. 调用HttpClient发送请求方法发送Http请求
  4. 处理返回结果

同步

同步Http请求使用send()方法

// 1.创建HttpClient
HttpClient client = HttpClient.newHttpClient();
// 2.创建HttpRequest
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(new URI("http://localhost:8080/linshifu/name?id=1"))
.build();
System.out.println(LocalDateTime.now()+"===> 开始发送发送请求");
// 3.发送http请求
HttpResponse<String> response = client.send(request, (responseInfo ->
HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8)
));
// 4.处理返回结果
System.out.println(LocalDateTime.now()+"===> 结束发送请求,请求结果:\n"
+response.body());
// 1.创建HttpClient
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(5))
.followRedirects(HttpClient.Redirect.ALWAYS)
.build();
// 2.创建HttpRequest--GET
HttpRequest getRequest = HttpRequest.newBuilder()
.GET()
.uri(URI.create("http://www.flydean.com"))
.header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36")
.build();
// 2.创建HttpRequest--POST
HttpRequest.BodyPublisher requestBody = HttpRequest.BodyPublishers
.ofString("{ 我是body }");
HttpRequest postRequest = HttpRequest.newBuilder()
.POST(requestBody)
.uri(URI.create("http://www.flydean.com"))
.build();
// 3.发送http请求
HttpResponse<String> response = client.send( getRequest, HttpResponse.BodyHandlers.ofString());
String respnseBody = response.body();
log.info(respnseBody);

异步

异步发送Http请求使用的是sendAsync()方法,返回CompletableFuture<ResponseBody<T>>

// 1.创建HttpClient
HttpClient client = HttpClient.newHttpClient();
// 2.创建HttpRequest
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(new URI("http://localhost:8080/linshifu/name?id=1"))
.build();
System.out.println(LocalDateTime.now()+"===> 开始发送发送请求");
// 3.sendAsync异步请求,返回CompleteFuture
CompletableFuture<HttpResponse<String>> asyncResult = client.sendAsync(request, (responseInfo ->
HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8)
));
// 4.处理CompletableFuture结果
asyncResult.thenAccept(response -> {
System.out.println(LocalDateTime.now() + "===> 结束发送请求,请求结果:\n"
+ response.body());
});
asyncResult.get();

public void useAsyncHttp()  {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(5))
.followRedirects(HttpClient.Redirect.ALWAYS)
.build();

CompletableFuture<Void> completableFuture=checkUri(client,URI.create("http://www.flydean.com"));
//获取completableFuture的值
completableFuture.join();
}

private CompletableFuture<Void> checkUri(HttpClient httpClient, URI uri){
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(uri)
.header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36")
.build();

return httpClient.sendAsync(request,HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::statusCode)
.thenApply(statusCode -> statusCode == 200 )
.exceptionally(ex -> false)
.thenAccept(valid ->
{
if (valid) {
log.info("uri {} is valid",uri);
} else {
log.info("uri {} is not valid", uri);
}
});
}

上面的例子中我们获取了HTTP请求,然后调用了CompletableFuture的thenApply和thenAccept对结果进行过滤和处理。
CompletableFuture是Future和CompletionStage的混合体。Future大家都很熟悉了,可以通过get方法获取异步执行的结果。而CompletionStage代表的是一个异步计算的stage,不同的stage可以互相依赖,通过then***方法来组成一个级联操作。和Stream的操作有点像,和ES6中的promise的then一样,使用CompletionStage可以避免回调地狱。CompletionStage可以将异步回调转换成级联操作。
thenApply的参数是一个Function,thenAccept是一个Consumer。
最后,我们需要调用completableFuture.join()来保证completableFuture的异步操作执行完成。
当然调用completableFuture.get()方法也是可以的