JDK 之 HttpClient(jdk11)


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();

  




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM