HttpClient以及連接池的使用


一、開始使用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、 為什么使用連接池

  1. 降低延遲:如果不采用連接池,每次連接發起Http請求的時候都會重新建立TCP連接(經歷3次握手),用完就會關閉連接(4次揮手),如果采用連接池則減少了這部分時間損耗
  2. 支持更大的並發:如果不采用連接池,每次連接都會打開一個端口,在大並發的情況下系統的端口資源很快就會被用完,導致無法建立新的連接

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/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM