浅析 jdk11 中 HttpClient 的使用

5次阅读

共计 5155 个字符,预计需要花费 13 分钟才能阅读完成。

零 前期准备
0 版本
JDK 版本 : OpenJDK 11.0.1
IDE : idea 2018.3
1 HttpClient 简介
java.net.http.HttpClient 是 jdk11 中正式启用的一个 http 工具类(其实早在 jdk9 的时候就已经存在了,只是处于孵化期),官方寓意为想要取代 HttpURLConnection 和 Apache HttpClient 等比较古老的开发工具。
新增的 HttpClient 截止到目前 (2019 年 3 月) 为止其实网络资料还比较少,笔者只是根据一些博文和官方 Demo 自己摸索了一下,做了下总结。
【由于是 jdk11 中才正式使用的工具类,距离开发者还很遥远,所以对于源码笔者暂不打算深挖,浅浅的理解怎么使用就行】
一 HttpClient
在 Apache HttpClient 中,一般会创建一个 HttpClient 对象来作为门面。java.net.http.HttpClient 的逻辑也差不多,只是创建方式更加时髦了:
// 创建 builder
HttpClient.Builder builder = HttpClient.newBuilder();

// 链式调用
HttpClient client = builder

//http 协议版本 1.1 或者 2
.version(HttpClient.Version.HTTP_2) //.version(HttpClient.Version.HTTP_1_1)

// 连接超时时间,单位为毫秒
.connectTimeout(Duration.ofMillis(5000)) //.connectTimeout(Duration.ofMinutes(1))

// 连接完成之后的转发策略
.followRedirects(HttpClient.Redirect.NEVER) //.followRedirects(HttpClient.Redirect.ALWAYS)

// 指定线程池
.executor(Executors.newFixedThreadPool(5))

// 认证,默认情况下 Authenticator.getDefault() 是 null 值,会报错
//.authenticator(Authenticator.getDefault())

// 代理地址
//.proxy(ProxySelector.of(new InetSocketAddress(“http://www.baidu.com”, 8080)))

// 缓存,默认情况下 CookieHandler.getDefault() 是 null 值,会报错
//.cookieHandler(CookieHandler.getDefault())

// 创建完成
.build();
在 builder() 方法中,最终会调用到 HttpClientImpl 的构造器,完成 HttpClient 的创建工作:
//HttpClientImpl.class
private HttpClientImpl(HttpClientBuilderImpl builder,
SingleFacadeFactory facadeFactory) {
//CLIENT_IDS 是 AtomicLong 类型的变量,使用 incrementAndGet() 方法实现自增长的 id
id = CLIENT_IDS.incrementAndGet();
// 记录下存有 id 的字符串
dbgTag = “HttpClientImpl(” + id +”)”;

//ssl 认证
if (builder.sslContext == null) {
try {
sslContext = SSLContext.getDefault();
} catch (NoSuchAlgorithmException ex) {
throw new InternalError(ex);
}
} else {
sslContext = builder.sslContext;
}

// 线程池,没有的话就默认创建一个
Executor ex = builder.executor;
if (ex == null) {
ex = Executors.newCachedThreadPool(new DefaultThreadFactory(id));
isDefaultExecutor = true;
} else {
isDefaultExecutor = false;
}
delegatingExecutor = new DelegatingExecutor(this::isSelectorThread, ex);
facadeRef = new WeakReference<>(facadeFactory.createFacade(this));

// 处理 http 2 的 client 类
client2 = new Http2ClientImpl(this);‘
// 缓存操作
cookieHandler = builder.cookieHandler;
// 超时时间
connectTimeout = builder.connectTimeout;
// 转发策略,默认为 NEVER
followRedirects = builder.followRedirects == null ?
Redirect.NEVER : builder.followRedirects;
// 代理设置
this.userProxySelector = Optional.ofNullable(builder.proxy);
this.proxySelector = userProxySelector
.orElseGet(HttpClientImpl::getDefaultProxySelector);
if (debug.on())
debug.log(“proxySelector is %s (user-supplied=%s)”,
this.proxySelector, userProxySelector.isPresent());
// 认证设置
authenticator = builder.authenticator;
// 设置 http 协议版本
if (builder.version == null) {
version = HttpClient.Version.HTTP_2;
} else {
version = builder.version;
}
if (builder.sslParams == null) {
sslParams = getDefaultParams(sslContext);
} else {
sslParams = builder.sslParams;
}
// 连接线程池
connections = new ConnectionPool(id);
connections.start();
timeouts = new TreeSet<>();

//SelectorManager 本质上是 Thread 类的封装
//selmgr 会开启一条线程,HttpClient 的主要逻辑运行在此线程中
// 所以说 HttpClient 是非阻塞的,因为并不跑在主线程中
try {
selmgr = new SelectorManager(this);
} catch (IOException e) {
throw new InternalError(e);
}
// 设置为守护线程
selmgr.setDaemon(true);
filters = new FilterFactory();
initFilters();
assert facadeRef.get() != null;
}
主要是一些储存操作,大致理解即可,不细究。
二 HttpRequest
HttpRequest 是发起请求的主体配置:
// 创建 builder
HttpRequest.Builder reBuilder = HttpRequest.newBuilder();

// 链式调用
HttpRequest request = reBuilder

// 存入消息头
// 消息头是保存在一张 TreeMap 里的
.header(“Content-Type”, “application/json”)

//http 协议版本
.version(HttpClient.Version.HTTP_2)

//url 地址
.uri(URI.create(“http://openjdk.java.net/”))

// 超时时间
.timeout(Duration.ofMillis(5009))

// 发起一个 post 消息,需要存入一个消息体
.POST(HttpRequest.BodyPublishers.ofString(“hello”))

// 发起一个 get 消息,get 不需要消息体
//.GET()

//method(…) 方法是 POST(…) 和 GET(…) 方法的底层,效果一样
//.method(“POST”,HttpRequest.BodyPublishers.ofString(“hello”))

// 创建完成
.build();
三 发送
发起请求:
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
这是同步式的发起请求方式,先来看一下它的实现:
public <T> HttpResponse<T> send(HttpRequest req, BodyHandler<T> responseHandler)
throws IOException, InterruptedException{
CompletableFuture<HttpResponse<T>> cf = null;
try {
// 调用 sendAsync(…) 方法异步地完成主逻辑,并获取 Future
cf = sendAsync(req, responseHandler, null, null);
return cf.get();

// 这之后的所有代码都是在进行异常捕捉,所以可以忽略
} catch (InterruptedException ie) {
if (cf != null)
cf.cancel(true);
throw ie;
} catch (ExecutionException e) {
final Throwable throwable = e.getCause();
final String msg = throwable.getMessage();

if (throwable instanceof IllegalArgumentException) {
throw new IllegalArgumentException(msg, throwable);
} else if (throwable instanceof SecurityException) {
throw new SecurityException(msg, throwable);
} else if (throwable instanceof HttpConnectTimeoutException) {
HttpConnectTimeoutException hcte = new HttpConnectTimeoutException(msg);
hcte.initCause(throwable);
throw hcte;
} else if (throwable instanceof HttpTimeoutException) {
throw new HttpTimeoutException(msg);
} else if (throwable instanceof ConnectException) {
ConnectException ce = new ConnectException(msg);
ce.initCause(throwable);
throw ce;
} else if (throwable instanceof IOException) {
throw new IOException(msg, throwable);
} else {
throw new IOException(msg, throwable);
}
}
}
本质上是使用了异步实现方法 sendAsync(…)。
在 Demo 中也可以直接使用:
// 返回的是 future,然后通过 future 来获取结果
CompletableFuture<String> future =
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body);
// 阻塞线程,从 future 中获取结果
String body = future.get();
四 一点唠叨
java.net.http.HttpClient 非常的年轻,网络资料不多,且代码非常精细和复杂,目前来看底层应该是使用了线程池搭配 Socket 进行异步通讯。具体有待后续研究。

正文完
 0