在上文中分析了 HttpURLConnection的用法,功能還是比較簡單的,沒有什么封裝
接下來看看Apache HttpClient是如何封裝httpClient的
- 組成
- 請求代碼
- 代碼分析
- 自定義攔截器和處理器
- 異步請求
- 使用示例
- 創建HttpClient
- GET方法請求
- POST請求
- 總結
組成
HttpClient 5 的系統架構主要由以下幾個部分組成:
- HttpCore:核心包,包含了 HTTP 協議的核心抽象和實作,定義了 HTTP 客戶端和服務端的基本組件,例如請求訊息、回應訊息、傳輸層等,
- HttpClient:高級 API,封裝了 HttpCore 包中的核心抽象,提供了一組簡單易用的 API,以便于客戶端應用程式發送 HTTP 請求,
- HttpAsyncClient:異步 API,是基于 HttpCore 和 HttpClient 構建的異步 HTTP 客戶端,可以通過異步方式實作 HTTP 請求,
- 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,攔截器是HttpResponseInterceptor和HttpRequestInterceptor
//執行鏈處理器
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();
注意看日志就有輸出了
攔截器和處理器都是用于攔截請求和回應的中間件,但它們在功能上有些不同:
- 攔截器:在請求發送前或回應回傳后對請求或回應進行修改,例如添加、洗掉、修改請求頭或回應頭、修改請求體等,攔截器的主要作用是攔截請求和回應,對它們進行一些操作,并將它們傳遞給下一個攔截器或處理器
- 處理器:用于執行實際的請求和回應處理,例如建立連接、發送請求、決議回應等,處理器通常是在整個請求-回應流程中的最后一環,負責將最終的回應結果回傳給呼叫方
總之,攔截器和處理器都是用于處理請求和回應的中間件,但它們的職責和功能略有不同
異步請求
異步請求的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);
異步請求會創建SimpleRequestProducer和SimpleResponseConsumer來處理請求和回應,execute()也支持我們自己傳進去
最終的請求和資料的接收都是依賴管道,程序有點像NIO
當請求時,會呼叫requestProducer.produce(channel);把請求資料寫入channel中,在回應時,responseConsumer從channel中取得資料
原始碼的整一塊請求代碼都是通過幾個匿名函式的寫法完成的,看的有點繞
發起請求,匿名函式段代表的是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
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/545268.html
標籤:Java
