說明
本文示例代碼基於 4.5.13 版本
轉載請注明出處:https://www.cnblogs.com/qnlcy/p/15378446.html
一、項目介紹
Apache 提供用來做http請求的項目有兩個,3.x 版本的項目叫做 The Commons HttpClient
。
它一開始是 Apache Jakarta Common
下的子項目,后來獨立出去了,現在這個項目已經結束了它的生命周期,不再開發和維護。
取而代之的是 4.x 版本的 Apache Httpcomponents
項目,它包括 HttpClient
和 HttpCore
兩大模塊,能提供更好的性能和更大的靈活性。
二、項目模塊
Apache Httpcomponents
項目包括 HttpClient
和 HttpCore
兩大模塊,其中,HttpCore
是一套HTTP協議實現包。而 HttpClient
是基於HttpCore的一套客戶端。
三、使用方式
使用 Httpclient
需要經過如下步驟
- 創建
HttpClient
- 創建 http 請求,如
HttpGet
、HttpPost
- 添加請求參數
- 添加請求設置,如超時等
- 使用
HttpClient
執行 http 請求 - 讀取返回內容並釋放連接
3.1 創建 HttpClient
3.1.1 創建默認客戶端:
CloseableHttpClient httpclient = HttpClients.createDefault();
一些重要的默認配置:
- 默認連接池大小10,每域名最大連接5
- 連接池中連接存活時間
connTimeToLive = -1
,默認單位為毫秒,默認連接不失效 - 域名驗證器為
DefaultHostnameVerifier
, 會驗證域名 - SSL 上下文為
SSLContext.getInstance("TLS")
,沒有使用密鑰管理器(KeyManager
)和信任管理器(TrustManager
)
3.1.2 自定義客戶端
- 失敗不重試
CloseableHttpClient client = HttpClients.custom().setRetryHandler((e, i, c) -> false).build();
- 自定義連接池
//設置自定義連接池
@Test
public void customConnectionPool() throws Exception {
//1.創建 https 需要的 SslContext 相關內容
//1.1 創建 SslContext
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream("證書文件"), "密碼".toCharArray());
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(TrustAllStrategy.INSTANCE)
.loadKeyMaterial(ks, "證書密碼".toCharArray()).build();
//1.2 創建 SSLConnectionSocketFactory
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new String[]{"SSLv3", "TLSv1.1", "TLSv1.2"}, null,
NoopHostnameVerifier.INSTANCE);
//2.創建連接池
//2.1 構建協議 registry
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory)
.build();
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(registry);
//3.連接池針對所有連接、每域名的連接的數量設置
poolingHttpClientConnectionManager.setMaxTotal(100);
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(20);
//4.創建client
CloseableHttpClient client =
HttpClients.custom().setConnectionManager(poolingHttpClientConnectionManager).build();
}
3.2 創建 Http 請求
創建 HttpGet
、HttpPost
請求
@Test
public void getAndPost(){
//1.創建get請求
HttpGet get = new HttpGet("https://www.baidu.com");
//2.創建post請求
HttpPost post = new HttpPost("https://www.baidu.com");
//3.其他如 HttpPut、HttpOptions、HttpTrace、HttpDelete、HttpPatch
}
3.3 添加請求參數
@Test
public void addParams() throws IOException {
HttpPost post = new HttpPost("https://www.baidu.com");
//1.底層流,基礎參數
BasicHttpEntity basicHttpEntity = new BasicHttpEntity();
//1.1添加參數內容
InputStream bis = new ByteArrayInputStream("參數".getBytes());
basicHttpEntity.setContent(bis);
//1.2設置內容長度
basicHttpEntity.setContentLength(bis.available());
//1.3取消分塊發送
basicHttpEntity.setChunked(false);
post.setEntity(basicHttpEntity);
//2.字節碼類型參數
HttpEntity entity = new ByteArrayEntity("name=zhangsan&age=100".getBytes());
post.setEntity(entity);
//3.字符串類型參數
entity = new StringEntity("name=zhangsan&age=100");
post.setEntity(entity);
//4.流式參數,用法與BasicHttpEntity類似,內容和長度嚴格匹配
entity = new InputStreamEntity(bis,bis.available());
post.setEntity(entity);
//5.文件類型參數
entity = new FileEntity(new File("上傳文件"));
post.setEntity(entity);
//6.添加請求頭
post.addHeader("Content-Type","text/html;charset=UTF-8");
Header contentType = new BasicHeader("Content-Type","text/html;charset=UTF-8");
post.addHeader(contentType);
Header host = new BasicHeader("Host","www.baidu.com");
post.setHeaders(new Header[]{contentType,host});
}
3.4 添加請求設置
@Test
public void requestConfig(){
//1.配置RequestConfig
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(10000) //從連接池獲取可用連接的超時時間,單位毫秒
.setSocketTimeout(5000) //請求獲取數據的超時時間
.setConnectTimeout(4000) //連接超時時間
.build();
HttpPost post = new HttpPost("https://www.baidu.com");
//2.設置到post請求當中
post.setConfig(requestConfig);
//也可以當作默認值,設置到client當中,此client都會按這個超時處理
CloseableHttpClient client = HttpClients.custom().setDefaultRequestConfig(requestConfig).build();
}
3.4.1 超時時間說明
超時類型 | 說明 |
---|---|
connectionTimeout | 連接建立時間,即3次握手時間,默認值-1 |
socketTimeout | 連接后,數據傳輸過程中數據包之間間隔 的最大時間,默認值-1 |
connectionRequestTimeout | 從連接池獲取連接的超時時間,默認值-1 |
注意:
socketTimeout
和connectionRequestTimeout
如果不設置,請求會阻塞。但是
connectionTimeout
的情況有所不同,它依賴於各平台的socket
超時時間設置。windows 10 實測為 20s, linux 平台則不定,它會按
/proc/sys/net/ipv4/tcp_syn_retries
中配置的次數重試,一般為3s\7s\15s\31s\63s遞增另外,即使 java 程序返回了超時結果,但是linux服務器依舊在執行重試直到服務器端超時,為了提高資源利用率,可以手動關閉
關於 linux socket 超時的問題,請參閱 無毀的湖光-Al 的 從linux源碼看socket(tcp)的timeout
3.5 執行 http 請求
執行 http 請求比較簡單,直接調用 execute()
方法即可
@Test
public void execute(){
CloseableHttpClient client = HttpClients.createDefault();
try {
client.execute(new HttpPost("https://www.baidu.com"));
client.execute(new HttpGet("https://www.baidu.com"));
} catch (IOException e) {
e.printStackTrace();
}
}
3.6 讀取返回內容並釋放連接
服務器返回結果被封裝到 HttpResponse
對象里,我們可以從這里拿到我們想要的返回結果
@Test
public void getResponse() {
CloseableHttpClient client = HttpClients.createDefault();
CloseableHttpResponse httpResponse = null;
final HttpGet httpGet = new HttpGet("https://www.baidu.com");
try {
httpResponse = client.execute(httpGet);
//1.獲取返回狀態
System.out.println(httpResponse.getStatusLine().getStatusCode());
//2.獲取返回頭信息
Header[] headers = httpResponse.getAllHeaders();
for (Header header : headers) {
System.out.println(header.getName() + ":" + header.getValue());
}
//3.獲取返回消息體
HttpEntity entity = httpResponse.getEntity();
if(null != entity){
//3.1 得到返回結果並關閉流,與下面的只能執行一個,因為流只能讀取一次
String content = EntityUtils.toString(entity);
System.out.println(content);
//3.2 得到返回結果並關閉流,與上面的只能執行一個
// byte[] contents = EntityUtils.toByteArray(entity);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != httpResponse) {
//4.歸還連接到連接池
try {
httpResponse.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//如果復用 httpGet ,則重置其狀態使其可以重復使用
httpGet.releaseConnection();
}
//只在應用關閉的時候關閉client
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}