<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          HttpClient使用不當,服務掛了!是時候系統(tǒng)學習一下了

          共 10768字,需瀏覽 22分鐘

           ·

          2022-01-06 04:11

          c470a488b877ce2d74746d211bd5caf0.webp

          背景

          最近發(fā)生了兩件事,覺得有必要系統(tǒng)的學習一下Apache的HttpClient了。

          事件一:聯(lián)調(diào)微信支付接口,用到HttpClient,花時間整理了一番。如果有一篇文章,讀一讀就可以掌握HttpClient 80%的內(nèi)容,再有可以直接用的Demo,下次再遇到是不是就可以非常容易集成了?這篇便是這篇文章的目標之一。

          事件二:上家公司同事發(fā)消息求助,說系統(tǒng)JVM溢出,找不到原因不了。查看了發(fā)來的日志文件,基本定位是HttpClient調(diào)用三方接口時內(nèi)存溢出導致的。

          無論出于哪種原因,HTTP調(diào)用的熟練使用都是必不可少的,今天就來一起系統(tǒng)學習一下,查漏補缺。

          HttpClient

          HTTP協(xié)議的重要性不言而喻,它是現(xiàn)在Internet中使用最多,最重要的協(xié)議了。雖然JDK中已經(jīng)提供了HTTP協(xié)議的基本功能,但對于大部分應用來說,這套API還是不夠豐富和靈活。

          HttpClient是用來編程實現(xiàn)HTTP調(diào)用的一款框架,它是Apache Jakarta Common下的子項目,相比傳統(tǒng)JDK自帶的URLConnection,增加了易用性和靈活性。

          HttpClient不僅使客戶端發(fā)送Http請求變得更加容易,而且也方便了開發(fā)人員測試接口(基于Http協(xié)議的),即提高了開發(fā)的效率,也方便提高代碼的健壯性。

          目前主流的SpringCloud框架,服務與服務之間的調(diào)用也全部是基于HttpClient來實現(xiàn)的。因此,系統(tǒng)的學習一下HttpClient,還是非常有必要的。

          HttpClient功能及特性

          HttpClient主要提供了以下功能及特性實現(xiàn):

          • 基于標準、純凈的java語言。實現(xiàn)了HTTP 1.0和HTTP 1.1;
          • 以可擴展的面向?qū)ο蟮慕Y(jié)構(gòu)實現(xiàn)了HTTP全部的方法(GET、 POST、PUT、DELETE、HEAD、OPTIONS、TRACE)等。
          • 支持HTTPS協(xié)議。
          • 通過HTTP代理建立透明的連接。
          • 利用CONNECT方法通過HTTP代理建立隧道的HTTPs連接。
          • Basic, Digest, NTLMv1, NTLMv2, NTLM2 Session, SNPNEGO/Kerberos認證方案。
          • 插件式的自定義認證方案。
          • 便攜可靠的套接字工廠使它更容易的使用第三方解決方案。
          • 連接管理器支持多線程應用。支持設置最大連接數(shù),同時支持設置每個主機的最大連接數(shù),發(fā)現(xiàn)并關閉過期的連接。
          • 自動處理Set-Cookie中的Cookie。
          • 插件式的自定義Cookie策略。
          • Request的輸出流可以避免流中內(nèi)容直接緩沖到Socket服務器。
          • Response的輸入流可以有效的從Socket服務器直接讀取相應內(nèi)容。
          • 在HTTP 1.0和HTTP1.1中利用KeepAlive保持持久連接。
          • 直接獲取服務器發(fā)送的response code和 headers。
          • 設置連接超時的能力。
          • 實驗性的支持HTTP1.1 response caching。
          • 源代碼基于Apache License 可免費獲取。
          • 支持自動(跳轉(zhuǎn))轉(zhuǎn)向;

          關于以上特性,了解即可,用到時再進行深入學習和實踐。

          HttpClient使用步驟

          使用HttpClient來發(fā)送請求、接收響應通常有以下步驟:

          • 引入依賴:項目中通過Maven等形式引入HttpClient依賴類庫。
          • 創(chuàng)建HttpClient對象。
          • 創(chuàng)建請求方法實例:GET請求創(chuàng)建HttpGet對象,POST請求創(chuàng)建HttpPost對象,并在對象構(gòu)建時指定請求URL。
          • 設置請求參數(shù):調(diào)用HttpGet、HttpPost共同的setParams(HetpParams params)方法來添加請求參數(shù);HttpPost也可調(diào)用setEntity(HttpEntity entity)方法來設置請求參數(shù)。
          • 發(fā)送請求:調(diào)用HttpClient對象的execute(HttpUriRequest request)發(fā)送請求,該方法返回一個HttpResponse。
          • 獲取響應結(jié)果:調(diào)用HttpResponsegetAllHeaders()、getHeaders(String name)等方法獲取服務器的響應頭;調(diào)用HttpResponsegetEntity()方法可獲取HttpEntity對象,該對象包裝了服務器的響應內(nèi)容。
          • 釋放連接:無論執(zhí)行方法是否成功,都必須釋放連接。

          以上便是使用HttpClient的核心步驟:引入依賴、創(chuàng)建HttpClient對象、創(chuàng)建請求實例、設置請求參數(shù)、發(fā)送請求、獲取請求結(jié)果、釋放連接。

          文章剛開始提到的事件二,便是由于釋放連接不當導致連接累積導致內(nèi)存溢出。

          了解了HttpClient的使用步驟,就可以具體的代碼實現(xiàn)了。

          實例代碼實戰(zhàn)

          在項目中引入HttpClient依賴:


          ????org.apache.httpcomponents
          ????httpclient
          ????4.5.13

          Get請求示例

          先以Get請求為例,展示一下調(diào)用百度搜索Java關鍵字:

          ?@Test
          ?public?void?testGet()?throws?IOException?{
          ??//1、構(gòu)建HttpClient對象
          ??CloseableHttpClient?httpClient?=?HttpClients.createDefault();
          ??//2、創(chuàng)建HttpGet,聲明get請求
          ??HttpGet?httpGet?=?new?HttpGet("http://www.baidu.com/s?wd=java");
          ??//3、發(fā)送請求
          ??CloseableHttpResponse?response?=?httpClient.execute(httpGet);
          ??//4.判斷狀態(tài)碼
          ??if?(response.getStatusLine().getStatusCode()?==?200)?{
          ???HttpEntity?entity?=?response.getEntity();
          ???//?使用工具類EntityUtils,從響應中取出實體表示的內(nèi)容并轉(zhuǎn)換成字符串
          ???String?string?=?EntityUtils.toString(entity,?"utf-8");
          ???System.out.println(string);
          ??}
          ??//?5、關閉資源
          ??response.close();
          ??httpClient.close();
          ?}

          執(zhí)行上述代碼,HttpClient調(diào)用成功,控制臺會打印出百度返回結(jié)果的HTML信息。這個過程也遵循了上面說到的HttpClient的使用步驟。

          上述代碼看似能夠正常使用,但在執(zhí)行的過程中如果出現(xiàn)異常,則會出現(xiàn)連接無法正常釋放,導致內(nèi)存溢出問題

          對上述代碼進行改進:

          ?@Test
          ?public?void?testGet()?{

          ??CloseableHttpClient?httpClient?=?null;
          ??CloseableHttpResponse?response?=?null;

          ??try?{
          ???//1、構(gòu)建HttpClient對象
          ???httpClient?=?HttpClients.createDefault();
          ???//2、創(chuàng)建HttpGet,聲明get請求
          ???HttpGet?httpGet?=?new?HttpGet("http://www.baidu.com/s?wd=java");
          ???//3、發(fā)送請求
          ???response?=?httpClient.execute(httpGet);
          ???//4.判斷狀態(tài)碼
          ???if?(response.getStatusLine().getStatusCode()?==?200)?{
          ????HttpEntity?entity?=?response.getEntity();
          ????//?使用工具類EntityUtils,從響應中取出實體表示的內(nèi)容并轉(zhuǎn)換成字符串
          ????String?string?=?EntityUtils.toString(entity,?"utf-8");
          ????System.out.println(string);
          ???}
          ??}?catch?(Exception?e)?{
          ???//?打印堆棧信息,進行異常情況處理;
          ??}?finally?{
          ???//?5、關閉資源
          ???if?(response?!=?null)?{
          ????try?{
          ?????response.close();
          ????}?catch?(IOException?e)?{
          ?????e.printStackTrace();
          ????}
          ???}
          ???if?(httpClient?!=?null)?{
          ????try?{
          ?????httpClient.close();
          ????}?catch?(IOException?e)?{
          ?????e.printStackTrace();
          ????}
          ???}
          ??}
          ?}

          雖然代碼復雜了一些,但此時無論是否出現(xiàn)異常,都可以將連接進行正常的關閉,避免內(nèi)存溢出。

          在上述代碼中,其中HttpGet的參數(shù)是直接拼接到HTTP連接后面的,當然也可以通過URI來構(gòu)建,代碼實現(xiàn)如下:

          HttpGet?httpGet?=?new?HttpGet("http://www.baidu.com/s?wd=java");

          //?上述實現(xiàn)等價于下面的實現(xiàn);

          URI?uri?=?new?URIBuilder("http://www.baidu.com/s").setParameter("wd","java").build();
          HttpGet?httpGet?=?new?HttpGet(uri);

          當然,針對資源釋放部分,還可以利用Java 8提供的try-with-resources語法糖來進行簡化代碼。

          Post請求示例

          下面的實例中的Post請求相對Get請求,多了添加Header參數(shù)和Http的Entity參數(shù):

          ?@Test
          ?public?void?testPost(){
          ??CloseableHttpClient?httpClient?=?null;
          ??CloseableHttpResponse?response?=?null;
          ??try?{
          ???//1.打開瀏覽器
          ???httpClient?=?HttpClients.createDefault();
          ???//2.聲明get請求
          ???HttpPost?httpPost?=?new?HttpPost("https://www.oschina.net/");
          ???//3.網(wǎng)站為了防止惡意攻擊,在post請求中都限制了瀏覽器才能訪問
          ???httpPost.addHeader("User-Agent","Mozilla/5.0?(Windows?NT?10.0;?Win64;?x64)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/68.0.3440.106?Safari/537.36");
          ???//4.判斷狀態(tài)碼
          ???List?parameters?=?new?ArrayList<>(0);
          ???parameters.add(new?BasicNameValuePair("scope",?"project"));
          ???parameters.add(new?BasicNameValuePair("q",?"java"));

          ???UrlEncodedFormEntity?formEntity?=?new?UrlEncodedFormEntity(parameters,"UTF-8");
          ???httpPost.setEntity(formEntity);
          ???//5.發(fā)送請求
          ???response?=?httpClient.execute(httpPost);
          ???if(response.getStatusLine().getStatusCode()==200){
          ????HttpEntity?entity?=?response.getEntity();
          ????String?string?=?EntityUtils.toString(entity,?"utf-8");
          ????System.out.println(string);
          ???}
          ??}?catch?(Exception?e){
          ???//?打印堆棧信息,進行異常情況處理;
          ??}?finally?{
          ???//?5、關閉資源
          ???if?(response?!=?null)?{
          ????try?{
          ?????response.close();
          ????}?catch?(IOException?e)?{
          ?????e.printStackTrace();
          ????}
          ???}
          ???if?(httpClient?!=?null)?{
          ????try?{
          ?????httpClient.close();
          ????}?catch?(IOException?e)?{
          ?????e.printStackTrace();
          ????}
          ???}
          ??}
          ?}

          Post請求部分與Get請求的關鍵區(qū)別在于構(gòu)建的請求對象不同,傳輸?shù)膮?shù)不再局限于URL的拼接,還可以基于Entity來進行傳輸。我們在實踐的過程中,大多數(shù)也是將數(shù)據(jù)放在Entity中基于JSON等格式進行傳輸。

          HttpClient 超時配置

          正常來說上面的代碼已經(jīng)基本滿足了業(yè)務需求,但還是有需要完善的地方,特別是針對HTTP請求超時情況的處理。

          HttpClient對此提供了setConfig(RequestConfig config)方法來為請求配置超時時間等,部分核心代碼如下:

          //?設置配置請求參數(shù)(沒有可忽略)
          RequestConfig?requestConfig?=?RequestConfig.custom().setConnectTimeout(35000)//?連接主機服務超時時間
          ?.setConnectionRequestTimeout(35000)//?請求超時時間
          ?.setSocketTimeout(60000)//?數(shù)據(jù)讀取超時時間
          ?.build();
          //?為httpGet實例設置配置
          httpGet.setConfig(requestConfig);

          關于上述配置的重要性,也是不容忽視的。否則可能會導致請求阻塞,影響性能等問題。

          HttpClient工具類封裝

          看完上述使用,是不是發(fā)現(xiàn)HttpClient的使用非常簡單、便捷?其實,還可以根據(jù)具體是使用場景,進一步進行封裝,封裝成工具類,業(yè)務使用時直接調(diào)用即可。

          關于HttpClientUtil的封裝有很多方式,這里提供一種封裝,僅供參考:

          import?org.apache.http.HttpStatus;
          import?org.apache.http.NameValuePair;
          import?org.apache.http.client.config.RequestConfig;
          import?org.apache.http.client.entity.UrlEncodedFormEntity;
          import?org.apache.http.client.methods.CloseableHttpResponse;
          import?org.apache.http.client.methods.HttpGet;
          import?org.apache.http.client.methods.HttpPost;
          import?org.apache.http.client.utils.URIBuilder;
          import?org.apache.http.entity.StringEntity;
          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.net.URI;
          import?java.util.ArrayList;
          import?java.util.List;
          import?java.util.Map;

          /**
          ?*?http請求客戶端
          ?*
          ?*?@author?zzs
          ?*/
          public?class?HttpClientUtil?{

          ?private?static?RequestConfig?requestConfig?=?null;

          ?private?HttpClientUtil()?{
          ?}

          ?static?{
          ??//設置http的狀態(tài)參數(shù)
          ??requestConfig?=?RequestConfig.custom()
          ????.setSocketTimeout(5000)
          ????.setConnectTimeout(5000)
          ????.setConnectionRequestTimeout(5000)
          ????.build();
          ??//?TODO?補充其他配置
          ?}

          ?public?static?String?doGet(String?url,?Map?param)?{
          ??//?創(chuàng)建Httpclient對象
          ??CloseableHttpClient?httpClient?=?HttpClients.createDefault();

          ??String?resultString?=?"";
          ??CloseableHttpResponse?response?=?null;
          ??try?{
          ???//?創(chuàng)建uri
          ???URIBuilder?builder?=?new?URIBuilder(url);
          ???if?(param?!=?null)?{
          ????for?(String?key?:?param.keySet())?{
          ?????builder.addParameter(key,?param.get(key));
          ????}
          ???}
          ???URI?uri?=?builder.build();
          ???//?創(chuàng)建http?GET請求
          ???HttpGet?httpGet?=?new?HttpGet(uri);
          ???httpGet.setConfig(requestConfig);
          ???//?執(zhí)行請求
          ???response?=?httpClient.execute(httpGet);
          ???//?判斷返回狀態(tài)是否為200
          ???if?(response.getStatusLine().getStatusCode()?==?200)?{
          ????resultString?=?EntityUtils.toString(response.getEntity(),?"UTF-8");
          ???}
          ??}?catch?(Exception?e)?{
          ???//?TODO?完善異常處理
          ???e.printStackTrace();
          ??}?finally?{
          ???try?{
          ????if?(response?!=?null)?{
          ?????response.close();
          ????}
          ????if?(httpClient?!=?null)?{
          ?????httpClient.close();
          ????}
          ???}?catch?(IOException?e)?{
          ????e.printStackTrace();
          ???}
          ??}
          ??return?resultString;
          ?}

          ?public?static?String?doGet(String?url)?{
          ??return?doGet(url,?null);
          ?}

          ?public?static?String?doPost(String?url,?Map?param)?{
          ??//?創(chuàng)建Httpclient對象
          ??CloseableHttpClient?httpClient?=?HttpClients.createDefault();
          ??CloseableHttpResponse?response?=?null;
          ??String?resultString?=?"";
          ??try?{
          ???//?創(chuàng)建Http?Post請求
          ???HttpPost?httpPost?=?new?HttpPost(url);
          ???httpPost.setConfig(requestConfig);
          ???//?創(chuàng)建參數(shù)列表
          ???if?(param?!=?null)?{
          ????List?paramList?=?new?ArrayList<>();
          ????for?(String?key?:?param.keySet())?{
          ?????paramList.add(new?BasicNameValuePair(key,?(String)?param.get(key)));
          ????}
          ????//?模擬表單
          ????UrlEncodedFormEntity?entity?=?new?UrlEncodedFormEntity(paramList);
          ????httpPost.setEntity(entity);
          ???}
          ???//?執(zhí)行http請求
          ???response?=?httpClient.execute(httpPost);
          ???resultString?=?EntityUtils.toString(response.getEntity(),?"utf-8");
          ??}?catch?(Exception?e)?{
          ???//?TODO?完善異常處理
          ???e.printStackTrace();
          ??}?finally?{
          ???try?{
          ????if?(response?!=?null)?{
          ?????response.close();
          ????}
          ????if?(httpClient?!=?null)?{
          ?????httpClient.close();
          ????}
          ???}?catch?(IOException?e)?{
          ????e.printStackTrace();
          ???}
          ??}

          ??return?resultString;
          ?}

          ?public?static?String?doPost(String?url)?{
          ??return?doPost(url,?null);
          ?}

          ?public?static?String?doPostJson(String?url,?String?json,?String?token_header)?throws?Exception?{
          ??//?創(chuàng)建Httpclient對象
          ??CloseableHttpClient?httpClient?=?HttpClients.createDefault();
          ??CloseableHttpResponse?response?=?null;
          ??String?resultString?=?"";
          ??try?{
          ???//?創(chuàng)建Http?Post請求
          ???HttpPost?httpPost?=?new?HttpPost(url);
          ???httpPost.setConfig(requestConfig);
          ???//?創(chuàng)建請求內(nèi)容
          ???httpPost.setHeader("HTTP?Method",?"POST");
          ???httpPost.setHeader("Connection",?"Keep-Alive");
          ???httpPost.setHeader("Content-Type",?"application/json;charset=utf-8");
          ???httpPost.setHeader("x-authentication-token",?token_header);

          ???StringEntity?entity?=?new?StringEntity(json);

          ???entity.setContentType("application/json;charset=utf-8");
          ???httpPost.setEntity(entity);

          ???//?執(zhí)行http請求
          ???response?=?httpClient.execute(httpPost);
          ???if?(response.getStatusLine().getStatusCode()?==?HttpStatus.SC_OK)?{
          ????resultString?=?EntityUtils.toString(response.getEntity(),?"UTF-8");
          ???}
          ??}?catch?(Exception?e)?{
          ???//?TODO?完善異常處理
          ???e.printStackTrace();
          ??}?finally?{
          ???try?{
          ????if?(response?!=?null)?{
          ?????response.close();
          ????}
          ????if?(httpClient?!=?null)?{
          ?????httpClient.close();
          ????}
          ???}?catch?(IOException?e)?{
          ????e.printStackTrace();
          ???}
          ??}
          ??return?resultString;
          ?}
          }

          上述代碼滿足了基本的功能,如果有特殊功能則可進一步擴展。同時,static代碼塊中可進一步完善RequestConfig的參數(shù)配置和其他配置的初始化。另外,針對異常處理部分,也看根據(jù)具體的業(yè)務場景選擇:直接拋出異常、打印日志、拋出自定義異常等方式進行處理。

          小結(jié)

          本篇文章我們學習了HttpClient及其基本使用,同時以代碼的形式展示了最佳實踐、封裝、改進以及其中會遇到的問題。掌握本篇內(nèi)容基本可以滿足80%的日常使用場景了。當然,還有一些針對HTTPs請求、連接池配置、異步處理等特定使用,則需要讀者在實踐的過程中有針對性的自行探索了。


          66e54074e7ef7e6e1c3c02a49cd4f962.webp


          0、重磅!兩萬字長文總結(jié),梳理 Java 入門進階哪些事(推薦收藏)

          1、30歲生日,總結(jié)過去的一年,我哭了……

          2、看完這設計模式匯總,你確定不加收藏嗎

          2df84ee238992aff78d4f40302757bea.webp

          瀏覽 105
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  九色PORNY原创自拍 | 青草网络青 | 人人摸在线观看 | 天天操成人电影 | 无码ww|