Apache HttpClient使用和源碼分析

来源:https://www.cnblogs.com/aruo/archive/2023/02/28/17165218.html
-Advertisement-
Play Games

在上文中分析了 HttpURLConnection的用法,功能還是比較簡單的,沒有什麼封裝 接下來看看Apache HttpClient是如何封裝httpClient的 組成 HttpClient 5 的系統架構主要由以下幾個部分組成: HttpCore:核心包,包含了 HTTP 協議的核心抽象和實 ...


在上文中分析了 HttpURLConnection的用法,功能還是比較簡單的,沒有什麼封裝

接下來看看Apache HttpClient是如何封裝httpClient的

目錄

組成

HttpClient 5 的系統架構主要由以下幾個部分組成:

  1. HttpCore:核心包,包含了 HTTP 協議的核心抽象和實現,定義了 HTTP 客戶端和服務端的基本組件,例如請求消息、響應消息、傳輸層等。
  2. HttpClient:高級 API,封裝了 HttpCore 包中的核心抽象,提供了一組簡單易用的 API,以便於客戶端應用程式發送 HTTP 請求。
  3. HttpAsyncClient:非同步 API,是基於 HttpCore 和 HttpClient 構建的非同步 HTTP 客戶端,可以通過非同步方式實現 HTTP 請求。
  4. HttpClient 和 HttpAsyncClient 都可以通過擴展進行定製和優化,可以添加攔截器、設置連接管理器、Cookie 管理器、認證器等。

請求代碼

GET請求代碼

String resultContent = null;
String url = "http://127.0.0.1:8081/get";
HttpGet httpGet = new HttpGet(url);
//通過工廠獲取
CloseableHttpClient httpClient = HttpClients.createDefault();
//請求
CloseableHttpResponse response = httpClient.execute(httpGet);

// Get status code
System.out.println(response.getVersion()); 
// HTTP/1.1
System.out.println(response.getCode()); 
// 200
System.out.println(response.getReasonPhrase()); 
// OK
HttpEntity entity = response.getEntity();
// Get response information
resultContent = EntityUtils.toString(entity);
System.out.println(resultContent);

代碼分析

創建實例

Apache HttpClient提供了一個工廠類來返回HttpClient實例

但實際上都是通過HttpClientBuilder去創建的,

Apache HttpClient通過構建者模式加上策略模式實現非常靈活的配置,以實現各種不同的業務場景

通過看build()的代碼,創建HttpClient主要分為兩步

    public static CloseableHttpClient createDefault() {
        return HttpClientBuilder.create().build();
    }

第一步是初始化配置

裡邊很多策略模式的使用,可以實現相關的類來拓展自己的需求,可以通過HttpClient的set方法把新的策略設置進去,其他配置也可以通過RequestConfig設置好

ConnectionKeepAliveStrategy keepAliveStrategyCopy = this.keepAliveStrategy;
if (keepAliveStrategyCopy == null) {
    keepAliveStrategyCopy = DefaultConnectionKeepAliveStrategy.INSTANCE;
}
AuthenticationStrategy targetAuthStrategyCopy = this.targetAuthStrategy;
if (targetAuthStrategyCopy == null) {
    targetAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE;
}
AuthenticationStrategy proxyAuthStrategyCopy = this.proxyAuthStrategy;
if (proxyAuthStrategyCopy == null) {
    proxyAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE;
}

在這裡會初始化包括連接管理器、請求重試處理器、請求執行器、重定向策略、認證策略、代理、SSL/TLS等配置

第二步是創建處理器鏈

通過組合多個處理器來構建成處理器鏈處理請求

需要註意的是這裡的添加順序的方法,添加最終的執行處理器調用的是addLast()

處理器鏈中的每個處理器都有不同的功能,例如請求預處理、重試、身份驗證、請求發送、響應解析等等。在每個處理器的處理過程中,可以對請求或響應進行修改或擴展,以滿足不同的需求

最後再把初始化好的參數傳遞給InternalHttpClient返回一個HttpClient實例

發起請求

接下來看看請求方法

CloseableHttpResponse response = httpClient.execute(httpGet);

主要請求方法在InternalHttpClient#doExecute

主要分為三步,第一步是將各種配置填充到上下文中HttpContext

第二步,執行剛剛封裝執行鏈

//execChain就是上一步封裝好的執行鏈
final ClassicHttpResponse response = this.execChain.execute(ClassicRequestBuilder.copy(request).build(), scope);

執行 execute 方法,執行鏈中的處理器被依次調用,每個元素都可以執行一些預處理、後處理、重試等邏輯

第三步,請求結束後,將結果轉換為CloseableHttpResponse返回

自定義攔截器和處理器

接下來試試加一下自定義的攔截器和處理器

攔截器和處理器的實現是不一樣的,處理器的實現是ExecChainHandler,攔截器是HttpResponseInterceptorHttpRequestInterceptor

//執行鏈處理器
class MyCustomInterceptor implements ExecChainHandler {
    @Override
    public ClassicHttpResponse execute(ClassicHttpRequest request, ExecChain.Scope scope, ExecChain chain) throws IOException, HttpException {
        System.out.println("MyCustomInterceptor-------------");
        //調用下一個鏈
        return chain.proceed(request,scope);
    }
}

//響應攔截器
class MyCustomResponseInterceptor implements HttpResponseInterceptor {

    @Override
    public void process(HttpResponse response, EntityDetails entity, HttpContext context) throws HttpException, IOException {
        System.out.println("MyCustomResponseInterceptor-------------");
    }
}

//請求攔截器
class MyCustomRequestInterceptor implements HttpRequestInterceptor {


    @Override
    public void process(HttpRequest request, EntityDetails entity, HttpContext context) throws HttpException, IOException {
        System.out.println("MyCustomRequestInterceptor-------------");
    }
}

然後加入到攔截鏈中,custom()方法返回HttpClientBuilder來支持自定義

CloseableHttpClient httpClient = HttpClients.custom()
.addExecInterceptorLast("myCustomInterceptor", new MyCustomInterceptor())
.addRequestInterceptorFirst(new MyCustomRequestInterceptor())
.addResponseInterceptorLast(new MyCustomResponseInterceptor())
.build();

註意看日誌就有輸出了

攔截器和處理器都是用於攔截請求和響應的中間件,但它們在功能上有些不同:

  1. 攔截器:在請求發送前或響應返回後對請求或響應進行修改,例如添加、刪除、修改請求頭或響應頭、修改請求體等。攔截器的主要作用是攔截請求和響應,對它們進行一些操作,並將它們傳遞給下一個攔截器或處理器
  2. 處理器:用於執行實際的請求和響應處理,例如建立連接、發送請求、解析響應等。處理器通常是在整個請求-響應流程中的最後一環,負責將最終的響應結果返回給調用方

總之,攔截器和處理器都是用於處理請求和響應的中間件,但它們的職責和功能略有不同

非同步請求

非同步請求的HttpAsyncClient通過HttpAsyncClients工廠返回

主要的流程和同步請求差不多,包括初始化配置和初始化執行鏈,主要差異在執行請求那裡

因為是非同步執行,需要開啟非同步請求的執行器線程池,通過httpClient.start();方法來設置非同步線程的狀態,否則非同步請求將無法執行

@Override
public final void start() {
    if (status.compareAndSet(Status.READY, Status.RUNNING)) {
        executorService.execute(ioReactor::start);
    }
}

如果沒有開啟,會拋出異常

if (!isRunning()) {
    throw new CancellationException("Request execution cancelled");
}

因為是非同步請求,所以請求方法需要提供回調方法,主要實現三個方法,執行完成、失敗和取消

//創建url
SimpleHttpRequest get = SimpleHttpRequest.create("GET", url);

Future<SimpleHttpResponse> future = httpClient.execute(get, 
//非同步回調                                                     
new FutureCallback<SimpleHttpResponse>() {
    @Override
    public void completed(SimpleHttpResponse result) {
        System.out.println("completed---------------");
    }

    @Override
    public void failed(Exception ex) {
        System.out.println("failed---------------");
    }

    @Override
    public void cancelled() {
        System.out.println("cancelled---------------");
    }
});

SimpleHttpResponse response = future.get();

通過future.get()來獲取非同步結果,接下來看看底層是怎麼實現的

//請求
execute(SimpleRequestProducer.create(request), SimpleResponseConsumer.create(), context, callback);

非同步請求會創建SimpleRequestProducerSimpleResponseConsumer來處理請求和響應,execute()也支持我們自己傳進去

最終的請求和數據的接收都是依賴管道,過程有點像NIO

當請求時,會調用requestProducer.produce(channel);把請求數據寫入channel中,在響應時,responseConsumerchannel中取得數據

源碼的整一塊請求代碼都是通過幾個匿名函數的寫法完成的,看的有點繞

發起請求,匿名函數段代表的是RequestChannel的請求方法

//requestProducer的sendRequest方法
void sendRequest(RequestChannel channel, HttpContext context)

因為RequestChannel類是一個函數式介面,所以可以通過這種方式調用

public interface RequestChannel {
	//請求方法也是叫sendRequest
    void sendRequest(HttpRequest request, EntityDetails entityDetails, HttpContext context) throws HttpException, IOException;
}

生產和消費數據的代碼都在那一刻函數段中,具體的實現細節可以看一下那一段源碼,最終requestProducer也是委托RequestChannel來發起請求

消費完後通過一層一層的回調,最終到達最上邊自己實現的三個方法上

非同步的HttpClient也可以自定義攔截器喝處理器,實現方式和上邊的一樣,處理非同步處理器的實現不同

 CloseableHttpClient httpClient = HttpClients.custom()
                .setDefaultRequestConfig(config)
                .addExecInterceptorLast("myCustomInterceptor", new MyCustomInterceptor())
                .addRequestInterceptorFirst(new MyCustomRequestInterceptor())
                .addResponseInterceptorLast(new MyCustomResponseInterceptor())
                .build();

使用示例

創建HttpClient

如果是同步的就使用HttpClients工廠,非同步的使用HttpAsyncClients

//返回預設的
CloseableHttpClient httpClient = HttpClients.createDefault();

如果想實現自定義配置,可以使用HttpClients.custom()方法

基本的配置被封裝在RequestConfig類中

RequestConfig config = RequestConfig.custom()
            .setConnectionRequestTimeout(3L, TimeUnit.SECONDS)
            .setResponseTimeout(3L, TimeUnit.SECONDS)
            .setDefaultKeepAlive(10L , TimeUnit.SECONDS)
            .build();

如果還不滿足,可以還可以去實現這些策略類

使用自定義配置創建httpClient

CloseableHttpClient httpClient = HttpClients.custom()
            .setDefaultRequestConfig(config)
            .addExecInterceptorLast("myCustomInterceptor", new MyCustomInterceptor())
            .addRequestInterceptorFirst(new MyCustomRequestInterceptor())
            .addResponseInterceptorLast(new MyCustomResponseInterceptor())
            .build();

GET方法請求

 String url = "http://127.0.0.1:8081/get";

List<NameValuePair> nvps = new ArrayList<>();
// GET 請求參數
nvps.add(new BasicNameValuePair("username", "test"));
nvps.add(new BasicNameValuePair("password", "password"));
//將參數填充道url中
URI uri = new URIBuilder(new URI(url))
        .addParameters(nvps)
        .build();
//創建get請求對象
HttpGet httpGet = new HttpGet(uri);

//發起請求
CloseableHttpResponse response = httpClient.execute(httpGet);

// Get status code
System.out.println(response.getVersion()); // HTTP/1.1
System.out.println(response.getCode()); // 200
HttpEntity entity = response.getEntity();
// Get response information
String resultContent = EntityUtils.toString(entity);
System.out.println(resultContent);

POST請求

這次將參數寫到HttpEntity

String url = "http://127.0.0.1:8081/post";

List<NameValuePair> nvps = new ArrayList<>();
// GET 請求參數
nvps.add(new BasicNameValuePair("username", "test"));
nvps.add(new BasicNameValuePair("password", "password"));

UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nvps, StandardCharsets.UTF_8);

HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(formEntity);

CloseableHttpClient httpClient = HttpClients.createDefault();

CloseableHttpResponse response = httpClient.execute(httpPost);

// Get status code
System.out.println(response.getVersion()); // HTTP/1.1
System.out.println(response.getCode()); // 200
HttpEntity entity = response.getEntity();
// Get response information
String resultContent = EntityUtils.toString(entity);
System.out.println(resultContent);

和GET請求基本一致,除了用的是HttpPost,參數可以像GET一樣填充到url中,也可以使用HttpEntity填充到請求體里

Json請求

String json = "{"
        + "    \"username\": \"test\","
        + "    \"password\": \"password\""
        + "}";
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
HttpPost post = new HttpPost("http://127.0.0.1:8081/postJson");
post.setEntity(entity);
CloseableHttpClient client = HttpClients.createDefault();
CloseableHttpResponse response = client.execute(post);

// Get status code
System.out.println(response.getCode()); // 200
// Get response information
String resultContent = EntityUtils.toString(response.getEntity());
System.out.println(resultContent);

總結

HttpURLConnection相比,做了很多封裝,功能也強大了很多,例如連接池、緩存、重試機制、線程池等等,並且對於請求參數的設置更加靈活,還封裝了非同步請求、HTTPS等、自定義攔截器和處理器等

請求是使用了處理鏈的方式發起的,可以對請求和響應進行一系列處理,好處是可以將這些處理器封裝成一個公共的類庫,然後通過自己組合來滿足自己的需求,還可以在請求和響應的不同階段進行攔截和修改,例如添加請求頭、修改請求參數、解密響應數據等,不需要在一個大類里寫很多代碼了,已免代碼臃腫

性能方面使用了連接池技術,可以有效地復用連接,提高性能

不得不說是apache的項目,源碼使用了包括構建者模式、策略模式、責任鏈模式等設計模式對整個httpClient進行了封裝,學習到了

本文來自博客園,作者:阿弱,轉載請註明原文鏈接:https://www.cnblogs.com/aruo/p/17165218.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • VL33 非整數倍數據位寬轉換8to12 和上一題一樣的,註意valid_out輸出時加一個valid_in(其實32題也要加,不過不加模擬也能過)。 `timescale 1ns/1ns module width_8to12( input clk , input rst_n , input val ...
  • 歡迎關註個人公眾號:愛喝可可牛奶 LeetCode演算法訓練-回溯總結 適用問題 組合問題:N個數裡面按一定規則找出k個數的集合 排列問題:N個數按一定規則全排列,有幾種排列方式 切割問題:一個字元串按一定規則有幾種切割方式 子集問題:一個N個數的集合里有多少符合條件的子集 棋盤問題:N皇後,解數獨等 ...
  • if條件語句 if語句 if條件語法結構: if 條件語句: 滿足條件運行的代碼1 滿足條件運行的代碼2 ... ps:條件語句(可以是單個數據,即本身就是布爾類型)需返回一個布爾類型,判斷是否進入條件分支語句 if True: print('條件成⽴執⾏的代碼1') print('條件成⽴執⾏的代 ...
  • Lambda表達式 Lambda表達式理解 Lambda表達式是Jdk 8 開始新增的一種語法形式;作用:用於簡化匿名內部類的代碼寫法 註意:Lambda表達式只能簡化函數式介面的匿名內部類!!! 什麼是函數式介面? 有且僅有一個抽象方法的介面。 註意:大部分函數式介面,上面可能會有一個@Funct ...
  • 本節開始,將對 ResourceManager 中一些常見行為進行分析探究,看某些具體關鍵的行為,在 RM 中是如何流轉的。本節將深入源碼探究「啟動 ApplicationMaster」的具體流程。 ...
  • 錯誤描述 在 Spring Cloud 項目中通過 Open Feign 遠程調用時出現如下錯誤: feign.codec.EncodeException: No qualifying bean of type 'org.springframework.boot.autoconfigure.http ...
  • 之前在做某個業務中,寫了個文件傳輸的程式,程式邏輯很簡單:掃描某個目錄下的文件,對文件進行一些處理,然後把文件移動到另一個目錄。 此前在大多數運行環境里,該程式一直正常運行,直到最近在一個新環境下,出現問題:文件移動失敗。查詢日誌發現在調用file.renameTo方法返回false。我第一反應是查 ...
  • C語言對記憶體的使用劃分為以下區域: 棧區(stack)、堆區(heap)、全局區(靜態區)、常量區、代碼區。 棧區: 由編譯器自動分配釋放,按記憶體地址從高(地址)到低(地址)存儲; 棧區內容的作用域為其所定義的函數內,生命周期為函數執行期間,函數結束自動釋放; 存放局部變數、const局部變數、函數 ...
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...