资讯专栏INFORMATION COLUMN

浅析 jdk11 中 HttpClient 的使用

Eminjannn / 2688人阅读

摘要:在中也可以直接使用返回的是,然后通过来获取结果阻塞线程,从中获取结果四一点唠叨非常的年轻,网络资料不多,且代码非常精细和复杂,目前来看底层应该是使用了线程池搭配进行异步通讯。

零 前期准备 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 response =
                client.send(request, HttpResponse.BodyHandlers.ofString());

这是同步式的发起请求方式,先来看一下它的实现:

public  HttpResponse send(HttpRequest req, BodyHandler responseHandler)
        throws IOException, InterruptedException{
    CompletableFuture> 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 future = 
                                client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                                      .thenApply(HttpResponse::body);
//阻塞线程,从 future 中获取结果
String body = future.get();
四 一点唠叨

java.net.http.HttpClient 非常的年轻,网络资料不多,且代码非常精细和复杂,目前来看底层应该是使用了线程池搭配 Socket 进行异步通讯。具体有待后续研究。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73595.html

相关文章

  • JDK11新增HttpClient谈谈非阻塞模型

    摘要:是一个倡议,它提倡提供一种带有非阻塞背压的异步流处理的标准。是标准的实现之一。的实现细节请求响应的与请求响应的暴露为是请求的的消费者是响应的的生产者内部的内部 北京时间 9 月 26 日,Oracle 官方宣布 Java 11 正式发布 一、JDK HTTP Client介绍 JDK11中的17个新特性 showImg(https://segmentfault.com/img/remo...

    pingan8787 评论0 收藏0
  • Java11 HttpClient小试牛刀

    序 本文主要研究一下Java11的HttpClient的基本使用。 变化 从java9的jdk.incubator.httpclient模块迁移到java.net.http模块,包名由jdk.incubator.http改为java.net.http 原来的诸如HttpResponse.BodyHandler.asString()方法变更为HttpResponse.BodyHandlers.of...

    Bmob 评论0 收藏0
  • 聊聊jdk httpclientretry参数

    摘要:序本文主要研究一下的参数这里有一个类型的变量,用来记录请求次数另外还有一个,读取的是值,读取不到默认取,为进入该方法的时候,调用,递增请求次数,然后判断有无超出限制,有则返回带有异常的,即通过返回如果没有超出限制,但是执行请求失败,则 序 本文主要研究一下jdk httpclient的retry参数 DEFAULT_MAX_ATTEMPTS java.net.http/jdk/inte...

    ityouknow 评论0 收藏0
  • java9系列(六)HTTP/2 Client (Incubator)

    摘要:鉴于它还处在,如果不是着急使用,建议还是使用的,它是遵循规范的,使用起来更加方便。貌似要在版本才支持。揭秘让支持协议如何启用命令支持 序 本文主要研究下JEP 110: HTTP/2 Client (Incubator) 基本实例 sync get /** * --add-modules jdk.incubator.httpclient * @throws ...

    tomlingtm 评论0 收藏0
  • Java11新特性

    摘要:从版本开始,不再单独发布或者版本了,有需要的可以自己通过去定制官方解读官方细项解读稳步推进系列六的小试牛刀一文读懂的为何如此高效弃用引擎 Java语言特性系列 Java5的新特性 Java6的新特性 Java7的新特性 Java8的新特性 Java9的新特性 Java10的新特性 Java11的新特性 Java12的新特性 Java13的新特性 序 本文主要讲述一下Java11的新...

    April 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<