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

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

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.baidu.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請求java

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

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

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

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

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

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());
    }
}

輸出爲:dom

HTTP/1.1
200
OK
HTTP/1.1 200 OK

3. HTTP消息頭ide

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

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),經過HttpEntitysetChunked()方法設置爲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);
    }
}
相關文章
相關標籤/搜索