歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Httpclient4.4之原理(請求執行)

Httpclient4.4之原理(請求執行)

日期:2017/3/1 9:29:06   编辑:Linux編程

Apache Httpclient基於java BIO實現的,也是基於apache HttpCore項目。他最基本的功能是執行HTTP方法。HttpClient的API的主要入口就是HttpClient接口,看看這個示例:

package httpclienttest;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class T1 {
public static void main(String[] args) {
HttpGet httpget = new HttpGet("http://www.linuxidc.com");
try(CloseableHttpClient httpclient = HttpClients.createDefault();
CloseableHttpResponse response = httpclient.execute(httpget);){
System.out.printf("內容類型為:%s",response.getEntity().getContentType());
}catch(Exception e){
e.printStackTrace();
}
}

1. HTTP請求

所有的http請求都由:方法名,請求url,HTTP協議組成。HttpClient支持HTTP/1.1支持的所有方法:GET,HEAD,POST,PUT,DELETE,TRACE和OPTIONS,HttpClient中都有一個特定的類與之對應:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace和HttpOptions。

HTTP請求URI包括協議,主機名,可選的端口,資源路徑,可選的查詢條件等。如下例:

HttpGet httpget = new HttpGet("http://www.google.com/search?hl=en"
+ "&q=httpclient&btnG=Google+Search&aq=f&oq=");

HttpClient提供了URIBuilder通用類來創建或修改請求URI,如例:

URI uri = new URIBuilder()
.setScheme("http")
.setHost("www.google.com")
.setPath("/search")
.setParameter("q", "httpclient")
.setParameter("btnG", "Google Search")
.setParameter("aq", "f")
.setParameter("oq", "")
.build();
HttpGet httpget = new HttpGet(uri);
System.out.println(httpget.getURI())

2. HTTP響應

HTTP響應是HTTP請求發送到服務端處理後響應到客戶端的消息。響應第一行是協議與協議版本號,接著是數字狀態碼和一些文本信息,示例演示一下看看執行結果:

package httpclienttest;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.message.BasicHttpResponse;

public class T2 {
public static void main(String[] args) {
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

3. HTTP消息頭

HTTP消息頭(header)包含多個消息描述的信息,例如:內容長度,內容類型等。HttpClient提供的方法有檢索,添加,刪除和枚舉等操作。 示例:

package httpclienttest;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.message.BasicHttpResponse;

public class T3 {
public static void main(String[] args) {
HttpResponse response = new BasicHttpResponse(HttpVersion.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接口獲得所有的header信息,示例:

package httpclienttest;

import org.apache.http.HeaderIterator;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.message.BasicHttpResponse;

public class T4 {
public static void main(String[] args) {
HttpResponse response = new BasicHttpResponse(HttpVersion.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\"");
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消息並獲得header中一個個獨立的header元素,示例:

package httpclienttest;

import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicHttpResponse;

public class T5 {
public static void main(String[] args) {
HttpResponse response = new BasicHttpResponse(HttpVersion.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\"");
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

4. HTTP Entity

HTTP消息能攜帶與請求或響應有關的實體內容,它只是可選的,能在有些請求或響應中找到。請求消息中使用實體是針對entity enclosing request的,HTTP規范中定義了兩個entity enclosing request方法:POST和PUT。響應通常都是包裝一個實體的,當然也有例外!

HttpClient區分三種實體,根據其內容來源:

•streamed(流):內容從流中接收的。流實體是不可重復的。


•self-contained(自包含):內容在內存中或者從連接或其它實體自主獲得的。自包含的實體通常都是可重復的。這種類型的實體通常用於entity enclosing request。


•wrapping(包裝):內容從其它實體獲得。


從HTTP響應中流出內容時,對於連接管理(connection management)這種區別還是很重要的。對於請求實體是通過應用程序創建的並且只是使用HttpClient發送,這種區別對於streamed與self-contained意義不大。這種情況下,建議把不可重復的實體當作streamed,把那些可重復的當作self-contained。

4.1 可重復的實體

一個實體能被重復獲取,意味著內容能被讀多次。這是唯一可能的自包含實體(如:ByteArrayEntity或StringEntity)。

4.2 使用HTTP實體

因為實體可以代表二進制和字符內容,它是支持字符集的(支持後者,即文字內容)。

從實體中讀取內容,可能通過HttpEntity的getContent()方法檢索輸入流,它返回一個java.io.InputStream,或者可以提供一個輸出流作為HttpEntity的writeTo(OutputStream)方法的參數,將返回的所有內容寫入給定的流。

當實體收到一個進來的消息時,HttpEntity的getContentType方法和getContentLength方法能被用來讀取請求header裡的元數據:Content-Type和Content-Length(如果它們可用)。由於header的Content-Type能包含像text/plain或text/html這樣的MIME類型的字符集(編碼),HttpEntity的getContentEncoding()方法用來讀取這個信息。 如果header不可用,長度將返回-1和內容類型為NULL。如果header的Content-Type是可用的,這個Header對象將返回。

創建一個輸出消息的實體時,該數據是由實體的創建者提供,示例:

package httpclienttest;

import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;

public class T6 {
public static void main(String[] args) throws Exception{
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

5. 確保底層資源的釋放

為了確保系統資源的釋放,必須關閉與實體相關的內容流或者response自身:

package httpclienttest;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Stream;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class T7 {
public static void main(String[] args) {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://ifeng.com");
//使用java7中的語法,自動調用close()方法,所以這裡沒有顯示調用
try(CloseableHttpResponse response = httpclient.execute(httpget)){
HttpEntity entity = response.getEntity();
try(BufferedReader reader = new BufferedReader(new InputStreamReader(
entity.getContent(),StandardCharsets.UTF_8))){
Stream<String> sm = reader.lines();
sm.forEach(System.out::println);
}
}catch(Exception e){
e.printStackTrace();
}
}
}

關閉內容流與關閉response之間的區別是,前者試圖保持消費實體內容的基本連接是活的,而後者關閉連接並丟棄!請注意:一旦實體被完全寫出,HttpEntity的writeTo(OutputStream)方法也要確保系統資源釋放。如果調用HttpEntity的getContent()方法獲得一個java.io.InputStream流的實例,在最後也是要關閉並釋放資源的。

當使用流實體工作時,EntityUtils的consume(HttpEntity)方法能確保實體內容完全被消費掉,並自動後台關閉流來釋放資源。

有種極少見的情況,請求響應的實體內容只有一小部分需要被檢索,剩下的都不需要,而消費剩下的內容又有性能損耗,造成重用連接很高。這種情況下,可以直接關閉response來終止內容流!如下例:

package httpclienttest;

import java.io.InputStream;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class T8 {
public static void main(String[] args) {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://ifeng.com");
try (CloseableHttpResponse response = httpclient.execute(httpget)) {
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
int byteOne = instream.read();
int byteTwo = instream.read();
System.out.printf("%d,%d",byteOne,byteTwo);
// instream中剩下的內容不需要了!直接關閉response
}
}catch(Exception e){
e.printStackTrace();
}
}
}

這樣連接不會被重用,而且所有級別的資源都會被釋放!

6. 消費實體內容

消費實體內容的推薦方式是使用HttpEntity的getContent()或HttpEntity的writeTo(OutputStream)方法。HttpClient還配備了EntityUtils類,它提供了幾個靜態方法讓讀取實體內容與信息更容易,而不是直接讀java.io.InputStream。能通過EntityUtils的這些靜態方法檢索整個內容體到這符串/字節數組。不管怎樣,強烈建議不要使用EntityUtils,除非響應實體產生自一個可信的HTTP服務端並且知道是有限長度的。示例:

package httpclienttest;

import java.nio.charset.StandardCharsets;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class T9 {
public static void main(String[] args) {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://www.ifeng.com/");
try (CloseableHttpResponse response = httpclient.execute(httpget)){
HttpEntity entity = response.getEntity();
if (entity != null) {
System.out.println(EntityUtils.toString(entity, StandardCharsets.UTF_8));
}
}catch(Exception e){
e.printStackTrace();
}
}
}

在某些情況下,可能需要讀實體內容不止一次。這種情況下,實體內容在某種程度上必須緩沖,無論在內存還是磁盤。最簡單的方法是通過BufferedHttpEntity類來包裝原始的實體,這將使原始實體的內容讀到內存緩沖區。示例:

package httpclienttest;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Stream;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class T10 {
public static void main(String[] args) {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://www.ifeng.com/");
try (CloseableHttpResponse response = httpclient.execute(httpget)){
HttpEntity entity = response.getEntity();
if (entity != null) {
//實體進行緩沖,可重復使用
entity = new BufferedHttpEntity(entity);
try(BufferedReader reader = new BufferedReader(new InputStreamReader(
entity.getContent(),StandardCharsets.UTF_8))){
Stream<String> sm = reader.lines();
sm.forEach(System.out::println);
}
System.out.println("讀第二次!");
try(BufferedReader reader = new BufferedReader(new InputStreamReader(
entity.getContent(),StandardCharsets.UTF_8))){
Stream<String> sm = reader.lines();
sm.forEach(System.out::println);
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}

7. 創建實體內容

HttpClient提供了幾個通過HTTP connection有效地流出內容的類。這些類的實例與POST和PUT這樣的entity enclosing requests有關,為了把這些實體內容放進即將發出的請求中。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);

請注意:InputStreamEntity是不可重復的,因為它只能從底層數據流讀一次。通常推薦使用InputStreamEntity來實現一個自定義的self-contained的HttpEntity類。

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("http://localhost/handler.do");
httppost.setEntity(entity);

這UrlEncodedFormEntity實體將使用URL編碼來編碼參數並產生如下內容:

param1=value1&param2=value2

7.2 HTTP分塊

通常推薦HttpClient選擇適當的基於HTTP消息傳輸特性的傳輸編碼。然而,有個可能是通知HttpEntity優先使用分塊編碼(chunk coding),通過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);

8. response處理

處理response最簡單,最方便的方式是使用ResponseHandler接口,它包含handleResponse(HttpResponse respnse)方法。這種方法讓用戶完全不用擔心連接的管理。使用ResponseHandler時,HttpClient將自動釋放連接並把連接放回連接管理器中,不論請求執行成功還是失敗。示例:

package httpclienttest;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class T14 {
public static void main(String[] args) {
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 = httpclient.execute(httpget, rh);
}
}

使用HttpClient實現文件的上傳下載 http://www.linuxidc.com/Linux/2014-07/104303.htm

Android 實現 HttpClient 請求Https http://www.linuxidc.com/Linux/2014-05/102306.htm

Android使用HttpClient下載圖片 http://www.linuxidc.com/Linux/2014-05/101855.htm

HttpClient使用詳解 http://www.linuxidc.com/Linux/2014-08/104945.htm

Copyright © Linux教程網 All Rights Reserved