1.1.4 HTTP實體
請求和響應可以攜帶一些內容實體作爲消息,但也可以不攜帶,實體是可選的。帶有實體的請求
被稱爲實體封閉請求(entity enclosing requests),HTTP協議指導定義了兩種實體封閉請求方法:
POST和PUT。響應通常都帶有內容實體,但也有例外,比如響應的HEAD方法和204 無內容,304 未修改,
205 重置內容響應。
根據消息的產生HttpClient分爲三種實體類型:
基於流的實體:消息來源於流。這種類型的消息實體在HTTP響應中最常見。基於流的消息實體通常不能被重複讀取。
自我包含的實體:消息內容在內容或者消息內容獨立於鏈接或者實體。自我包含的實體通常都是可以被重複讀取的,這種類型的實體長用戶實體封閉請求中。
封裝的實體:內容包含在其他實體。
當從HTTP響應流讀取信息時對連接管理來說這種實體分類顯得尤爲重要。對於被應用創建和被用HttpClient發送的請求實體來說,基於流式的實體和自我包含的實體的不同更加重要,在這種情況下,建議使用不可重複讀取的實體作爲流式的,使用可重複讀取的實體作爲自我包含的。
1.1.4.1 可重複讀取的實體
一個可被重複讀取的實體意味着它的內容可被多次讀取。只有自我包含的實體可以被重複讀取,例如:ByteArrayEntity和StringEntity。
1.1.4.2 使用HTTP實體
實體既可以表示二進制內容也可以表示字符文本,而且支持字符編碼。
當執行一個帶有信息的請求或者請求成功響應返回信息給客戶端時實體被創建。
爲了讀取實體內容,可以通過HttpEntity的getConetent()方法,此方法返回輸入流對象,即java.io.InputStream,也可以通過提供一個輸出流給HttpEntity的writeTo(OutputStream)方法一次性讀取所有輸入流的內容。
當實體接受一個輸入消息,HttpEntity的getContentType()方法和getContentLength()方法就可以讀取到實體內容的類型和長度(如果設置了內容類型),HttpEntity的getContentEncoding()方法用於讀取文本(像text/plain、text/html)類型實體的編碼字符集。但是如果消息頭不可用(沒有設置)則獲取長度會返回-1,獲取消息類型會返回NULL,如果消息頭可用則返回消息頭對象(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.in.InputStream)對象實例,則最好也在finally語句塊中關閉該輸入流。
當使用流式實體時,可以使用EntityUtils的coonsume(HttpEntity)方法確保實體內容被全部讀取並且潛在的流被關閉。
有這麼一種情況:只有整個響應內容的一小部分需要被讀取,此時,HttpClient爲了能讀取剩下的內容而保持鏈接可重用所帶來的性能損耗太高,對於這種情況,可以通過關閉響應來終止文本流的傳輸。
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(); int byteOne = instream.read(); int byteTwo = instream.read(); // Do not need the rest } } finally { response.close(); }
這樣,鏈接就不在可用,但是所有鏈接持有的資源都會被得到正確的釋放。
1.1.6 讀取實體內容
推薦通過HttpEntity類的getContent()方法或者writeTo(OutputStream)方法讀取實體內容。HttpClient也提供EntityUtils類,這個類裏的幾個靜態方法可以很容易地從實體讀取內容。爲了不直接從java.io.InputStream內讀取內容,我們可以使用EntityUtils的方法以字符串或者字節數組形式返回整個實體內容,然而,除非響應來自一個可被信任的HTTP服務器並且響應內容是有長度限制的否則強烈不建議使用此類。
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost/"); 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(EntityUtils.toString(entity)); } else { // Stream 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("http://localhost/action.do"); httppost.setEntity(entity);
請注意,因爲InputeStreamEntity只從流中讀取一次數據,所以它是不可被重複讀取的,通常建議通過實現普通的HttpEntity接口自定義流讀取來替代InputStreamEntity,FileEntity就是一個很好的例子。
1.1.7.1 HTML表單實體
許多應用需要模仿提交HTML表單的過程,例如,爲了登錄網站或者提交輸入數據,HttpClient提供UrlEncodeFormEntity類來實現這一功能。
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("http://localhost/handler.do"); httppost.setEntity(entity);
類UrlEncodedFormEntity的實例會用制定的URL編碼方法編碼參數併產生如下內容:
param1=value1¶m2=value2
1.1.7.2 消息分包
通常建議讓HttpClient自己選擇最佳的消息分包編碼策略,但是也可以通過將HttpEntity類的setChunked()方法設置爲true來強制分包編碼,不過需要注意的是HttpClient只是以這樣的方式做一個標記,如果HTTP協議版本不支持分包編碼則此標記就會被忽略,例如HTTP/1.0就不支持。
StringEntity entity = new StringEntity("important message",ContentType.create("plain/text", Consts.UTF_8)); entity.setChunked(true); HttpPost httppost = new HttpPost("http://localhost/acrtion.do"); httppost.setEntity(entity);
1.1.8 響應處理
最簡單、最方便的處理響應的方式是通過使用ResponseHandler接口,這個接口含有handleResponse(HttpResponse response) 方法,此方法完全解除了用戶對鏈接管理的擔心。當使用ResponseHandler時,HttpClient會自動管理以確保釋放鏈接資源而不管請求執行成功與否。
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost/json"); ResponseHandler<MyJsonObject> rh = new ResponseHandler<MyJsonObject>() { @Override 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);
1.2 HttpClient接口
HttpClient接口是HTTP請求執行最重要的規範,他強調在請求執行的過程中不附加任何限制和特定的細節,並且保持鏈接管理、狀態管理、認證和重定向處理的細節獨立於實現類,這使得使用額外的功能裝飾接口變得更加容易,例如響應內容緩衝。
通常,HttpClient的實現類充當一些特殊目的處理的門面或者是充當負責HTTP協議的某一方面的處理的策略接口,例如:重定向和認證處理,以及決定是否保持鏈接處於持續鏈接狀態,這使得用戶能夠有選擇性的使用定製的實現替代默認實現。
ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() { @Override public long getKeepAliveDuration(HttpResponse response,HttpContext context) { long keepAlive = super.getKeepAliveDuration(response, context); if (keepAlive == -1) { // Keep connections alive 5 seconds if a keep-alive value // has not be explicitly set by the server keepAlive = 5000; } return keepAlive; } }; CloseableHttpClient httpclient = HttpClients.custom(). setKeepAliveStrategy(keepAliveStrat).build();
1.2.1 HttpClient線程安全
期望HttpClient的實現類是線程安全的,建議相同的實現類的實例對於多請求執行是可重用的。
1.2.2 HttpClient資源釋放
如果一個CloseableHttpClient的實例不再需要或者超出了與之關聯的鏈接管理的範疇,那麼一定要通過調用CloseableHttpClient的close()方法關閉它。