一、開始使用httpclient
本文檔需要使用的依賴有如下幾個:
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.10</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpmime</artifactId> <version>4.5.10</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> <version>4.4.11</version> </dependency>
1、創建HttpClient對象。
創建httpclient對象的方式有兩種,
第一種是使用默認配置創建:
CloseableHttpClient client = HttpClients.createDefault();
第二種是使用HttpClients.custom()定制:
HttpClients.custom().setDefaultRequestConfig(RequestConfig)
這其中RequestConfig的設計方式可以學習一下。RequestConfig只有一個默認訪問修飾符的構造函,這就意味着我們在使用這個類的時候不能之間構建示例;RequestConfig有很多私有屬性,這些屬性沒有get/set方法,所以,即便是我們拿到了RequestConfig類的實例,也不能去修改其中的屬性值。那么RequestConfig是如何構造實例並且為屬性賦值的呢?RequestConfig在實例化的時候會利用其公共內部類Builder。使用方式為RequestConfig.custom().setXXX().builder()。調用custom方法返回一個Builder類實例,改類里有設置RequestConfig同名屬性的各種方法,設置完畢后調用builder方法構造一個新的RequestConfig實例。從這個過程中可以看出來,RequestConfig對象的屬性是不可變的。
有幾個參數我們自己必須設置一下:
(1)connectionRequestTimeout:從連接池中獲取連接的超時時間,超過該時間未拿到可用連接,會拋出:
ConnectionPoolTimeoutException: Timeout waiting for connection from pool
(2)connectTimeout:連接上服務器(握手成功)的時間,超出該時間拋出connect timeout
(3)socketTimeout:服務器返回數據(response)的時間,超過該時間拋出read timeout
以上3個超時相關的參數如果未配置,默認為-1,意味着無限大,就是一直阻塞等待。
2、創建請求方法的實例
創建請求方法的實例並指定請求URL。如果需要發送GET請求,創建HttpGet對象;如果需要發送POST請求,創建HttpPost對象:
HttpGet httpGet = new HttpGet("https://www.baidu.com");
3、組裝請求參數
對於get請求,可以直接在構建HttpGet對象的時候在url中加入,也可以使用URIBuilder構建參數,具體可以參考下面的兩種方式:
1 URIBuilder builder = new URIBuilder("http://example.com/"); 2 builder.setParameter("var1", "value1").setParameter("var2", "value2"); 3 HttpGet request = new HttpGet(builder.build());
或使用了建造者模式的URIBuilder
URI uri = new URIBuilder() .setScheme("http") .setHost("www.example.cn") .setPath("/search") .setParameter("var1", "value1") .build();
對於HttpPost對象而言,可調用setEntity(HttpEntity entity)方法來設置請求參數:
//拼接參數 List<NameValuePair> params= new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair("username", "vip")); params.add(new BasicNameValuePair("password", "secret")); httpPost.setEntity(new UrlEncodedFormEntity(params));
其中setEntity的參數種類可以有很多種,比如:
JSONObject params = new JSONObject(); params.put("var1", "value1"); params.put("var2", "value2"); httpPost.setEntity(new StringEntity(params.toJSONString(), "utf-8"));
或者使用mutltipar形式傳輸參數:
InputStream in = httpResponse.getEntity().getContent(); InputStreamBody inputStreamBody = new InputStreamBody(in, "filename"); HttpEntity entity = MultipartEntityBuilder.create() .addPart("image", inputStreamBody) .addPart("sign", new StringBody("", ContentType.APPLICATION_XML)) .build();
使用MultipartEntityBuilder需要依賴httpmime包。其中的inputStreamBody是從http查詢返回中獲得的一個圖片的輸入流。
4、發送請求
調用HttpClient對象的execute(HttpUriRequest request)發送請求,該方法返回一個HttpResponse:
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
5、獲取返回內容
調用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可獲取服務器的響應頭;調用HttpResponse的getEntity()方法可獲取HttpEntity對象,該對象包裝了服務器的響應內容。程序可通過該對象獲取服務器的響應內容。
// 獲取響應狀態碼 int statusCode = httpResponse.getStatusLine().getStatusCode(); // 獲取返回內容 HttpEntity httpEntity = httpResponse.getEntity(); String content = EntityUtils.toString(httpEntity);
6、釋放連接。
無論執行方法是否成功,都必須釋放連接
httpClient.close();
二、http連接池
1、 為什么使用連接池
- 降低延遲:如果不采用連接池,每次連接發起Http請求的時候都會重新建立TCP連接(經歷3次握手),用完就會關閉連接(4次揮手),如果采用連接池則減少了這部分時間損耗
- 支持更大的並發:如果不采用連接池,每次連接都會打開一個端口,在大並發的情況下系統的端口資源很快就會被用完,導致無法建立新的連接
2、 創建連接池
PoolingHttpClientConnectionManager cm = null; LayeredConnectionSocketFactory sslsf = null; try { sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create() .register("https", sslsf) .register("http", new PlainConnectionSocketFactory()) .build(); cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry); cm.setMaxTotal(200); cm.setDefaultMaxPerRoute(20); CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(cm) .build();
三、注意事項
1、 錯誤關閉連接
連接池中連接都是在發起請求的時候建立,並且都是長連接。Client.java[附錄]中的in.close()作用就是將用完的連接釋放,下次請求可以復用,這里特別注意的是,如果不使用in.close()而僅僅使用response.close(),結果就是連接會被關閉,並且不能被復用,這樣就失去了采用連接池的意義。
連接池釋放連接的時候,並不會直接對TCP連接的狀態有任何改變,只是維護了兩個Set,leased和avaliabled,leased代表被占用的連接集合,avaliabled代表可用的連接的集合,釋放連接的時候僅僅是將連接從leased中remove掉了,並把連接放到avaliabled集合中。
2、 未關閉連接
try{ HttpClient httpClient = HttpClientBuilder.create().build(); HttpPost request = new HttpPost(httpUrl); StringEntity params = new StringEntity(new ObjectMapper().writeValueAsString(body), "UTF-8"); request.addHeader("content-type", "application/json"); request.setEntity(params); httpClient.execute(request); //問題代碼 } catch (Exception ex) { logger.error("Http post error. body={}", body, ex); }
上段代碼中,httpClient.execute()執行的返回值response沒有被close,導致線程一直在等待。可以改用CloseableHttpClient,並保證httpClient.execute()執行的response最終被close掉。而httpClient無需close,可以重復使用。如果使用CloseableHttpResponse,則response的close也不用顯示調用。
CloseableHttpClient httpClient = HttpClientBuilder.create().build(); HttpPost request = new HttpPost(httpUrl); StringEntity params = new StringEntity(new ObjectMapper().writeValueAsString(body), "UTF-8"); request.addHeader("content-type", "application/json"); request.setEntity(params); //httpClient.execute(request); try { CloseableHttpResponse response = httpClient.execute(request); //response.close()會被自動調用 } catch (Exception ex) { logger.error("Http post execute error. body={}", body, ex); }
四、附錄
1、client.java
public class Client { HttpConnectionManager connManager; public <T> T get(String path,Class<T> clazz){ CloseableHttpClient httpClient=connManager.getHttpClient(); HttpGet httpget = new HttpGet(path); String json=null; CloseableHttpResponse response=null; try { response = httpClient.execute(httpget); InputStream in=response.getEntity().getContent(); json=IOUtils.toString(in); in.close(); } catch (UnsupportedOperationException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { if(response!=null){ try { response.close(); } catch (IOException e) { e.printStackTrace(); } } } return JSON.parseObject(json, clazz); } }
參考:
HttpClient中文網.http://www.httpclient.cn/