第一章。基礎java
1.1 請求的執行程序員
HttpClient最重要的函數是用於執行HTTP方法.執行一次HTTP方法包含一次或數次HTTP請求和HTTP響應的交互,一般在httpClient內部完成.程序員只須要提供一個請求對象用於執行,HttpClient發送請求到目標服務器並得到對應的響應對象,或者在執行不成功時拋出異常.web
HttpClient API的主要入口點是HttpClient接口.
json
如下是請求執行處理過程的簡單示例數組
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost"); CloseableHttpResponse response = httpclient.execute(httpget); try { <...> } finally { resopnse.close(); }
1.1.1 HTTP請求緩存
全部的HTTP請求都有一個由方法名,請求路徑和HTTP協議版本組成的請求行.
服務器
HttpClient支持HTTP/1.1定義的全部HTTP方法,具體是;GET,HEAD,POST,PUT,DELETE,TRACE和OPTIONS.每一個類型的方法分別對應具體的類:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace和HttpOptions.
app
Request-URI是用於請求惟一的統一資源定位符.HTTP請求路徑由協議,主機名,可選的端口號,資源路徑,可選的查詢參數和可選的段(能夠理解爲頁面元素的ID,可直接跳轉).
dom
HttpGet httpget = new HttpGet("http://www.google.com/searche?h1=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
HttpClient提供了URIBuilder工具類能夠方便的建立和修改請求路徑函數
URI uri = new URIBuilder().setScheme("http").setHost(".setPath("search").setParameter("q","httpclient").setParameter("btnG","Google Search").setPatameter("aq","f").setParameter("oq","").build(); HttpGet httpget = new HttpGet(uri); System.out.println(httpget.getURI());
輸出
http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=
1.1.2 HTTP 響應
HTTP響應是由服務器接收和解釋請求信息後返回給客戶端的信息.消息的第一行由協議及版本後跟着數值的狀態code及緣由組成.
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,HttpStatus.SC_OK,"OK"); System.out.println(response.getProtocolVersion()); System.out.println(response.getStatusLine().getStatusCode()); System.out.println(response.getStatusLine().getReasonPhrase()); System.out.println(response.getStatusLine().toString());
輸出
HTTP/1.1 200 OK HTTP/1.1 200 OK
1.1.3 處理消息頭部信息
HTTP消息包含多個頭部用於描述消息的屬性好比:content length,content type等等.HttpClient提供了方法用於檢索,添加,移除和枚舉頭部信息.
HttpResponse response = new BasicHttpResponse(HttpVersioin.HTTP_1_1,HttpStatus.SC_OK,"OK"); response.addHeader("Set-Cookie","c1=a;path=/;domain-localhost"); response.addHeader("Set-Cookie","c2=b;path=\"/\",c3=c;domain=\"localhost\""); Header h1 = response.getFirstHeader("Set-Cookie"); System.out.println(h1); Header h2 = response.getLastHeader("Set-Cookie"); System.out.println(h2); Header[] hs = response.getHeaders("Set-Cookie"); System.out.println(hs.length);
輸出
Set-Cookie:c1=a;path=/;domain=localhost Set-Cookie:c2=b;path="/",c3=c;domain="localhost" 2
獲取全部給定類型頭部信息最有效的方法是使用HeaderIterator接口.
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,HttpStatus.SC,"OK"); response.addHeader("Set-Cookie","c1=a;path=/;domain-localhost"); response.addHeader("Set-Cookie","c2=b;path=\"/\",c3=c;domain=\"localhost\""); HeaderIterator it = response.headerIterator("Set-Cookie"); while(it.hasNext){ System.out.println(it.next()); }
輸出
Set-Cookie: c1=a; path=/; domain=localhost Set-Cookie: c2=b; path="/", c3=c; domain="localhost"
一樣提供了簡便的方法解析HTTP消息單個的頭元素
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,HttpStatus.SC,"OK"); response.addHeader("Set-Cookie","c1=a;path=/;domain-localhost"); response.addHeader("Set-Cookie","c2=b;path=\"/\",c3=c;domain=\"localhost\""); HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator("Set-Cookie")); while (it.hasNext()){ HeaderElement elem = it.nextElement(); System.out.println(elem.getName() + " = " + elem.getValue()); NameValuePair[] params = elem.getParameters(); for(int i = 0; i < params.length; i++){ System.out.println(" " + params[i]); } }
輸出
c1 = a path=/ domain=localhost c2 = b path=/ c3 = c domain=localhost
1.1.4 HTTP實體
HTTP消息能夠攜帶一個與請求或響應相關的內容實體.實體是可選的,能夠在某些請求或響應中找到.HTTP規範定義的兩個包含實體的請求方法:POST和PUT.響應一般包含一個內容實體.某些狀況下例外,好比:HEADE方法的響應,204 Not Content,304 Not Modified,205 Reset Content這些響應.
HttpClient根據內容的來源將實體分爲三類:
streamed:內容從流中接收或者在運行過程當中產生.具體的,這類包含接收自HTTP響應的實體.Streamed實體一般是不可重複的.
self-contained:存儲在內存中,獨立於HTTP鏈接或其餘的實體.一般是可重複的.這一類的實體經常使用於HTTP請求封裝.
wrapping:內容來自其餘的實體
對於鏈接管理,當從HTTP響應中得到流時這些區別十分重要.
1.1.4.1 可重複的實體
一個實體可重複意味着他的內容能夠讀屢次.僅對於self contained是可能的(好比ByteArrayEntity或StringEntity)
1.1.4.2 使用HTTP實體
因爲實體能夠表明二進制和字符內容,所以有編碼的支持.
實體在執行請求過程當中封裝內容時建立,或者請求成功,響應體用來向客戶端返回結果.
從實體中讀取內容,一種經過HttpEntity#getContent()方法得到輸入流,該方法返回一個java.io.InputStream,或者爲HttpEntity#writeTo(OuputStream)方法提供輸出流,該方法將全部內容寫入提供的流中.
當實體已經經過傳入消息被接收,HttpEntity#getContentType()和HttpEntity#getContentLength()方法能夠用來讀取普通的元數據好比Content-Type和Content-Length頭部(若是它們存在).因爲Content-Type頭部能夠包含字符編碼,HttpEntity#getContentEncoding()方法用於讀取這部分信息.當該頭部不存在時,長度返回-1,內容類型返回NULL.當Content-Type頭部存在,返回Header對象.
當建立實體用於輸出消息是,這些實體的元數據須由實體的建立者提供.
StringEntity myEntity = new StringEntity("important message",ContentType.create("text/plain","UTF-8" )); System.out.println(myEntity.getContentType()); System.out.println(myEntity.getContentLength()); System.out.println(EntityUtils.toString(myEntity)); System.out.println(EntityUtils.toByteArray(myEntity).length);
輸出
Content-Type: text/plain; charset=utf-8 17 important message 17
1.1.5 確認釋放底層資源
爲了保證適當的釋放系統資源,必須關閉實體相關聯的流或者響應自己.
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost/"); CloseableHttpResponse response = httpclient.execute(httpget); try { HttpEntity entity = response.getEntity(); if(entity != null){ InputStream instream = entity.getContent(); try { //do something useful } finally { instream.close(); } } } finally { response.close(); }
關閉內容流和關閉響應兩種方式的區別在於前者嘗試經過消耗實體內容保持底層鏈接的活動然後者則馬上關閉並丟棄鏈接.
請注意HttpEntity#writeTo(OutputStream)方法一樣要求確保在內容被徹底寫出後釋放系統資源.若是該方法包含經過調用HttpEntity#getContent()獲得的java.io.InputStream實例,一樣應該在finally代碼塊中關閉該流.
當使用stream實體時,能夠經過使用EntityUtils#consume(HttpEntity)方法確保實體內容被徹底消耗掉以及底層的流已經被關閉.
有這樣一種狀況,當僅須要檢索響應內容中的一小部分時,消耗剩餘內容複用鏈接代價太高,這種狀況能夠經過關閉響應來終止流.
CloseableHttpClient httpclient = HttpClient.createDefault(); HttpGet httpget = new HttpGet(); CloseableHttpResponse response = httpClient.execute(httpget); try { HttpEntity entity = response.getEntity(); if(entity != null){ InputStream instream = entity.getContent(); int byteOne = instream.read(); int byteTwo = instream.read(); //Do not need the rest } } finally { response.close(); }
該鏈接將不會被複用,且全部引用的資源都將被正確的釋放.
1.1.6 消耗實體內容
消耗實體內容推薦使用HttpEntity#getContent()或者HttpEntity#writeTo(OutputStream)方法.HttpClient還配備了一EntityUtils類,該類提供了幾個靜態方法可以更容易的讀取實體的內容或信息.除了直接讀取java.io.InputStream,能夠經過該類的方法在字符串或字節數組中檢索內容body部分.然而,強烈不鼓勵使用EntityUtils,除非響應來自可信任的HTTP服務器且已知是有長度限制的.
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(); CloseableHttpResponse response = httpclient.execute(httpget); try { HttpEntity entity = response.getEntity(); if(entity != null){ long len = entity.getContentLength(); if(len != -1 && len < 2048) { System.out.println(EntityUtil.toString(entity)); } else { // Steam content out } } } finally { response.close(); }
在某些狀況下須要屢次讀取實體內容.這時必須經過某種方式緩存實體內容,經過內存或磁盤.最簡單的方法是使用BufferedHttpEntity類封裝原始的實體.這樣可使原始實體的內容被讀入到內存緩衝區.
CloseableHttpResponse response = <...>; HttpEntity entity = response.getEntity(); if(entity != null) { entity = new BufferedHttpEntity(entity); }
1.1.7 生產實體內容
HttpClient提供了多個類經過HTTP鏈接高效的流出內容.這些類的實例能夠關聯請求內附的實體好比POST和PUT以將實體內容
裝入傳出HTTP請求.HttpClient提供了多個類做爲大部分數據的容器好比字符串,字節數組,輸入流和文件:StringEntity,ByteArrayEntity,InputStreamEntity,和FileEntity.
File file = new File("somefile.txt"); FileEntity entity = new FileEntity(file,ContentType.create("text/plain","UTF-8")); HttpPost httppost = new HttpPost(""); httppost.setEntity(entity);
請注意InputStreamEntity是不可重複的,由於它只能從底層數據流讀取一次.通常狀況下建議實現自定義HttpEntity類.
1.1.7.1 HTML表單
許多程序須要模擬HTML表單提交操做,好比登陸到web應用程序或者提交輸入數據.HttpClient提供實體類UrlEncodedFormEntity來幫助實現這個過程.
List<NameValuePair> formparams = new ArrayList<NameValuePair>(); formparams.add(new BasicNameValuePair("param1","value1")); formparams.add(new BasicNameValuePair("param2","value2")); UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams,Consts.UTF_8); HttpPost httppost = new HttpPost(); httppost.setEntity(entity);
UrlEncodedFormEntity實例將調用URL encoding對參數編碼併產生如下內容:
param1=value1¶m2=value2
1.1.7.2 內容分塊
一般狀況下推薦由HttpClient根據傳輸的HTTP消息的屬性選擇合適的傳輸編碼.然而也能夠經過設置HttpEntity#setChunked()爲true告知HttpClient該塊優先使用的編碼.HttpClient僅將該標記做爲提示使用.當使用的HTTP協議版本不支持分塊編碼時該值將被忽略.好比HTTP/1.0.
StringEntity entity = new StringEntity("important messae",ContentType.create("plain/text",Consts.UTF_8)); entity.setChunked(true); HttpPost httpPost = new HttpPost(); httppost.setEntity(entity);
1.1.8 響應處理
處理響應最簡單和方便的方式是使用ResponseHandler接口,該接口包含handleResponse(HttpRespinse response)方法.該方法不須要用戶擔憂鏈接管理.當使用ResponseHandler,HttpClient將自動鏈接的管理和釋放不管請求執行成功仍是致使異常.
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet(); ResponseHandler<MyJsonObject> rh = new ResponseHandler<MyJsonObject>(){ public JsonObject handleResponse(final HttpResponse response) throws IOException{ StatusLine statusLine = response.getStatusLine(); HttpEntity entity = response.getEntity(); if(statusLine.getStatusCode() >= 300){ throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); } if(entity == null){ throw new ClientProtocolException("Response contains no content"); } Gson gson = new GsonBuilder().create(); ContentType contentType = ContentType.getOrDefault(entity); Charset charset = contentType.getCharset(); Reader reader = new InputStreamReader(entity.getContent(),charset); return gson.fromJson(reader,MyJsonObject.class); } }; MyJsonObject myjson = client.execute(httpget,rh);