制作網站必做步驟域名注冊
一、簡單介紹
? ? ? ? 1、簡介
? ? ? ? HttpClient是Apache Jakarta Common下的子項目,用來提供高效的、最新的、功能豐富的支持HTTP協(xié)議的客戶端編程工具包,并且它支持HTTP協(xié)議最新的版本和建議。HttpClient已經應用在很多的項目中,比如Apache Jakarta上很著名的另外兩個開源項目Cactus和HTMLUnit都使用了HttpClient。
?????????HTTP和瀏覽器有點像,但卻不是瀏覽器。很多人覺得既然HttpClient是一個HTTP客戶端編程工具,很多人把他當做瀏覽器來理解,但是其實HttpClient不是瀏覽器,它是一個HTTP通信庫,因此它只提供一個通用瀏覽器應用程序所期望的功能子集,最根本的區(qū)別是HttpClient中沒有用戶界面,瀏覽器需要一個渲染引擎來顯示頁面,并解釋用戶輸入,例如鼠標點擊顯示頁面上的某處,有一個布局引擎,計算如何顯示HTML頁面,包括級聯(lián)樣式表和圖像。javascript解釋器運行嵌入HTML頁面或從HTML頁面引用的javascript代碼。來自用戶界面的事件被傳遞到javascript解釋器進行處理。除此之外,還有用于插件的接口,可以處理Applet,嵌入式媒體對象(如pdf文件,Quicktime電影和Flash動畫)或ActiveX控件(可以執(zhí)行任何操作)。HttpClient只能以編程的方式通過其API用于傳輸和接受HTTP消息。
? ? ? 2、特性:
- 基于標準、純凈的java語言。實現(xiàn)了Http1.0和Http1.1
- 以可擴展的面向對象的結構實現(xiàn)了Http全部的方法(GET, POST, PUT, DELETE, HEAD, OPTIONS, and 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的輸出流可以避免流中內容直接緩沖到socket服務器。
- Response的輸入流可以有效的從socket服務器直接讀取相應內容。
- ?在http1.0和http1.1中利用KeepAlive保持持久連接。
- ?直接獲取服務器發(fā)送的response code和 headers。
- ?設置連接超時的能力。
- 實驗性的支持http1.1 response caching。
- 源代碼基于Apache License 可免費獲取。
二、簡單使用
? ? ? ? 1、引入依賴
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.13</version></dependency>
? ? ? ? 2、使用HttpClient發(fā)送請求、接收響應很簡單,一般需要如下幾步即可。
//1. 創(chuàng)建HttpClient對象。CloseableHttpClient httpClient = HttpClientBuilder.create().build();//2. 創(chuàng)建請求方法的實例,并指定請求URL。// 如果需要發(fā)送GET請求,創(chuàng)建HttpGet對象;如果需要發(fā)送POST請求,創(chuàng)建HttpPost對象。HttpGet httpGet = new HttpGet("http://localhost:8090/emp/get");//3. 如果需要發(fā)送請求參數(shù),可調用HttpGet、HttpPost共同的setParams(HttpParams params)方法來添加請求參數(shù);// 對于HttpPost對象而言,也可調用setEntity(HttpEntity entity)方法來設置請求參數(shù)。// 響應模型CloseableHttpResponse response = null;try {// 4. 調用HttpClient對象的execute(HttpUriRequest request)發(fā)送請求,// 該方法返回一個HttpResponse。response = httpClient.execute(httpGet);// 從響應模型中獲取響應實體//5. 調用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可獲取服務器的響應頭;// 調用HttpResponse的getEntity()方法可獲取HttpEntity對象,該對象包裝了服務器的響應內容。程序可通過該對象獲取服務器的響應內容。HttpEntity responseEntity = response.getEntity();System.out.println("響應狀態(tài)為:" + response.getStatusLine());if (responseEntity != null) {System.out.println("響應內容長度為:" + responseEntity.getContentLength());System.out.println("響應內容為:" + EntityUtils.toString(responseEntity));}} catch (ClientProtocolException e) {e.printStackTrace();} catch (ParseException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {try {// 6. 釋放連接。無論執(zhí)行方法是否成功,都必須釋放連接if (httpClient != null) {httpClient.close();}if (response != null) {response.close();}} catch (IOException e) {e.printStackTrace();}}
三、各種使用案例
? ? ? ? 1、GET請求? 沒有參數(shù)
//1、創(chuàng)建HttpClient 客戶端CloseableHttpClient httpClient = HttpClientBuilder.create().build();//2、 創(chuàng)建請求方法的實例,并指定請求URLHttpGet httpGet = new HttpGet("http://localhost:8090/user/get");//響應模型CloseableHttpResponse response = null;try {// 由客戶端執(zhí)行發(fā)送Get請求response = httpClient.execute(httpGet);// 從響應模型中獲取響應實體HttpEntity responseEntity = response.getEntity();System.out.println("響應狀態(tài)為:" + response.getStatusLine());if (responseEntity != null) {System.out.println("響應內容長度為:" + responseEntity.getContentLength());System.out.println("響應內容為:" + EntityUtils.toString(responseEntity));}} catch (ClientProtocolException e) {e.printStackTrace();} catch (ParseException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {try {// 釋放資源if (httpClient != null) {httpClient.close();}if (response != null) {response.close();}} catch (IOException e) {e.printStackTrace();}}
? ? ? ? 2、GET 請求 路徑上拼接參數(shù)
? ? ? ? 1)直接拼接參數(shù)
String userId = "1";String userName = "張三";//1、創(chuàng)建HttpClient 客戶端CloseableHttpClient httpClient = HttpClientBuilder.create().build();//參數(shù) 拼接StringBuffer params = new StringBuffer();try {// 字符數(shù)據(jù)最好encoding一下;這樣一來,某些特殊字符才能傳過去(如:某人的名字就是“&”,不encoding的話,傳不過去)params.append("userId=" + URLEncoder.encode(userId, "utf-8"));params.append("&");params.append("userName="+ URLEncoder.encode(userName, "utf-8"));} catch (UnsupportedEncodingException e1) {e1.printStackTrace();}//2、 創(chuàng)建請求方法的實例,并指定請求URLHttpGet httpGet = new HttpGet("http://localhost:8090/user/get"+ "?" + params);//響應模型CloseableHttpResponse response = null;try {//添加配置信息RequestConfig requestConfig = RequestConfig.custom()//設置連接超時時間(單位毫秒).setConnectTimeout(5000)//設置請求超時時間(單位毫秒).setConnectionRequestTimeout(5000)//socket讀寫超時時間(單位毫秒).setSocketTimeout(50000)//設置是否允許重定向(默認為true).setRedirectsEnabled(true).build();// 將上面的配置信息,添加到Get請求中httpGet.setConfig(requestConfig);// 由客戶端執(zhí)行發(fā)送Get請求response = httpClient.execute(httpGet);// 從響應模型中獲取響應實體HttpEntity responseEntity = response.getEntity();System.out.println("響應狀態(tài)為:" + response.getStatusLine());if (responseEntity != null) {System.out.println("響應內容長度為:" + responseEntity.getContentLength());System.out.println("響應內容為:" + EntityUtils.toString(responseEntity));}} catch (ClientProtocolException e) {e.printStackTrace();} catch (ParseException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {try {// 釋放資源if (httpClient != null) {httpClient.close();}if (response != null) {response.close();}} catch (IOException e) {e.printStackTrace();}}
? ? ? ? 2)使用URI獲得HttpGet
String userId = "1";String userName = "張三";//1、創(chuàng)建HttpClient 客戶端CloseableHttpClient httpClient = HttpClientBuilder.create().build();// 使用URI 拼接好請求路徑URI uri = null;try {// 將參數(shù)放入鍵值對類NameValuePair中,再放入集合中List<NameValuePair> params = new ArrayList<>();params.add(new BasicNameValuePair("userId", userId));params.add(new BasicNameValuePair("userName", userName));// 設置uri信息,并將參數(shù)集合放入uri;// 注:這里也支持一個鍵值對一個鍵值對地往里面放setParameter(String key, String value)uri = new URIBuilder().setScheme("http").setHost("localhost").setPort(8090).setPath("/user/get").setParameters(params).build();} catch (URISyntaxException e1) {e1.printStackTrace();}//2、 創(chuàng)建請求方法的實例,并指定請求URLHttpGet httpGet = new HttpGet(uri);//響應模型CloseableHttpResponse response = null;try {//添加配置信息RequestConfig requestConfig = RequestConfig.custom()//設置連接超時時間(單位毫秒).setConnectTimeout(5000)//設置請求超時時間(單位毫秒).setConnectionRequestTimeout(5000)//socket讀寫超時時間(單位毫秒).setSocketTimeout(50000)//設置是否允許重定向(默認為true).setRedirectsEnabled(true).build();// 將上面的配置信息,添加到Get請求中httpGet.setConfig(requestConfig);// 由客戶端執(zhí)行發(fā)送Get請求response = httpClient.execute(httpGet);// 從響應模型中獲取響應實體HttpEntity responseEntity = response.getEntity();System.out.println("響應狀態(tài)為:" + response.getStatusLine());if (responseEntity != null) {System.out.println("響應內容長度為:" + responseEntity.getContentLength());System.out.println("響應內容為:" + EntityUtils.toString(responseEntity));}} catch (ClientProtocolException e) {e.printStackTrace();} catch (ParseException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {try {// 釋放資源if (httpClient != null) {httpClient.close();}if (response != null) {response.close();}} catch (IOException e) {e.printStackTrace();}}
? ? ? ? 3、POST請求 沒有參數(shù)
//1、創(chuàng)建HttpClient 客戶端CloseableHttpClient httpClient = HttpClientBuilder.create().build();//2、 創(chuàng)建請求方法的實例,并指定請求URLHttpPost httpPost = new HttpPost("http://localhost:8090/user/get");//響應模型CloseableHttpResponse response = null;try {// 由客戶端執(zhí)行發(fā)送Post請求response = httpClient.execute(httpPost);// 從響應模型中獲取響應實體HttpEntity responseEntity = response.getEntity();System.out.println("響應狀態(tài)為:" + response.getStatusLine());if (responseEntity != null) {System.out.println("響應內容長度為:" + responseEntity.getContentLength());System.out.println("響應內容為:" + EntityUtils.toString(responseEntity));}} catch (ClientProtocolException e) {e.printStackTrace();} catch (ParseException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {try {// 釋放資源if (httpClient != null) {httpClient.close();}if (response != null) {response.close();}} catch (IOException e) {e.printStackTrace();}}
? ? ? ? 4、POST請求 有參(對象參數(shù) 或 路徑參數(shù))
? ? ? ? 1)路徑拼接參數(shù)
String userId = "1";String userName = "張三";//1、創(chuàng)建HttpClient 客戶端CloseableHttpClient httpClient = HttpClientBuilder.create().build();//參數(shù) 拼接StringBuffer params = new StringBuffer();try {// 字符數(shù)據(jù)最好encoding一下;這樣一來,某些特殊字符才能傳過去(如:某人的名字就是“&”,不encoding的話,傳不過去)params.append("userId=" + URLEncoder.encode(userId, "utf-8"));params.append("&");params.append("userName="+ URLEncoder.encode(userName, "utf-8"));} catch (UnsupportedEncodingException e1) {e1.printStackTrace();}//2、 創(chuàng)建請求方法的實例,并指定請求URLHttpPost httpPost = new HttpPost("http://localhost:8090/user/get"+"?"+params);// 設置ContentType(注:一般路徑拼接參數(shù)請求頭都是application/x-www-form-urlencoded)httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");//響應模型CloseableHttpResponse response = null;try {//添加配置信息RequestConfig requestConfig = RequestConfig.custom()//設置連接超時時間(單位毫秒).setConnectTimeout(5000)//設置請求超時時間(單位毫秒).setConnectionRequestTimeout(5000)//socket讀寫超時時間(單位毫秒).setSocketTimeout(50000)//設置是否允許重定向(默認為true).setRedirectsEnabled(true).build();// 將上面的配置信息,添加到Get請求中httpPost.setConfig(requestConfig);// 由客戶端執(zhí)行發(fā)送Get請求response = httpClient.execute(httpPost);// 從響應模型中獲取響應實體HttpEntity responseEntity = response.getEntity();System.out.println("響應狀態(tài)為:" + response.getStatusLine());if (responseEntity != null) {System.out.println("響應內容長度為:" + responseEntity.getContentLength());System.out.println("響應內容為:" + EntityUtils.toString(responseEntity));}} catch (ClientProtocolException e) {e.printStackTrace();} catch (ParseException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {try {// 釋放資源if (httpClient != null) {httpClient.close();}if (response != null) {response.close();}} catch (IOException e) {e.printStackTrace();}}
? ? ? ? 2)對象參數(shù)
//1、創(chuàng)建HttpClient 客戶端CloseableHttpClient httpClient = HttpClientBuilder.create().build();//2、 創(chuàng)建請求方法的實例,并指定請求URLHttpPost httpPost = new HttpPost("http://localhost:8090/user/get");User user = new User();user.setId(1L);user.setName("張三");// 我這里利用阿里的fastjson,將Object轉換為json字符串;// (需要導入com.alibaba.fastjson.JSON包)String jsonString = JSON.toJSONString(user);// post請求是將參數(shù)放在請求體里面?zhèn)鬟^去的;這里將entity放入post請求體中StringEntity entity = new StringEntity(jsonString, "UTF-8");httpPost.setEntity(entity);// json參數(shù)需要設置請求頭為 application/jsonhttpPost.setHeader("Content-Type", "application/json;charset=utf8");//響應模型CloseableHttpResponse response = null;try {//添加配置信息RequestConfig requestConfig = RequestConfig.custom()//設置連接超時時間(單位毫秒).setConnectTimeout(5000)//設置請求超時時間(單位毫秒).setConnectionRequestTimeout(5000)//socket讀寫超時時間(單位毫秒).setSocketTimeout(50000)//設置是否允許重定向(默認為true).setRedirectsEnabled(true).build();// 將上面的配置信息,添加到Get請求中httpPost.setConfig(requestConfig);// 由客戶端執(zhí)行發(fā)送Get請求response = httpClient.execute(httpPost);// 從響應模型中獲取響應實體HttpEntity responseEntity = response.getEntity();System.out.println("響應狀態(tài)為:" + response.getStatusLine());if (responseEntity != null) {System.out.println("響應內容長度為:" + responseEntity.getContentLength());System.out.println("響應內容為:" + EntityUtils.toString(responseEntity));}} catch (ClientProtocolException e) {e.printStackTrace();} catch (ParseException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {try {// 釋放資源if (httpClient != null) {httpClient.close();}if (response != null) {response.close();}} catch (IOException e) {e.printStackTrace();}}
? ? ? ? 3)(普通參數(shù) + 對象參數(shù))
String userId = "1";String userName = "張三";//1、創(chuàng)建HttpClient 客戶端CloseableHttpClient httpClient = HttpClientBuilder.create().build();//參數(shù) 拼接StringBuffer params = new StringBuffer();try {// 字符數(shù)據(jù)最好encoding一下;這樣一來,某些特殊字符才能傳過去(如:某人的名字就是“&”,不encoding的話,傳不過去)params.append("userId=" + URLEncoder.encode(userId, "utf-8"));params.append("&");params.append("userName="+ URLEncoder.encode(userName, "utf-8"));} catch (UnsupportedEncodingException e1) {e1.printStackTrace();}//2、 創(chuàng)建請求方法的實例,并指定請求URLHttpPost httpPost = new HttpPost("http://localhost:8090/user/get"+"?"+params);User user = new User();user.setId(1L);user.setName("張三");// 我這里利用阿里的fastjson,將Object轉換為json字符串;// (需要導入com.alibaba.fastjson.JSON包)String jsonString = JSON.toJSONString(user);// post請求是將參數(shù)放在請求體里面?zhèn)鬟^去的;這里將entity放入post請求體中StringEntity entity = new StringEntity(jsonString, "UTF-8");httpPost.setEntity(entity);// json參數(shù)需要設置請求頭為 application/jsonhttpPost.setHeader("Content-Type", "application/json;charset=utf8");//響應模型CloseableHttpResponse response = null;try {//添加配置信息RequestConfig requestConfig = RequestConfig.custom()//設置連接超時時間(單位毫秒).setConnectTimeout(5000)//設置請求超時時間(單位毫秒).setConnectionRequestTimeout(5000)//socket讀寫超時時間(單位毫秒).setSocketTimeout(50000)//設置是否允許重定向(默認為true).setRedirectsEnabled(true).build();// 將上面的配置信息,添加到Get請求中httpPost.setConfig(requestConfig);// 由客戶端執(zhí)行發(fā)送Get請求response = httpClient.execute(httpPost);// 從響應模型中獲取響應實體HttpEntity responseEntity = response.getEntity();System.out.println("響應狀態(tài)為:" + response.getStatusLine());if (responseEntity != null) {System.out.println("響應內容長度為:" + responseEntity.getContentLength());System.out.println("響應內容為:" + EntityUtils.toString(responseEntity));}} catch (ClientProtocolException e) {e.printStackTrace();} catch (ParseException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {try {// 釋放資源if (httpClient != null) {httpClient.close();}if (response != null) {response.close();}} catch (IOException e) {e.printStackTrace();}}
????????
????????5、使用httpClient下載文件
? ? ? ? 使用httpclient下載文件,其實在調用上跟上面沒有區(qū)別,唯一需要注意的是,再獲取響應實體時,容易出現(xiàn)?Attempted read from closed?stream嘗試讀取關閉的流 異常,我們可以用BufferedHttpEntity 的方式獲取流:
//1、創(chuàng)建HttpClient 客戶端CloseableHttpClient httpClient = HttpClientBuilder.create().build();//2、 創(chuàng)建請求方法的實例,并指定請求URLHttpPost httpPost = new HttpPost("http://localhost:8090/user/get");//響應模型CloseableHttpResponse response = null;try {//添加配置信息RequestConfig requestConfig = RequestConfig.custom()//設置連接超時時間(單位毫秒).setConnectTimeout(5000)//設置請求超時時間(單位毫秒).setConnectionRequestTimeout(5000)//socket讀寫超時時間(單位毫秒).setSocketTimeout(50000)//設置是否允許重定向(默認為true).setRedirectsEnabled(true).build();// 將上面的配置信息,添加到Get請求中httpPost.setConfig(requestConfig);// 由客戶端執(zhí)行發(fā)送Get請求response = httpClient.execute(httpPost);// 從響應模型中獲取響應實體HttpEntity responseEntity = response.getEntity();//entity實體流保存緩沖區(qū),否則只能操作一次流就會關閉 ,BufferedHttpEntity可以多次讀取流responseEntity = new BufferedHttpEntity(responseEntity);System.out.println("響應狀態(tài)為:" + response.getStatusLine());if (responseEntity != null) {System.out.println("響應內容長度為:" + responseEntity.getContentLength());System.out.println("響應內容為:" + EntityUtils.toString(responseEntity));try(FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\新建文件夾 (2)\\測試.docx")){responseEntity.writeTo(fileOutputStream);}}} catch (ClientProtocolException e) {e.printStackTrace();} catch (ParseException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {try {// 釋放資源if (httpClient != null) {httpClient.close();}if (response != null) {response.close();}} catch (IOException e) {e.printStackTrace();}}
? ? ? ? 6、關于https的調用
? ? ? ? 目前還沒使用到,等使用到再補充。
四、HttpClient連接池
? ? ? ? 事實上對于httpClient 連接池以及相關的配置大有學問,針對高并發(fā)的場景有很多解決策略,這里只先簡單介紹一下如何配置連接池以及怎么使用,復雜的業(yè)務范圍可自行查詢。
? ?HttpClient
連接池是用于優(yōu)化HTTP請求性能的一個關鍵機制,特別是在處理大量HTTP請求時,通過重用現(xiàn)有連接來減少每次新建連接的開銷。連接池的主要優(yōu)勢包括減少建立連接的時間、減少資源消耗、提高請求的并發(fā)處理能力。
? ? ? ? 以下是HttpClient
連接池的幾個核心概念和配置:
????????
????????1. 連接池的作用
????????每次HTTP請求都需要進行TCP握手,特別是對于HTTPS,還需要進行TLS握手,這個過程非常耗時。連接池通過復用連接(連接保持在一個"池"中)避免了為每次請求都新建連接,從而大大提高性能。
? ? ? ? 2、配置HttpClient連接池
????????在Apache HttpClient中,可以通過PoolingHttpClientConnectionManager
類來配置連接池。以下是一個基本的配置示例:
// 創(chuàng)建連接池管理器
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// 設置最大連接數(shù)
cm.setMaxTotal(100);
// 設置每個路由的最大連接數(shù)
cm.setDefaultMaxPerRoute(20);
// 創(chuàng)建HttpClient實例
CloseableHttpClient client = HttpClients.custom().setConnectionManager(connectionManager).setConnectionManagerShared(true).evictIdleConnections(30, TimeUnit.SECONDS) // 超過30秒的閑置連接會被清除.build();
最大連接數(shù)(MaxTotal): 池中允許的最大連接數(shù)。默認值較小,通常需要根據(jù)實際情況調大。
每個路由的最大連接數(shù)(DefaultMaxPerRoute): 每個目標主機(路由)允許的最大并發(fā)連接數(shù)。不同的主機或API服務器通常會有各自的并發(fā)限制。
保持連接活動時間(ConnectionKeepAlive): 指定一個連接在閑置后可以保持多久。短時間的?;羁梢员苊獠槐匾闹亟ㄟB接,但太長時間可能導致連接閑置過多,浪費資源。
????????注意事項
- 連接泄漏: 如果不正確管理連接(如未關閉),可能會導致連接泄漏,從而耗盡連接池中的可用連接,影響系統(tǒng)的穩(wěn)定性。
- 線程安全:
PoolingHttpClientConnectionManager
是線程安全的,可以在多個線程中共享同一個HttpClient
實例。
高并發(fā)場景下的 HttpClient 優(yōu)化方案,QPS 大大提升!