浅析 jdk11 中 HttpClient 的使用

零 前期准备

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 进行异步通讯。具体有待后续研究。

相关推荐