美文网首页Java高级架构互联网科技Java学习笔记
【本人秃顶程序员】浅析 jdk11 中 HttpClient 的

【本人秃顶程序员】浅析 jdk11 中 HttpClient 的

作者: 本人秃顶程序员 | 来源:发表于2019-03-08 17:05 被阅读3次

    ←←←←←←←←←←←← 快!点关注

    前期准备

    • JDK 版本 : OpenJDK 11.0.1
    • IDE : idea 2018.3

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

    写在最后:

    秃顶程序员的不易,看到这里,点了关注吧!点关注,不迷路,持续更新!!!

    相关文章

      网友评论

        本文标题:【本人秃顶程序员】浅析 jdk11 中 HttpClient 的

        本文链接:https://www.haomeiwen.com/subject/hntdpqtx.html