學習鏈接
Apache httpclient & okhttp(1)
Apache httpclient & okhttp(2)
httpcomponents-client github
apache httpclient文檔
apache httpclient文檔詳細使用
log4j日志官方文檔
【Java基礎】- HttpURLConnection詳解
java使用httpclient 、HttpURLConnection 發送第三方請求 使用詳解、總結。HttpClient 的使用
Java 中的 HTTP 客戶端庫OkHttp、Apache HttpClient和HttpUrlConnection
文章目錄
- 學習鏈接
- Apache HttpClient
- HttpComponents概述
- 特點
- httpcomponents-client
- 項目代碼
- 文檔資料
- 0、HTTP編程入門
- 1、快速入門httpclient
- 2、使用教程
- 3、代碼示例
- 4、日志說明
- log4j的配置說明
- 快速入門
- pom.xml
- log4j2.xml
- QuickStart01
- HTTP客戶端不讀取響應時的服務端處理機制
- QuickStart02
- QuickStart03(Fluent流式)
- 示例代碼
- 響應處理
- 手動連接釋放
- httpclient的配置
- 請求中止
- 客戶端鑒權
- 代理請求
- 代理鑒權
- 塊編碼流式傳輸請求實體(文件傳輸)
- 多部分編碼請求實體(文件上傳)
- 自定義執行上下文
- 基于表單的登錄
- 多線程請求執行
- 刪除過期連接
- 自定義SSL上下文
- 代理隧道
- 異步請求
- Fluent流式
- FluentRequests
- FluentResponseHandling
- FluentExecutor
- FluentAsync
Apache HttpClient
HttpComponents概述
HttpCore是一組低級HTTP傳輸組件,可用于以最小的占用空間構建自定義客戶端和服務器端HTTP服務。HttpCore支持兩種輸入/輸出模型:基于經典Java輸入/輸出的阻塞輸入/輸出模型
和基于JavaNIO的非阻塞、事件驅動輸入/輸出模型
。
特點
- 基于標準,純Java,HTTP版本1.0和1.1的實現
- 在可擴展的OO框架中完全實現所有HTTP方法(GET、POST、PUT、DELETE、HEAD、OPTIONS和TRACE)。
- 支持安全超文本傳輸協議(HTTP over SSL)加密。
- 通過HTTP代理的透明連接。
- 隧道安全超文本傳輸協議連接通過HTTP代理,通過CONNECT方法。
- 基本,摘要,NTLMv1,NTLMv2,NTLM2會話,SNPNEGO,Kerberos協議鑒權方案。
- 自定義鑒權方案的插件機制。
- 可插拔安全插座工廠,更易于使用第三方解決方案
- 支持在多線程應用程序中使用的連接管理。支持設置最大總連接數以及每個主機的最大連接數。檢測并關閉過時的連接。
- 自動Cookie處理讀取Set-Cookie:從服務器的標頭,并在適當的時候將它們發送回Cookie表頭。
- 自定義cookie策略的插件機制。
- 請求輸出流以避免通過直接流式傳輸到套接字到服務器來緩沖任何內容體。
- 響應輸入流通過直接從套接字流式傳輸到服務器來有效地讀取響應正文。
- 在HTTP/1.0中使用KeepAlive和在HTTP/1.1中使用持久連接
- 直接訪問服務器發送的響應代碼和標頭。
- 設置連接超時的能力。
- 支持HTTP/1.1響應緩存。
- 源代碼在Apache許可下免費提供。
httpcomponents-client
項目代碼
apache httpcomponents-client - github,項目有4.5.x和5.4.x分支。穩定版建議使用4.5.14。
文檔資料
0、HTTP編程入門
https://hc.apache.org/httpcomponents-client-4.5.x/primer.html
1、快速入門httpclient
https://hc.apache.org/httpcomponents-client-4.5.x/quickstart.html
2、使用教程
https://hc.apache.org/httpcomponents-client-4.5.x/current/tutorial/html/
3、代碼示例
https://github.com/apache/httpcomponents-client/tree/4.5.x/httpclient/src/examples/org/apache/http/examples/client - 這里面包括豐富的示例,覆蓋一些更復雜使用場景
4、日志說明
https://hc.apache.org/httpcomponents-client-4.5.x/logging.html
HttpClient利用Commons Logging包提供的日志接口。默認情況下Commons Logging支持以下日志記錄框架:
-
Log4J 2
-
java.util.logging
-
SimpleLog(Commons Logging內部)
HttpClient執行三種不同類型的日志記錄:每個類中使用的標準上下文日志記錄、HTTP表頭日志記錄和全線日志記錄。
- 上下文日志記錄包含有關HttpClient
執行HTTP請求時的內部操作的信息
。每個類都有自己的日志,根據類的完全限定名稱命名。例如,類DefaultHttpClient有一個名為org.apache.http.impl.client.DefaultHttpClient的日志。由于所有類都遵循此約定,因此可以使用名為org.apache.http.impl.client的單個日志為所有類配置上下文日志 - 連線日志用于記錄執行HTTP請求時與服務器之間傳輸的所有數據。
連線日志使用org.apache.http.wire日志類別
。該日志應該只啟用調試問題,因為它會產生極其大量的日志數據
。 - 因為HTTP請求的內容對于調試來說通常不如HTTP標頭重要,所以org.apache.http.headers日志記錄類別僅用于捕獲HTTP標頭。
log4j的配置說明
因為使用的版本是httpclient-4.5.14版本,它默認使用的是 apache的commons-logging來記錄日志的(即JCL),現在要使用log4j來作為日志實現,需要先排除掉httpclient引入的commons-logging依賴,然后引入jcl-over-slf4j
橋接器,橋接器的作用是取代commons-logging依賴(因為httpclient中使用的類名是無法更改的,橋接器的作用就是使用和commons-logging同樣的包名和類名,來個偷梁換柱的效果,這樣就可以修改日志的實現了),然后再引入log4j-core、log4j-slf4j-impl將日志實現綁定到log4j上
httpclient官網有關日志配置的說明,以及 log4j官網文檔說明
快速入門
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.zzhua</groupId><artifactId>demo-http</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.14</version><exclusions><exclusion><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>fluent-hc</artifactId><version>4.5.14</version></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpmime</artifactId><version>4.5.14</version></dependency><!-- JCL 轉 SLF4J 橋接 --><dependency><groupId>org.slf4j</groupId><artifactId>jcl-over-slf4j</artifactId><version>1.7.36</version></dependency><!-- Log4j2 核心和SLF4J綁定 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.17.1</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId><version>2.17.1</version></dependency></dependencies></project>
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration><Appenders><Console name="Console"><PatternLayout pattern="%d %-5level [%logger] %msg%n%xThrowable" /></Console></Appenders><Loggers><Logger name="org.apache.http" level="DEBUG"><AppenderRef ref="Console"/></Logger><Logger name="org.apache.http.wire" level="DEBUG"><AppenderRef ref="Console"/></Logger><Root level="INFO"><AppenderRef ref="Console" /></Root></Loggers></Configuration>
QuickStart01
public class QuickStart01 {public static void main(String[] args) throws IOException {CloseableHttpClient httpclient = HttpClients.createDefault();HttpGet httpGet = new HttpGet("https://www.solargod.cn/api/articles/pageListArticles?current=1&size=5");CloseableHttpResponse response1 = httpclient.execute(httpGet);// 底層HTTP連接仍由響應對象持有,以便直接從網絡套接字流式傳輸響應內容。// 為確保正確釋放系統資源,用戶必須在finally代碼塊中調用CloseableHttpResponse#close()方法。// 需特別注意:若未完整消費響應內容,底層連接將無法安全復用,最終會被連接管理器強制關閉并廢棄。try {System.out.println(response1.getStatusLine());HttpEntity entity1 = response1.getEntity();// 拿到響應體后, 可以使用它// 需要確保響應體被消費完,否則底層連接將無法安全復用。// EntityUtils.consume(entity1);System.out.println(EntityUtils.toString(entity1, Charsets.UTF_8));} finally {response1.close();}}}
HTTP客戶端不讀取響應時的服務端處理機制
假設客戶端httpclient向服務端springboot發起請求,對吧,服務端需要響應數據給客戶端,這個數據比較多,服務端需要不斷的讀取1個文件,每讀取一段,然后寫到響應里。httpClient.execute()是發起請求,然后得到1個response,這時服務端這時就收到了請求,然后服務端不斷的寫入,但是如果此時客戶端拿到response之后不去讀取響應信息,那底層是怎么做的?服務端仍然不斷的寫入,但是客戶端又不去讀?
用戶的問題是關于HTTP客戶端和服務端之間的數據流交互的。具體來說,當客戶端(比如用HttpClient)發送請求到Spring Boot服務端,服務端需要返回大量數據,比如分塊讀取文件并逐步寫入響應。而客戶端在獲取響應對象后,如果不主動讀取內容,底層會發生什么情況呢?
首先,我需要回憶一下HTTP協議的相關知識,特別是關于數據傳輸的部分。HTTP/1.1支持分塊傳輸編碼(chunked transfer encoding),允許服務端逐步發送數據,而不需要預先知道總數據大小。這種情況下,客戶端可以邊接收邊處理數據,而不是等所有數據都傳輸完畢。
接下來,用戶的問題是,當客戶端調用httpClient.execute()并得到響應對象后,如果客戶端不主動讀取響應體(比如不調用getEntity().getContent()或者類似的方法),那么服務端那邊還在繼續寫入數據的話,會發生什么情況?
這個時候,可能需要考慮TCP層的流量控制機制。TCP使用滑動窗口來控制數據傳輸的速度,確保接收方有足夠的緩沖區來接收數據。如果客戶端不讀取響應數據,接收窗口可能會被填滿,導致服務端無法繼續發送數據,進而阻塞寫入操作,直到客戶端開始讀取并釋放窗口空間。
不過,這里可能涉及到應用層和傳輸層的交互。服務端在寫入響應時,如果使用分塊編碼,會逐步發送數據塊。每個數據塊會被寫入到輸出流中,然后通過TCP發送。如果客戶端的TCP接收緩沖區滿了,服務端的寫入操作可能會被阻塞,直到客戶端讀取數據,騰出緩沖區空間。這種情況下,服務端的線程可能會被阻塞,無法繼續處理其他請求,尤其是在使用同步I/O的情況下。
另外,需要考慮服務端框架的具體實現。比如Spring Boot中使用的是Servlet API,當使用response.getOutputStream()進行寫入時,每次寫入數據會嘗試發送到客戶端。如果客戶端不讀取,緩沖區的數據積壓可能導致服務端寫入操作變慢或阻塞。此外,可能還存在超時機制,如果長時間沒有讀取,連接可能會被關閉,釋放資源。
另外,HTTP協議本身是請求-響應模型,客戶端發起請求后,服務端開始發送響應。如果客戶端不讀取響應體,但連接仍然保持打開狀態,服務端可能會持續嘗試發送數據,直到達到某個超時限制。例如,在Spring Boot中,可能有配置的連接超時或響應超時設置,超過時間后會關閉連接。
還需要考慮HttpClient的具體行為。比如,Apache HttpClient在獲取響應對象后,如果不讀取內容,可能會在連接釋放時關閉連接,導致未讀取的數據被丟棄。或者在嘗試讀取響應體時,如果已經關閉了連接,可能會拋出異常。
可能的誤解點在于,用戶可能認為服務端會一直寫入數據,即使客戶端不讀取,但實際上TCP層的流量控制會阻止這種情況,導致服務端的寫入操作被阻塞,直到客戶端開始讀取數據。或者,如果服務端使用非阻塞I/O(如NIO),可能不會阻塞,但需要相應的處理機制。
總結起來,關鍵點在于TCP的流量控制、服務端框架的處理方式、超時設置以及客戶端的讀取行為。如果客戶端不讀取,服務端的寫入可能被阻塞或最終超時關閉連接。
1、TCP流量控制與緩沖區機制
-
接收窗口限制:TCP協議通過滑動窗口機制實現流量控制。客戶端的內核會為每個TCP連接維護一個接收緩沖區。服務端寫入的數據會先填充此緩沖區。
-
如果客戶端不讀取數據,緩沖區逐漸被填滿,導致接收窗口縮小。當窗口大小為0時,服務端會暫停發送數據,直到客戶端讀取數據、窗口重新打開。
-
此時,服務端的寫入操作(如OutputStream.write())會因TCP層的阻塞而暫停,寫入線程被掛起。
-
2、 服務端框架的行為(以Spring Boot為例)
-
同步寫入場景:若服務端使用傳統阻塞I/O(如Servlet的response.getOutputStream()逐段寫入文件):
-
每次調用write()會嘗試將數據寫入TCP發送緩沖區。
-
若客戶端不讀取導致緩沖區滿,服務端的write()操作會阻塞,直到客戶端讀取數據騰出空間或超時。
-
若阻塞時間過長,可能觸發服務端的寫超時(如Spring Boot的server.connection-timeout),強制關閉連接。
-
-
異步非阻塞場景:若服務端使用Reactive框架(如WebFlux)或異步Servlet:
-
寫入操作不會阻塞線程,數據會被暫存在內存或隊列中。
-
但若客戶端長期不讀取,可能導致服務端內存耗盡,最終拋出OutOfMemoryError或觸發背壓(Backpressure)機制暫停數據生產。
-
3、客戶端的潛在行為
-
不調用響應內容讀取方法:
-
若客戶端獲取HttpResponse后,既不調用getEntity().getContent(),也不關閉連接:
-
連接保持打開狀態,但數據堆積在TCP接收緩沖區。
-
最終可能導致服務端因寫超時或客戶端因讀超時(如httpclient.socket-timeout)關閉連接。
-
-
-
若客戶端直接關閉連接(如不處理響應),服務端會收到Connection reset異常,中斷寫入。
4、資源泄漏與超時
- 服務端資源占用:若客戶端不讀取且不關閉連接,服務端的線程可能被長時間阻塞(同步模型)或內存被占用(異步模型)。
- 超時機制:
-
服務端超時:通過server.connection-timeout(默認無限制)強制關閉空閑連接。
-
客戶端超時:通過httpclient.socket-timeout中斷長時間未響應的連接。
-
總結
- 關鍵制約因素:TCP流量控制確保服務端不會無限寫入,客戶端不讀取時,服務端寫入會因緩沖區滿而阻塞或觸發超時。
最佳實踐:
-
客戶端應及時讀取響應流,或在不需要數據時主動取消請求。
-
服務端應設置合理的超時和緩沖區大小,對大規模數據流采用分頁或分塊傳輸,并考慮異步非阻塞模型以避免線程阻塞。
-
示例場景:假設客戶端發起下載大文件的請求后崩潰,未讀取響應。服務端在嘗試寫入時會因TCP窗口關閉而暫停,最終因超時(如30秒)關閉連接,釋放資源。
QuickStart02
import org.apache.commons.codec.Charsets;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;public class QuickStart02 {public static void main(String[] args) throws IOException {CloseableHttpClient httpclient = HttpClients.createDefault();HttpPost httpPost = new HttpPost("http://127.0.0.1:8080/test01");List<NameValuePair> nvps = new ArrayList<NameValuePair>();nvps.add(new BasicNameValuePair("username", "vip"));nvps.add(new BasicNameValuePair("password", "secret"));// 提交表單httpPost.setEntity(new UrlEncodedFormEntity(nvps));CloseableHttpResponse response2 = httpclient.execute(httpPost);try {// HTTP/1.1 200System.out.println(response2.getStatusLine());HttpEntity entity2 = response2.getEntity();// 拿到響應體后, 可以使用它// 需要確保響應體被消費完,否則底層連接將無法安全復用。// EntityUtils.consume(entity2);System.out.println(EntityUtils.toString(entity2, Charsets.UTF_8));} finally {response2.close();}}}
QuickStart03(Fluent流式)
流式調用風格,需要引入fluent-hc依賴。
import org.apache.commons.codec.Charsets;
import org.apache.http.client.fluent.Content;
import org.apache.http.client.fluent.Form;
import org.apache.http.client.fluent.Request;import java.io.IOException;public class QuickStart03 {public static void main(String[] args) throws IOException {Content content1 = Request.Get("https://www.solargod.cn/api/articles/pageListArticles?current=1&size=5").execute().returnContent();System.out.println(content1.asString(Charsets.UTF_8));Content content2 = Request.Post("http://127.0.0.1:8080/test01").bodyForm(Form.form().add("username", "vip").add("password", "secret").build()).execute().returnContent();System.out.println(content2.asString());}}
示例代碼
響應處理
此示例演示了如何使用響應處理程序處理HTTP響應。這是執行HTTP請求和處理HTTP響應的推薦方法。這種方法使調用方能夠專注于消化HTTP響應的過程,并將系統資源釋放的任務委托給HttpClient。HTTP響應處理程序的使用保證了在所有情況下底層HTTP連接都會自動釋放回連接管理器。
/*** This example demonstrates the use of the {@link ResponseHandler} to simplify* the process of processing the HTTP response and releasing associated resources.*/
public class ClientWithResponseHandler {public final static void main(String[] args) throws Exception {CloseableHttpClient httpclient = HttpClients.createDefault();try {HttpGet httpget = new HttpGet("http://httpbin.org/");System.out.println("Executing request " + httpget.getRequestLine());// Create a custom response handlerResponseHandler<String> responseHandler = new ResponseHandler<String>() {@Overridepublic String handleResponse(final HttpResponse response) throws ClientProtocolException, IOException {int status = response.getStatusLine().getStatusCode();if (status >= 200 && status < 300) {HttpEntity entity = response.getEntity();return entity != null ? EntityUtils.toString(entity) : null;} else {throw new ClientProtocolException("Unexpected response status: " + status);}}};String responseBody = httpclient.execute(httpget, responseHandler);System.out.println("----------------------------------------");System.out.println(responseBody);} finally {httpclient.close();}}}
手動連接釋放
/*** This example demonstrates the recommended way of using API to make sure* the underlying connection gets released back to the connection manager.*/
public class ClientConnectionRelease {public final static void main(String[] args) throws Exception {CloseableHttpClient httpclient = HttpClients.createDefault();try {HttpGet httpget = new HttpGet("http://httpbin.org/get");System.out.println("Executing request " + httpget.getRequestLine());CloseableHttpResponse response = httpclient.execute(httpget);try {System.out.println("----------------------------------------");System.out.println(response.getStatusLine());// Get hold of the response entityHttpEntity entity = response.getEntity();// If the response does not enclose an entity, there is no need// to bother about connection releaseif (entity != null) {InputStream inStream = entity.getContent();try {inStream.read();// do something useful with the response} catch (IOException ex) {// In case of an IOException the connection will be released// back to the connection manager automaticallythrow ex;} finally {// 關閉輸入流 會觸發連接釋放// Closing the input stream will trigger connection releaseinStream.close();}}} finally {response.close();}} finally {httpclient.close();}}}
httpclient的配置
此示例演示如何自定義和配置HTTP請求執行和連接管理的最常見方面。
/*** This example demonstrates how to customize and configure the most common aspects* of HTTP request execution and connection management.*/
public class ClientConfiguration {public final static void main(String[] args) throws Exception {// 使用自定義的消息解析器/寫入器,來定制 HTTP 消息從數據流中解析及寫入的方式。HttpMessageParserFactory<HttpResponse> responseParserFactory = new DefaultHttpResponseParserFactory() {@Overridepublic HttpMessageParser<HttpResponse> create(SessionInputBuffer buffer, MessageConstraints constraints) {LineParser lineParser = new BasicLineParser() {@Overridepublic Header parseHeader(final CharArrayBuffer buffer) {try {return super.parseHeader(buffer);} catch (ParseException ex) {return new BasicHeader(buffer.toString(), null);}}};return new DefaultHttpResponseParser(buffer, lineParser, DefaultHttpResponseFactory.INSTANCE, constraints) {@Overrideprotected boolean reject(final CharArrayBuffer line, int count) {// try to ignore all garbage preceding a status line infinitelyreturn false;}};}};HttpMessageWriterFactory<HttpRequest> requestWriterFactory = new DefaultHttpRequestWriterFactory();// 使用自定義的連接工廠來定制 HTTP 出站連接 的初始化過程。// 除了標準的連接配置參數外,HTTP 連接工廠還可以定義 消息解析器/寫入器 的例程,供各個連接使用。// (這里指的是可以通過自定義 ConnectionFactory 來靈活控制 HTTP 連接的建立方式,并指定底層的數據解析和寫入邏輯,從而適應特殊協議或優化網絡通信。)HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory = new ManagedHttpClientConnectionFactory(requestWriterFactory, responseParserFactory);// 客戶端 HTTP 連接對象在完全初始化后,可綁定至任意網絡套接字。// 網絡套接字的初始化過程(包括連接遠程地址和綁定本地地址)均由 連接套接字工廠 控制。// (該機制允許通過自定義 ConnectionSocketFactory 靈活管理底層套接字的創建、連接和綁定行為,從而支持代理、隧道或特殊網絡環境下的 HTTP 通信。)// 安全連接的 SSL 上下文 既可根據系統默認配置創建,也可基于應用程序的特定屬性進行定制。// (這意味著開發者可以選擇依賴操作系統/Java 默認的 SSL/TLS 設置,或通過自定義密鑰庫、信任庫及協議參數等,靈活配置 HTTPS 的安全策略。)SSLContext sslcontext = SSLContexts.createSystemDefault();// 為支持的協議方案(如 http/https)創建一個自定義連接套接字工廠的注冊表。// (技術說明:該機制允許針對不同協議(如 HTTP、HTTPS 或其他自定義協議)注冊對應的 ConnectionSocketFactory 實現,從而在建立連接時動態選擇底層套接字的創建和管理邏輯,例如支持代理、自定義 TLS 配置或網絡隧道等場景。)Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.INSTANCE).register("https", new SSLConnectionSocketFactory(sslcontext)).build();// 使用自定義 DNS 解析器 以覆蓋系統的默認 DNS 解析機制。// (技術說明:通過自定義 DnsResolver 實現,可以接管 HTTP 客戶端的域名解析過程,支持硬編碼映射、智能路由、故障規避或特殊網絡環境下的 DNS 定制需求。)DnsResolver dnsResolver = new SystemDefaultDnsResolver() {@Overridepublic InetAddress[] resolve(final String host) throws UnknownHostException {if (host.equalsIgnoreCase("myhost")) {return new InetAddress[] { InetAddress.getByAddress(new byte[] {127, 0, 0, 1}) };} else {return super.resolve(host);}}};// 創建帶有自定義配置的 連接管理器。// (技術說明:通過自定義 HttpClientConnectionManager(如 PoolingHttpClientConnectionManager 或 BasicHttpClientConnectionManager),// 可靈活控制 HTTP 連接的 生命周期、池化策略、超時設置 等核心參數,以滿足高并發、長連接或特殊網絡環境的需求。)PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, connFactory, dnsResolver);// 創建 Socket 配置// (技術說明:通過自定義 SocketConfig 可精細控制底層套接字的行為,適用于所有由連接管理器創建的 HTTP 連接。// 關鍵配置項包括:超時控制、性能調優、網絡容錯)SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).build();// 配置連接管理器,使其 默認全局應用 Socket 配置,或 針對特定主機 單獨設置。connManager.setDefaultSocketConfig(socketConfig);connManager.setSocketConfig(new HttpHost("somehost", 80), socketConfig);// 在 1 秒空閑時間 后驗證連接的有效性connManager.setValidateAfterInactivity(1000);// 配置消息約束(Message Constraints)// 消息約束用于控制 HTTP 請求/響應 的解析行為,防止惡意或錯誤格式的數據導致資源耗盡(如過大的 Headers、過長的行或無效的結構)。MessageConstraints messageConstraints = MessageConstraints.custom().setMaxHeaderCount(200).setMaxLineLength(2000).build();// 創建連接配置(Connection Configuration)// 連接配置用于精細控制 HTTP 連接的底層行為,包括超時、緩沖區、Keep-Alive、SSL/TLS 等。ConnectionConfig connectionConfig = ConnectionConfig.custom().setMalformedInputAction(CodingErrorAction.IGNORE).setUnmappableInputAction(CodingErrorAction.IGNORE).setCharset(Consts.UTF_8).setMessageConstraints(messageConstraints).build();// 配置連接管理器:全局默認 vs 特定主機的連接設置// 可以通過 HttpClientConnectionManager 為所有連接設置 全局默認配置,或針對 特定主機 進行精細化調整。connManager.setDefaultConnectionConfig(connectionConfig);connManager.setConnectionConfig(new HttpHost("somehost", 80), ConnectionConfig.DEFAULT);// 配置連接池的最大連接數限制(全局 & 單路由)// 用于控制 HTTP 連接池 中持久化連接的最大數量,防止資源耗盡,適用于高并發場景。connManager.setMaxTotal(100);connManager.setDefaultMaxPerRoute(10);connManager.setMaxPerRoute(new HttpRoute(new HttpHost("somehost", 80)), 20);// 使用自定義 Cookie 存儲(Custom Cookie Store)// 允許覆蓋默認的 Cookie 管理策略,適用于需要 持久化 Cookie、跨會話共享 Cookie 或自定義 Cookie 邏輯 的場景。CookieStore cookieStore = new BasicCookieStore();// 使用自定義憑證提供器(Custom Credentials Provider)CredentialsProvider credentialsProvider = new BasicCredentialsProvider();// 創建全局請求配置(Global Request Configuration)// 用于定義所有 HTTP 請求的默認行為,如超時、代理、重定向策略等。適用于統一管理請求級參數。RequestConfig defaultRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.DEFAULT).setExpectContinueEnabled(true).setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.DIGEST)).setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)).build();// 創建自定義配置的 HttpClient// - 連接池管理(最大連接數、路由限制)// - 請求配置(超時、代理、重定向)// - SSL 安全策略(自定義證書/信任庫)// - 自定義組件(DNS 解析器、Cookie 存儲、憑證提供器等)CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(connManager).setDefaultCookieStore(cookieStore).setDefaultCredentialsProvider(credentialsProvider).setProxy(new HttpHost("myproxy", 8080)).setDefaultRequestConfig(defaultRequestConfig).build();try {HttpGet httpget = new HttpGet("http://httpbin.org/get");// 請求級配置覆蓋(Request-Level Configuration Override)// 允許在 單個 HTTP 請求 中覆蓋全局配置,優先級高于客戶端默認設置。// 適用于需要動態調整參數的場景(如特定請求的超時、代理或認證)。RequestConfig requestConfig = RequestConfig.copy(defaultRequestConfig).setSocketTimeout(5000).setConnectTimeout(5000).setConnectionRequestTimeout(5000).setProxy(new HttpHost("myotherproxy", 8080)).build();httpget.setConfig(requestConfig);// 本地化定制執行上下文(Execution Context)// 允許在 單個請求的上下文 中覆蓋或擴展全局配置,實現請求級別的個性化設置(如動態路由、自定義狀態管理)。HttpClientContext context = HttpClientContext.create();// 設置再本地化執行上下文的屬性比設置再客戶端級別的優先級高context.setCookieStore(cookieStore);context.setCredentialsProvider(credentialsProvider);System.out.println("executing request " + httpget.getURI());CloseableHttpResponse response = httpclient.execute(httpget, context);try {System.out.println("----------------------------------------");System.out.println(response.getStatusLine());System.out.println(EntityUtils.toString(response.getEntity()));System.out.println("----------------------------------------");// 請求執行后檢查上下文狀態// Once the request has been executed the local context can// be used to examine updated state and various objects affected// by the request execution.// Last executed requestcontext.getRequest();// Execution routecontext.getHttpRoute();// Target auth statecontext.getTargetAuthState();// Proxy auth statecontext.getProxyAuthState();// Cookie origincontext.getCookieOrigin();// Cookie spec usedcontext.getCookieSpec();// User security tokencontext.getUserToken();} finally {response.close();}} finally {httpclient.close();}}}
請求中止
此示例演示如何在HTTP請求正常完成之前中止該請求。
/*** This example demonstrates how to abort an HTTP method before its normal completion.*/
public class ClientAbortMethod {public final static void main(String[] args) throws Exception {CloseableHttpClient httpclient = HttpClients.createDefault();try {HttpGet httpget = new HttpGet("http://localhost:8080/test02?time=5000");System.out.println("Executing request " + httpget.getURI());CloseableHttpResponse response = httpclient.execute(httpget);try {System.out.println("----------------------------------------");System.out.println(response.getStatusLine());// 如果不打算讀取響應正文,則對請求對象調用abort方法// Do not feel like reading the response body// Call abort on the request objecthttpget.abort();} finally {response.close();}} finally {httpclient.close();}}}
客戶端鑒權
下面示例就是basic認證
/*** A simple example that uses HttpClient to execute an HTTP request against* a target site that requires user authentication.*/
public class ClientAuthentication {public static void main(String[] args) throws Exception {CredentialsProvider credsProvider = new BasicCredentialsProvider();credsProvider.setCredentials(new AuthScope("httpbin.org", 80),new UsernamePasswordCredentials("user", "passwd"));CloseableHttpClient httpclient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();try {HttpGet httpget = new HttpGet("http://httpbin.org/basic-auth/user/passwd");System.out.println("Executing request " + httpget.getRequestLine());CloseableHttpResponse response = httpclient.execute(httpget);try {System.out.println("----------------------------------------");System.out.println(response.getStatusLine());System.out.println(EntityUtils.toString(response.getEntity()));} finally {response.close();}} finally {httpclient.close();}}
}
代理請求
演示如何通過代理發送HTTP請求。
這段代碼通過本地 8080 端口的代理服務器,向 https://httpbin.org 發送一個 GET 請求,并打印響應內容。httpbin.org 是一個用于測試 HTTP 請求和響應的服務。
/*** How to send a request via proxy.** @since 4.0*/
public class ClientExecuteProxy {public static void main(String[] args)throws Exception {CloseableHttpClient httpclient = HttpClients.createDefault();try {HttpHost target = new HttpHost("httpbin.org", 443, "https");HttpHost proxy = new HttpHost("127.0.0.1", 8080, "http");RequestConfig config = RequestConfig.custom().setProxy(proxy).build();HttpGet request = new HttpGet("/");request.setConfig(config);System.out.println("Executing request " + request.getRequestLine() + " to " + target + " via " + proxy);CloseableHttpResponse response = httpclient.execute(target, request);try {System.out.println("----------------------------------------");System.out.println(response.getStatusLine());System.out.println(EntityUtils.toString(response.getEntity()));} finally {response.close();}} finally {httpclient.close();}}}
代理鑒權
一個簡單的示例顯示了通過身份驗證代理隧道的安全連接上執行HTTP請求。
/*** A simple example that uses HttpClient to execute an HTTP request* over a secure connection tunneled through an authenticating proxy.*/
public class ClientProxyAuthentication {public static void main(String[] args) throws Exception {CredentialsProvider credsProvider = new BasicCredentialsProvider();credsProvider.setCredentials(new AuthScope("localhost", 8888),new UsernamePasswordCredentials("squid", "squid"));credsProvider.setCredentials(new AuthScope("httpbin.org", 80),new UsernamePasswordCredentials("user", "passwd"));CloseableHttpClient httpclient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();try {HttpHost target = new HttpHost("httpbin.org", 80, "http");HttpHost proxy = new HttpHost("localhost", 8888);RequestConfig config = RequestConfig.custom().setProxy(proxy).build();HttpGet httpget = new HttpGet("/basic-auth/user/passwd");httpget.setConfig(config);System.out.println("Executing request " + httpget.getRequestLine() + " to " + target + " via " + proxy);CloseableHttpResponse response = httpclient.execute(target, httpget);try {System.out.println("----------------------------------------");System.out.println(response.getStatusLine());System.out.println(EntityUtils.toString(response.getEntity()));} finally {response.close();}} finally {httpclient.close();}}
}
塊編碼流式傳輸請求實體(文件傳輸)
此示例顯示如何使用塊編碼流式傳輸請求實體。
/*** Example how to use unbuffered chunk-encoded POST request.*/
public class ClientChunkEncodedPost {public static void main(String[] args) throws Exception {CloseableHttpClient httpclient = HttpClients.createDefault();try {// HttpPost httppost = new HttpPost("http://httpbin.org/post");HttpPost httppost = new HttpPost("http://127.0.0.1:8080/test03");File file = new File("C:\\Users\\zzhua195\\Desktop\\新建文本文檔.txt");// 在這里,使用FileEntity類可能更合適,// 但這里用的是更通用的InputStreamEntity,以展示從任意數據源流式傳輸數據的能力。InputStreamEntity reqEntity = new InputStreamEntity(new FileInputStream(file), -1, ContentType.APPLICATION_OCTET_STREAM);reqEntity.setChunked(true);// FileEntity reqEntity = new FileEntity(file, "binary/octet-stream");httppost.setEntity(reqEntity);// Executing request: POST http://127.0.0.1:8080/test03 HTTP/1.1System.out.println("Executing request: " + httppost.getRequestLine());CloseableHttpResponse response = httpclient.execute(httppost);try {System.out.println("----------------------------------------");// HTTP/1.1 200 System.out.println(response.getStatusLine());// 響應內容System.out.println(EntityUtils.toString(response.getEntity()));/* EntityUtils#toStringfinal Reader reader = new InputStreamReader(inStream, charset);final CharArrayBuffer buffer = new CharArrayBuffer(capacity);final char[] tmp = new char[1024];int l;while((l = reader.read(tmp)) != -1) {buffer.append(tmp, 0, l);}return buffer.toString();*/} finally {response.close();}} finally {httpclient.close();}}}
可以這樣收到傳輸的數據(比如,傳的是個文本)
@RequestMapping("test03")
public Object test03(HttpServletRequest request) throws Exception {StringBuilder out = new StringBuilder();InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8);char[] buffer = new char[4 * 1024];int bytesRead = -1;while ((bytesRead = reader.read(buffer)) != -1) {out.append(buffer, 0, bytesRead);}return out.toString();
}
多部分編碼請求實體(文件上傳)
(需要引入httpmime-4.5.14的jar依賴)
此示例顯示如何執行包含多部分編碼實體的請求。
/*** Example how to use multipart/form encoded POST request.*/
public class ClientMultipartFormPost {public static void main(String[] args) throws Exception {CloseableHttpClient httpclient = HttpClients.createDefault();try {HttpPost httppost = new HttpPost("http://localhost:8080/multipart01");// HttpPost httppost = new HttpPost("http://localhost:8080/multipart02");FileBody bin = new FileBody(new File("C:\\Users\\zzhua195\\Desktop\\test.png"));StringBody comment = new StringBody("A binary file of some kind", ContentType.TEXT_PLAIN);HttpEntity reqEntity = MultipartEntityBuilder.create().addPart("bin", bin).addPart("comment", comment).build();httppost.setEntity(reqEntity);System.out.println("executing request " + httppost.getRequestLine());CloseableHttpResponse response = httpclient.execute(httppost);try {System.out.println("----------------------------------------");System.out.println(response.getStatusLine());HttpEntity resEntity = response.getEntity();if (resEntity != null) {System.out.println("Response content length: " + resEntity.getContentLength());}EntityUtils.consume(resEntity);} finally {response.close();}} finally {httpclient.close();}}}
對應的后端代碼
@RequestMapping("multipart01")public Object multipart(@RequestPart("bin") MultipartFile file,@RequestPart("comment") String comment) throws InterruptedException, IOException {System.out.println(file.getBytes().length);System.out.println(comment);return "ojbk";}@RequestMapping("multipart02")public Object multipart(MultipartDTO multipartDTO) throws InterruptedException, IOException {System.out.println(multipartDTO.getBin().getBytes().length);System.out.println(multipartDTO.getComment());return "ojbk";}
自定義執行上下文
此示例演示了使用本地HTTP上下文填充的自定義屬性。
/*** This example demonstrates the use of a local HTTP context populated with* custom attributes.*/
public class ClientCustomContext {public final static void main(String[] args) throws Exception {CloseableHttpClient httpclient = HttpClients.createDefault();try {// 創建1個本地的cookie存儲CookieStore cookieStore = new BasicCookieStore();// 創建1個本地的http上下文HttpClientContext localContext = HttpClientContext.create();// 將cookie存儲綁定到本地的http上下文localContext.setCookieStore(cookieStore);// HttpGet httpget = new HttpGet("http://httpbin.org/cookies");HttpGet httpget = new HttpGet("http://localhost:8080/cookie");// Executing request GET http://localhost:8080/cookie HTTP/1.1System.out.println("Executing request " + httpget.getRequestLine());// 將本地的http客戶端上下文 作為參數 傳入CloseableHttpResponse response = httpclient.execute(httpget, localContext);try {System.out.println("----------------------------------------");// HTTP/1.1 200 System.out.println(response.getStatusLine());List<Cookie> cookies = cookieStore.getCookies();for (int i = 0; i < cookies.size(); i++) {// Local cookie: [version: 0][name: name][value: zzhua][domain: localhost][path: /][expiry: null]System.out.println("Local cookie: " + cookies.get(i));}// EntityUtils.consume(response.getEntity());System.out.println(EntityUtils.toString(response.getEntity()));} finally {response.close();}} finally {httpclient.close();}}}
在響應中寫入cookie
@RequestMapping("cookie")
public Object cookie(HttpServletResponse response) throws InterruptedException {response.addCookie(new Cookie("name", "zzhua"));return "ojbk";
}
基于表單的登錄
此示例演示了如何使用HttpClient執行基于表單的登錄。
/*** A example that demonstrates how HttpClient APIs can be used to perform* form-based logon.*/
public class ClientFormLogin {public static void main(String[] args) throws Exception {BasicCookieStore cookieStore = new BasicCookieStore();// 自定義http客戶端CloseableHttpClient httpclient = HttpClients.custom()// 設置cookieStore.setDefaultCookieStore(cookieStore).build();try {HttpGet httpget = new HttpGet("http://localhost:8080/formLogin");CloseableHttpResponse response1 = httpclient.execute(httpget);try {HttpEntity entity = response1.getEntity();// Login form get: HTTP/1.1 200System.out.println("Login form get: " + response1.getStatusLine());EntityUtils.consume(entity);System.out.println("Initial set of cookies:");List<Cookie> cookies = cookieStore.getCookies();if (cookies.isEmpty()) {System.out.println("None");} else {for (int i = 0; i < cookies.size(); i++) {System.out.println("- " + cookies.get(i).toString());}}} finally {response1.close();}// 使用RequestBuilder構建請求對象HttpUriRequest login = RequestBuilder.post().setUri(new URI("http://localhost:8080/formLogin"))// 其實就是表單登錄來的方式,同 QuickStart02.addParameter("username", "IDToken1").addParameter("password", "IDToken2").build();CloseableHttpResponse response2 = httpclient.execute(login);try {HttpEntity entity = response2.getEntity();// Login form get: HTTP/1.1 200System.out.println("Login form get: " + response2.getStatusLine());EntityUtils.consume(entity);System.out.println("Post logon cookies:");List<Cookie> cookies = cookieStore.getCookies();if (cookies.isEmpty()) {System.out.println("None");} else {for (int i = 0; i < cookies.size(); i++) {System.out.println("- " + cookies.get(i).toString());}}} finally {response2.close();}} finally {httpclient.close();}}
}
上面發請求其實就是表單登錄來的方式
@RequestMapping("formLogin")
public Object formLogin(LoginForm loginForm, HttpServletResponse response) {System.out.println(loginForm);if (loginForm != null && !StringUtils.isEmpty(loginForm.getUsername())) {Cookie cookie = new Cookie("token", "123456");response.addCookie(cookie);}return "ojbk";
}
多線程請求執行
執行來自多個工作線程的HTTP請求的示例。
(當多個線程同時訪問httpClient時,必須使用連接管理器!!!)
/*** An example that performs GETs from multiple threads.**/
public class ClientMultiThreadedExecution {public static void main(String[] args) throws Exception {// 使用線程安全的連接管理器 來創建 httpClient// 當多個線程同時訪問httpClient時,必須使用連接管理器!!!PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();cm.setMaxTotal(100);CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(cm).build();try {// 發起多個請求的uri數組String[] urisToGet = {"http://hc.apache.org/","http://hc.apache.org/httpcomponents-core-ga/","http://hc.apache.org/httpcomponents-client-ga/",};// 每個uri都用單獨的線程去發請求GetThread[] threads = new GetThread[urisToGet.length];for (int i = 0; i < threads.length; i++) {HttpGet httpget = new HttpGet(urisToGet[i]);// 使用同1個httpclient發請求threads[i] = new GetThread(httpclient, httpget, i + 1);}// start the threadsfor (int j = 0; j < threads.length; j++) {threads[j].start();}// join the threadsfor (int j = 0; j < threads.length; j++) {threads[j].join();}// 等待所有線程執行完畢,關閉httpclient} finally {httpclient.close();}}/*** A thread that performs a GET.*/static class GetThread extends Thread {private final CloseableHttpClient httpClient;private final HttpContext context;private final HttpGet httpget;private final int id;public GetThread(CloseableHttpClient httpClient, HttpGet httpget, int id) {this.httpClient = httpClient;this.context = new BasicHttpContext();this.httpget = httpget;this.id = id;}/*** Executes the GetMethod and prints some status information.*/@Overridepublic void run() {try {System.out.println(id + " - about to get something from " + httpget.getURI());CloseableHttpResponse response = httpClient.execute(httpget, context);try {System.out.println(id + " - get executed");// get the response body as an array of bytesHttpEntity entity = response.getEntity();if (entity != null) {byte[] bytes = EntityUtils.toByteArray(entity);System.out.println(id + " - " + bytes.length + " bytes read");}} finally {response.close();}} catch (Exception e) {System.out.println(id + " - error: " + e);}}}}
刪除過期連接
/*** Example demonstrating how to evict expired and idle connections* from the connection pool.*/
public class ClientEvictExpiredConnections {public static void main(String[] args) throws Exception {PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();cm.setMaxTotal(100);CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(cm).evictExpiredConnections().evictIdleConnections(5L, TimeUnit.SECONDS).build();try {// create an array of URIs to perform GETs onString[] urisToGet = {"http://hc.apache.org/","http://hc.apache.org/httpcomponents-core-ga/","http://hc.apache.org/httpcomponents-client-ga/",};for (int i = 0; i < urisToGet.length; i++) {String requestURI = urisToGet[i];HttpGet request = new HttpGet(requestURI);System.out.println("Executing request " + requestURI);CloseableHttpResponse response = httpclient.execute(request);try {System.out.println("----------------------------------------");System.out.println(response.getStatusLine());System.out.println(EntityUtils.toString(response.getEntity()));} finally {response.close();}}PoolStats stats1 = cm.getTotalStats();System.out.println("Connections kept alive: " + stats1.getAvailable());// Sleep 10 sec and let the connection evictor do its jobThread.sleep(10000);PoolStats stats2 = cm.getTotalStats();System.out.println("Connections kept alive: " + stats2.getAvailable());} finally {httpclient.close();}}}
自定義SSL上下文
此示例演示如何使用自定義SSL上下文創建安全連接。
/*** This example demonstrates how to create secure connections with a custom SSL* context.*/
public class ClientCustomSSL {public final static void main(String[] args) throws Exception {// Trust own CA and all self-signed certsSSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(new File("my.keystore"), "nopassword".toCharArray(),new TrustSelfSignedStrategy()).build();// Allow TLSv1 protocol onlySSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,new String[] { "TLSv1" },null,SSLConnectionSocketFactory.getDefaultHostnameVerifier());CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();try {HttpGet httpget = new HttpGet("https://httpbin.org/");System.out.println("Executing request " + httpget.getRequestLine());CloseableHttpResponse response = httpclient.execute(httpget);try {HttpEntity entity = response.getEntity();System.out.println("----------------------------------------");System.out.println(response.getStatusLine());EntityUtils.consume(entity);} finally {response.close();}} finally {httpclient.close();}}}
代理隧道
此示例顯示如何使用ProxyClient通過HTTP代理為任意協議建立隧道。
/*** Example code for using {@link ProxyClient} in order to establish a tunnel through an HTTP proxy.*/
public class ProxyTunnelDemo {public final static void main(String[] args) throws Exception {ProxyClient proxyClient = new ProxyClient();HttpHost target = new HttpHost("www.yahoo.com", 80);HttpHost proxy = new HttpHost("localhost", 8888);UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("user", "pwd");Socket socket = proxyClient.tunnel(proxy, target, credentials);try {Writer out = new OutputStreamWriter(socket.getOutputStream(), HTTP.DEF_CONTENT_CHARSET);out.write("GET / HTTP/1.1\r\n");out.write("Host: " + target.toHostString() + "\r\n");out.write("Agent: whatever\r\n");out.write("Connection: close\r\n");out.write("\r\n");out.flush();BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), HTTP.DEF_CONTENT_CHARSET));String line = null;while ((line = in.readLine()) != null) {System.out.println(line);}} finally {socket.close();}}}
異步請求
@Slf4j
public class ClientWithRequestFuture {public static void main(String[] args) throws Exception {// 創建異步請求最簡單的方式HttpClient httpclient = HttpClientBuilder.create().setMaxConnPerRoute(5).setMaxConnTotal(5).build();ExecutorService execService = Executors.newFixedThreadPool(5);FutureRequestExecutionService requestExecService = new FutureRequestExecutionService(httpclient, execService);try {// 因為所有東西都是異步的,因此需要提供1個響應處理器ResponseHandler<Boolean> handler = new ResponseHandler<Boolean>() {@Overridepublic Boolean handleResponse(HttpResponse response) throws ClientProtocolException, IOException {log.info("ResponseHandler#handleResponse: {}", response.getStatusLine().getStatusCode());log.info("ResponseHandler#handleResponse: {}", EntityUtils.toString(response.getEntity()));// simply return true if the status was OKreturn response.getStatusLine().getStatusCode() == 200;}};// 《發起1個異步請求》HttpGet request1 = new HttpGet("http://httpbin.org/get");log.info("發請第1個請求 start");HttpRequestFutureTask<Boolean> futureTask1 = requestExecService.execute(request1, HttpClientContext.create(), handler);log.info("發請第1個請求 end");Boolean wasItOk1 = futureTask1.get();log.info("It was ok? {}", wasItOk1);// 《取消1個異步請求》try {HttpGet request2 = new HttpGet("http://httpbin.org/get");log.info("發請第2個請求 start");HttpRequestFutureTask<Boolean> futureTask2 = requestExecService.execute(request2, HttpClientContext.create(), handler);log.info("發請第2個請求 end");futureTask2.cancel(true);log.info("取消第2個請求");Boolean wasItOk2 = futureTask2.get();log.info("It was cancelled so it should never print this: {}", wasItOk2);} catch (CancellationException e) {log.info("We cancelled it, so this is expected");}// 《異步請求超時》HttpGet request3 = new HttpGet("http://httpbin.org/get");log.info("發請第3個請求 start");HttpRequestFutureTask<Boolean> futureTask3 = requestExecService.execute(request3,HttpClientContext.create(), handler);log.info("發請第3個請求 end");// 在指定的時間內,沒有響應,則拋出超時異常Boolean wasItOk3 = futureTask3.get(100, TimeUnit.MILLISECONDS);log.info("It was ok? {}", wasItOk3);//《請求回調》// 回調的執行與handler的執行是同1個線程,先執行handler,再執行回調FutureCallback<Boolean> callback = new FutureCallback<Boolean>() {@Overridepublic void completed(Boolean result) {log.info("completed with " + result);}@Overridepublic void failed(Exception ex) {log.info("failed with " + ex.getMessage());}@Overridepublic void cancelled() {log.info("cancelled");}};// 發起簡單請求時,添加1個回調HttpGet request4 = new HttpGet("http://httpbin.org/get");// 當任務 完成、失敗、取消 時,回調會被執行HttpRequestFutureTask<Boolean> futureTask4 = requestExecService.execute(request4, HttpClientContext.create(), handler, callback);Boolean wasItOk4 = futureTask4.get(10, TimeUnit.SECONDS);System.out.println("It was ok? " + wasItOk4);} finally {requestExecService.close();}}
}
Fluent流式
FluentRequests
/*** This example demonstrates basics of request execution with the HttpClient fluent API.*/
public class FluentRequests {public static void main(String[] args)throws Exception {// Execute a GET with timeout settings and return response content as String.Request.Get("http://somehost/").connectTimeout(1000).socketTimeout(1000).execute().returnContent().asString();// Execute a POST with the 'expect-continue' handshake, using HTTP/1.1,// containing a request body as String and return response content as byte array.Request.Post("http://somehost/do-stuff").useExpectContinue().version(HttpVersion.HTTP_1_1).bodyString("Important stuff", ContentType.DEFAULT_TEXT).execute().returnContent().asBytes();// Execute a POST with a custom header through the proxy containing a request body// as an HTML form and save the result to the fileRequest.Post("http://somehost/some-form").addHeader("X-Custom-header", "stuff").viaProxy(new HttpHost("myproxy", 8080)).bodyForm(Form.form().add("username", "vip").add("password", "secret").build()).execute().saveContent(new File("result.dump"));}
FluentResponseHandling
/*** This example demonstrates how the HttpClient fluent API can be used to handle HTTP responses* without buffering content body in memory.*/
public class FluentResponseHandling {public static void main(String[] args)throws Exception {Document result = Request.Get("http://somehost/content").execute().handleResponse(new ResponseHandler<Document>() {@Overridepublic Document handleResponse(final HttpResponse response) throws IOException {StatusLine statusLine = response.getStatusLine();HttpEntity entity = response.getEntity();if (statusLine.getStatusCode() >= 300) {throw new HttpResponseException(statusLine.getStatusCode(),statusLine.getReasonPhrase());}if (entity == null) {throw new ClientProtocolException("Response contains no content");}DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();try {DocumentBuilder docBuilder = dbfac.newDocumentBuilder();ContentType contentType = ContentType.getOrDefault(entity);if (!contentType.equals(ContentType.APPLICATION_XML)) {throw new ClientProtocolException("Unexpected content type:" + contentType);}Charset charset = contentType.getCharset();if (charset == null) {charset = Consts.ISO_8859_1;}return docBuilder.parse(entity.getContent(), charset.name());} catch (ParserConfigurationException ex) {throw new IllegalStateException(ex);} catch (SAXException ex) {throw new ClientProtocolException("Malformed XML document", ex);}}});// Do something useful with the resultSystem.out.println(result);}}
FluentExecutor
/*** This example demonstrates how the he HttpClient fluent API can be used to execute multiple* requests within the same security context. The Executor class maintains a common context shared* by all requests executed with it. The Executor is thread-safe and can be used to execute* requests concurrently from multiple threads of execution.*/
public class FluentExecutor {public static void main(String[] args)throws Exception {Executor executor = Executor.newInstance().auth(new HttpHost("somehost"), "username", "password").auth(new HttpHost("myproxy", 8080), "username", "password").authPreemptive(new HttpHost("myproxy", 8080));// Execute a GET with timeout settings and return response content as String.executor.execute(Request.Get("http://somehost/").connectTimeout(1000).socketTimeout(1000)).returnContent().asString();// Execute a POST with the 'expect-continue' handshake, using HTTP/1.1,// containing a request body as String and return response content as byte array.executor.execute(Request.Post("http://somehost/do-stuff").useExpectContinue().version(HttpVersion.HTTP_1_1).bodyString("Important stuff", ContentType.DEFAULT_TEXT)).returnContent().asBytes();// Execute a POST with a custom header through the proxy containing a request body// as an HTML form and save the result to the fileexecutor.execute(Request.Post("http://somehost/some-form").addHeader("X-Custom-header", "stuff").viaProxy(new HttpHost("myproxy", 8080)).bodyForm(Form.form().add("username", "vip").add("password", "secret").build())).saveContent(new File("result.dump"));}}
FluentAsync
/*** This example demonstrates how the he HttpClient fluent API can be used to execute multiple* requests asynchronously using background threads.*/
public class FluentAsync {public static void main(String[] args)throws Exception {// Use pool of two threadsExecutorService threadpool = Executors.newFixedThreadPool(2);Async async = Async.newInstance().use(threadpool);Request[] requests = new Request[] {Request.Get("http://www.google.com/"),Request.Get("http://www.yahoo.com/"),Request.Get("http://www.apache.com/"),Request.Get("http://www.apple.com/")};Queue<Future<Content>> queue = new LinkedList<Future<Content>>();// Execute requests asynchronouslyfor (final Request request: requests) {Future<Content> future = async.execute(request, new FutureCallback<Content>() {@Overridepublic void failed(final Exception ex) {System.out.println(ex.getMessage() + ": " + request);}@Overridepublic void completed(final Content content) {System.out.println("Request completed: " + request);}@Overridepublic void cancelled() {}});queue.add(future);}while(!queue.isEmpty()) {Future<Content> future = queue.remove();try {future.get();} catch (ExecutionException ex) {}}System.out.println("Done");threadpool.shutdown();}}