《HttpClient官方文檔》1.1 執行請求(二)
1.1.4. HTTP 實體
HTTP 消息可以攜帶與其相關聯的請求或響應的內容實體。實體可以在一些請求和響應中找到,因為它們也是可選的。使用了實體的請求被稱為封閉實體請求。HTTP規範定義了兩種封閉實體的方法: POST
和PUT
。響應通常期望包含一個內容實體。 這個規則也有特例,就像HEAD
方法和 204 No Content
,304 Not Modified
, 205 Reset Content
響應。
HttpClient根據其內容來源以此區分三種類型的實體:
-
streamed(流式):
從流中獲取或者是動態生成內容。尤其是這個類型包含了從HTTP響應中獲取的實體。流式實體是不可重複生成的。 -
self-contained(自包含式):
通過內存、使用獨立的連接、其他實體的方式來獲得內容。自包含實體可以重複生成。這種類型的實體將主要被用於封閉HTTP請求。 -
wrapping(包裝式):
通過其他實體來獲得內容.
當一個HTTP請求中正在流式輸出內容時,這個區分對於連接管理來說很重要。而對於由應用程序創建,並且隻使用 HttpClient 發送的請求實體,流式和自包含的區分是沒有多大意義的。在這種情況下,建議考慮如流式這樣的非可重複的實體,或者是像自包含式那樣的可重複實體。
由於一個實體可以同時表示二進製和字符內容,因此它擁有對字符編碼的支持(對後者支持,即支持字符內容)。
當執行一次使用封閉內容的請求或者是當請求成功後,響應體被當做返回結果發回客戶端的時候創建實體。
要讀取實體中的內容, 可以通過 HttpEntity#getContent()
方法來從輸入流中獲取, 它會返回 java.io.InputStream
對象。 或者提供一個輸出流到 HttpEntity#writeTo(OutputStream)
方法,
這會返回提供的流中已經寫入的全部內容。
當實體已經接收到傳入的消息, 使用HttpEntity#getContentType()
方法和HttpEntity#getContentLength()
方法能夠讀取通用元數據,例如 Content-Type
和Content-Length
頭不 (如果它們是可用的). 當Content-Type
頭部包含對文本 mime類型的字符編碼比如 text/plain 和text/html時,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
為了確保能夠係統資源的正確釋放,必須關閉實體或響應自身相關的內容流。
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("https://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)
方法也同樣確保係統資源在實體被全部寫出的時候必須正確的釋放,如果該方法獲得的實例通過類java.io.InputStream
調用了HttpEntity#getContent()
方法, 那麼它會被預期在finally子句關閉流。
當流實體工作時, 能夠使用EntityUtils#consume(HttpEntity)
方法確保
實體內容被充分的消耗和關閉底層流.
有可能出現這樣的特殊情況,隻需要獲取響應內容的其中一小部分或者是通過消耗剩餘內容來使連接可重用造成性能損失太高,這樣的情況下可以通過關閉響應來終止內容流。
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("https://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(); }
這樣做了之後,連接將不能被重用,而且其所持有的所有級別資源會被正確釋放.
通常推薦消耗實體內容的方式是使用HttpEntity#getContent()
或是HttpEntity#writeTo(OutputStream)
方法。 HttpClient
同樣也配備了 EntityUtils
類, 它公開了一些靜態方法,這些方法能夠更容易地從實體中讀取內容或信息,
替代通過 java.io.InputStream
類這樣直接讀取的方式,並且也可以使用這個類中的方法以字符串/字節數組的形式獲取整個內容體。
但是, EntityUtils
這個類隻有在響應實體來自受信任的 HTTP 服務器和長度是已知和有限的情況下才推薦使用。
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("https://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(); }
在某些情況下可能需要能夠讀取的實體內容超過一次。在這種情況下的實體內容必須以某種方式緩存在內存或磁盤上。解決這個問題最簡單的方式是通過
the BufferedHttpEntity
類來包裝源實體。這將使源實體的內容被讀入到內存緩衝區。其他任何方式的實體包裝都會包含源實體。
CloseableHttpResponse response = <...> HttpEntity entity = response.getEntity(); if (entity != null) { entity = new BufferedHttpEntity(entity); }
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("https://localhost/action.do"); httppost.setEntity(entity);
請注意, InputStreamEntity
是不可重複的,因為它僅能從底層數據流中讀取一次內容。
通常建議來實現一個自定義 HttpEntity
類, 這個是自包含式的類來替代通用類 InputStreamEntity
.FileEntity
也是一個很不錯的點子.
許多應用程序需要模擬一個提交 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("https://localhost/handler.do"); httppost.setEntity(entity);
UrlEncodedFormEntity
實例將會使用URL編碼來編碼參數,生成如下的內容::
param1=value1¶m2=value2
開始傳輸前,通常建議讓 HttpClient 選擇基於HTTP消息屬性的最適當的傳輸編碼方式。
不過當設置HttpEntity#setChunked()
方法為true時,通知HttpClient該塊編碼是首選。 請注意
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("https://localhost/acrtion.do"); httppost.setEntity(entity);
處理響應最簡單方便的方式是使用 ResponseHandler
接口, 它包含了handleResponse(HttpResponse response)
方法。
這個方法徹底的解決了用戶對於連接管理的困擾。當使用ResponseHandler
接口時, 無論是否請求執行成功或引發異常,HttpClient都會自動關注來確保釋放的連接返回到連接管理器。
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("https://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);
最後更新:2017-05-19 13:32:43