免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
HttpClient 4.3教程 第一章 基本概念 | 易蹤網(wǎng)


1.1. 請(qǐng)求執(zhí)行

HttpClient最基本的功能就是執(zhí)行Http方法。一個(gè)Http方法的執(zhí)行涉及到一個(gè)或者多個(gè)Http請(qǐng)求/Http響應(yīng)的交互,通常這個(gè)過(guò)程都會(huì)自動(dòng)被HttpClient處理,對(duì)用戶透明。用戶只需要提供Http請(qǐng)求對(duì)象,HttpClient就會(huì)將http請(qǐng)求發(fā)送給目標(biāo)服務(wù)器,并且接收服務(wù)器的響應(yīng),如果http請(qǐng)求執(zhí)行不成功,httpclient就會(huì)拋出異樣。

下面是個(gè)很簡(jiǎn)單的http請(qǐng)求執(zhí)行的例子:

    CloseableHttpClient httpclient = HttpClients.createDefault();    HttpGet httpget = new HttpGet("http://www.yeetrack.com/");    CloseableHttpResponse response = httpclient.execute(httpget);    try {        <...>    } finally {        response.close();    }

1.1.1. Http請(qǐng)求

所有的Http請(qǐng)求都有一個(gè)請(qǐng)求列(request line),包括方法名、請(qǐng)求的URI和Http版本號(hào)。

HttpClient支持HTTP/1.1這個(gè)版本定義的所有Http方法:GET,HEAD,POST,PUT,DELETE,’TRACEOPTIONS。對(duì)于每一種http方法,HttpClient都定義了一個(gè)相應(yīng)的類(lèi):HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTraceHttpOpquertions`。

Request-URI即統(tǒng)一資源定位符,用來(lái)標(biāo)明Http請(qǐng)求中的資源。Http request URIS包含協(xié)議名、主機(jī)名、主機(jī)端口(可選)、資源路徑、query(可選)和片段信息(可選)。

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

HttpClient提供URIBuilder工具類(lèi)來(lái)簡(jiǎn)化URIs的創(chuàng)建和修改過(guò)程。

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

上述代碼會(huì)在控制臺(tái)輸出:

http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=

1.1.2. HTTP響應(yīng)

服務(wù)器收到客戶端的http請(qǐng)求后,就會(huì)對(duì)其進(jìn)行解析,然后把響應(yīng)發(fā)給客戶端,這個(gè)響應(yīng)就是HTTP response.HTTP響應(yīng)第一行是HTTP版本號(hào),然后是響應(yīng)狀態(tài)碼和響應(yīng)內(nèi)容。

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

上述代碼會(huì)在控制臺(tái)輸出:

    HTTP/1.1    200    OK    HTTP/1.1 200 OK

1.1.3. 消息頭

一個(gè)Http消息可以包含一系列的消息頭,用來(lái)對(duì)http消息進(jìn)行描述,比如消息長(zhǎng)度,消息類(lèi)型等等。HttpClient提供了API來(lái)獲取、添加、修改、遍歷消息頭。

    HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");    response.addHeader("Set-Cookie", "c1=a; path=/; domain=yeetrack.com");    response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"yeetrack.com\"");    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);

上述代碼會(huì)在控制臺(tái)輸出:

    Set-Cookie: c1=a; path=/; domain=yeetrack.com    Set-Cookie: c2=b; path="/", c3=c; domain="yeetrack.com"    2

最有效的獲取指定類(lèi)型的消息頭的方法還是使用HeaderIterator接口。

    HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");    response.addHeader("Set-Cookie", "c1=a; path=/; domain=yeetrack.com");    response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"yeetrack.com\""); HeaderIterator it = response.headerIterator("Set-Cookie"); while (it.hasNext()) { System.out.println(it.next()); } 

上述代碼會(huì)在控制臺(tái)輸出:

    Set-Cookie: c1=a; path=/; domain=yeetrack.com    Set-Cookie: c2=b; path="/", c3=c; domain="yeetrack.com"

HeaderIterator也提供非常便捷的方式,將Http消息解析成單獨(dú)的消息頭元素。

    HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");    response.addHeader("Set-Cookie", "c1=a; path=/; domain=yeetrack.com");    response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"yeetrack.com\"");    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]);        }    }

上述代碼會(huì)在控制臺(tái)輸出:

    c1 = a    path=/    domain=yeetrack.com    c2 = b    path=/    c3 = c    domain=yeetrack.com

1.1.4. Http實(shí)體

Http消息可以攜帶http實(shí)體,這個(gè)http實(shí)體既可以是http請(qǐng)求,也可以是http響應(yīng)的。Http實(shí)體,可以在某些http請(qǐng)求或者響應(yīng)中發(fā)現(xiàn),但不是必須的。Http規(guī)范中定義了兩種包含請(qǐng)求的方法:POST和PUT。HTTP響應(yīng)一般會(huì)包含一個(gè)內(nèi)容實(shí)體。當(dāng)然這條規(guī)則也有異常情況,如Head方法的響應(yīng),204沒(méi)有內(nèi)容,304沒(méi)有修改或者205內(nèi)容資源重置。

HttpClient根據(jù)來(lái)源的不同,劃分了三種不同的Http實(shí)體內(nèi)容。

  • streamed: Http內(nèi)容是通過(guò)流來(lái)接受或者generated on the fly。特別是,streamed這一類(lèi)包含從http響應(yīng)中獲取的實(shí)體內(nèi)容。一般說(shuō)來(lái),streamed實(shí)體是不可重復(fù)的。
  • self-contained: The content is in memory or obtained by means that are independent from a connection or other entity。self-contained類(lèi)型的實(shí)體內(nèi)容通常是可重復(fù)的。這種類(lèi)型的實(shí)體通常用于關(guān)閉http請(qǐng)求。
  • wrapping: 這種類(lèi)型的內(nèi)容是從另外的http實(shí)體中獲取的。

當(dāng)從Http響應(yīng)中讀取內(nèi)容時(shí),上面的三種區(qū)分對(duì)于連接管理器來(lái)說(shuō)是非常重要的。請(qǐng)求類(lèi)的實(shí)體通常由應(yīng)用程序創(chuàng)建,由HttpClient發(fā)送給服務(wù)器,在請(qǐng)求類(lèi)的實(shí)體中,streamed和self-contained兩種類(lèi)型的區(qū)別就不重要了。在這種情況下,一般認(rèn)為不可重復(fù)的實(shí)體是streamed類(lèi)型,可重復(fù)的實(shí)體時(shí)self-contained。

1.1.4.1. 可重復(fù)的實(shí)體

一個(gè)實(shí)體是可重復(fù)的,也就是說(shuō)它的包含的內(nèi)容可以被多次讀取。這種多次讀取只有self contained(自包含)的實(shí)體能做到(比如ByteArrayEntity或者StringEntity)。

1.1.4.2. 使用Http實(shí)體

由于一個(gè)Http實(shí)體既可以表示二進(jìn)制內(nèi)容,又可以表示文本內(nèi)容,所以Http實(shí)體要支持字符編碼(為了支持后者,即文本內(nèi)容)。

當(dāng)需要執(zhí)行一個(gè)完整內(nèi)容的Http請(qǐng)求或者Http請(qǐng)求已經(jīng)成功,服務(wù)器要發(fā)送響應(yīng)到客戶端時(shí),Http實(shí)體就會(huì)被創(chuàng)建。

如果要從Http實(shí)體中讀取內(nèi)容,我們可以利用HttpEntity類(lèi)的getContent方法來(lái)獲取實(shí)體的輸入流(java.io.InputStream),或者利用HttpEntity類(lèi)的writeTo(OutputStream)方法來(lái)獲取輸出流,這個(gè)方法會(huì)把所有的內(nèi)容寫(xiě)入到給定的流中。
當(dāng)實(shí)體類(lèi)已經(jīng)被接受后,我們可以利用HttpEntity類(lèi)的getContentType()getContentLength()方法來(lái)讀取Content-TypeContent-Length兩個(gè)頭消息(如果有的話)。由于Content-Type包含mime-types的字符編碼,比如text/plain或者text/html,HttpEntity類(lèi)的getContentEncoding()方法就是讀取這個(gè)編碼的。如果頭信息不存在,getContentLength()會(huì)返回-1,getContentType()會(huì)返回NULL。如果Content-Type信息存在,就會(huì)返回一個(gè)Header類(lèi)。

當(dāng)為發(fā)送消息創(chuàng)建Http實(shí)體時(shí),需要同時(shí)附加meta信息。

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

上述代碼會(huì)在控制臺(tái)輸出:

    Content-Type: text/plain; charset=utf-8    17    important message    17

1.1.5. 確保底層的資源連接被釋放

為了確保系統(tǒng)資源被正確地釋放,我們要么管理Http實(shí)體的內(nèi)容流、要么關(guān)閉Http響應(yīng)。

    CloseableHttpClient httpclient =  HttpClients.createDefault();    HttpGet httpget = new HttpGet("http://www.yeetrack.com/");    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();    }

關(guān)閉Http實(shí)體內(nèi)容流和關(guān)閉Http響應(yīng)的區(qū)別在于,前者通過(guò)消耗掉Http實(shí)體內(nèi)容來(lái)保持相關(guān)的http連接,然后后者會(huì)立即關(guān)閉、丟棄http連接。

請(qǐng)注意HttpEntitywriteTo(OutputStream)方法,當(dāng)Http實(shí)體被寫(xiě)入到OutputStream后,也要確保釋放系統(tǒng)資源。如果這個(gè)方法內(nèi)調(diào)用了HttpEntitygetContent()方法,那么它會(huì)有一個(gè)java.io.InpputStream的實(shí)例,我們需要在finally中關(guān)閉這個(gè)流。

但是也有這樣的情況,我們只需要獲取Http響應(yīng)內(nèi)容的一小部分,而獲取整個(gè)內(nèi)容并、實(shí)現(xiàn)連接的可重復(fù)性代價(jià)太大,這時(shí)我們可以通過(guò)關(guān)閉響應(yīng)的方式來(lái)關(guān)閉內(nèi)容輸入、輸出流。

    CloseableHttpClient httpclient = HttpClients.createDefault();    HttpGet httpget = new HttpGet("http://www.yeetrack.com/");    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();    }

上面的代碼執(zhí)行后,連接變得不可用,所有的資源都將被釋放。

1.1.6. 消耗Http實(shí)體內(nèi)容

HttpClient推薦使用HttpEntitygetConent()方法或者HttpEntitywriteTo(OutputStream)方法來(lái)消耗掉Http實(shí)體內(nèi)容。HttpClient也提供了EntityUtils這個(gè)類(lèi),這個(gè)類(lèi)提供一些靜態(tài)方法可以更容易地讀取Http實(shí)體的內(nèi)容和信息。和以java.io.InputStream流讀取內(nèi)容的方式相比,EntityUtils提供的方法可以以字符串或者字節(jié)數(shù)組的形式讀取Http實(shí)體。但是,強(qiáng)烈不推薦使用EntityUtils這個(gè)類(lèi),除非目標(biāo)服務(wù)器發(fā)出的響應(yīng)是可信任的,并且http響應(yīng)實(shí)體的長(zhǎng)度不會(huì)過(guò)大。

    CloseableHttpClient httpclient = HttpClients.createDefault();    HttpGet httpget = new HttpGet("http://www.yeetrack.com/");    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();    }

有些情況下,我們希望可以重復(fù)讀取Http實(shí)體的內(nèi)容。這就需要把Http實(shí)體內(nèi)容緩存在內(nèi)存或者磁盤(pán)上。最簡(jiǎn)單的方法就是把Http Entity轉(zhuǎn)化成BufferedHttpEntity,這樣就把原Http實(shí)體的內(nèi)容緩沖到了內(nèi)存中。后面我們就可以重復(fù)讀取BufferedHttpEntity中的內(nèi)容。

    CloseableHttpResponse response = <...>    HttpEntity entity = response.getEntity();    if (entity != null) {        entity = new BufferedHttpEntity(entity);    }

1.1.7. 創(chuàng)建Http實(shí)體內(nèi)容

HttpClient提供了一個(gè)類(lèi),這些類(lèi)可以通過(guò)http連接高效地輸出Http實(shí)體內(nèi)容。(原文是HttpClient provides several classes that can be used to efficiently stream out content though HTTP connections.感覺(jué)thought應(yīng)該是throught)HttpClient提供的這幾個(gè)類(lèi)涵蓋的常見(jiàn)的數(shù)據(jù)類(lèi)型,如String,byte數(shù)組,輸入流,和文件類(lèi)型: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://www.yeetrack.com/action.do");    httppost.setEntity(entity);

請(qǐng)注意由于InputStreamEntity只能從下層的數(shù)據(jù)流中讀取一次,所以它是不能重復(fù)的。推薦,通過(guò)繼承HttpEntity這個(gè)自包含的類(lèi)來(lái)自定義HttpEntity類(lèi),而不是直接使用InputStreamEntity這個(gè)類(lèi)。FileEntity就是一個(gè)很好的起點(diǎn)(FileEntity就是繼承的HttpEntity)。

1.7.1.1. HTML表單

很多應(yīng)用程序需要模擬提交Html表單的過(guò)程,舉個(gè)例子,登陸一個(gè)網(wǎng)站或者將輸入內(nèi)容提交給服務(wù)器。HttpClient提供了UrlEncodedFormEntity這個(gè)類(lèi)來(lái)幫助實(shí)現(xiàn)這一過(guò)程。

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

UrlEncodedFormEntity實(shí)例會(huì)使用所謂的Url編碼的方式對(duì)我們的參數(shù)進(jìn)行編碼,產(chǎn)生的結(jié)果如下:

    param1=value1&param2=value2

1.1.7.2. 內(nèi)容分塊

一般來(lái)說(shuō),推薦讓HttpClient自己根據(jù)Http消息傳遞的特征來(lái)選擇最合適的傳輸編碼。當(dāng)然,如果非要手動(dòng)控制也是可以的,可以通過(guò)設(shè)置HttpEntitysetChunked()為true。請(qǐng)注意:HttpClient僅會(huì)將這個(gè)參數(shù)看成是一個(gè)建議。如果Http的版本(如http 1.0)不支持內(nèi)容分塊,那么這個(gè)參數(shù)就會(huì)被忽略。

    StringEntity entity = new StringEntity("important message",    ContentType.create("plain/text", Consts.UTF_8));    entity.setChunked(true);    HttpPost httppost = new HttpPost("http://www.yeetrack.com/acrtion.do");    httppost.setEntity(entity);

1.1.8. Response handlers

最簡(jiǎn)單也是最方便的處理http響應(yīng)的方法就是使用ResponseHandler接口,這個(gè)接口中有handleResponse(HttpResponse response)方法。使用這個(gè)方法,用戶完全不用關(guān)心http連接管理器。當(dāng)使用ResponseHandler時(shí),HttpClient會(huì)自動(dòng)地將Http連接釋放給Http管理器,即使http請(qǐng)求失敗了或者拋出了異常。

    CloseableHttpClient httpclient = HttpClients.createDefault();    HttpGet httpget = new HttpGet("http://www.yeetrack.com/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);        }    };    //設(shè)置responseHandler,當(dāng)執(zhí)行http方法時(shí),就會(huì)返回MyJsonObject對(duì)象。    MyJsonObject myjson = client.execute(httpget, rh);

1.2. HttpClient接口

對(duì)于Http請(qǐng)求執(zhí)行過(guò)程來(lái)說(shuō),HttpClient的接口有著必不可少的作用。HttpClient接口沒(méi)有對(duì)Http請(qǐng)求的過(guò)程做特別的限制和詳細(xì)的規(guī)定,連接管理、狀態(tài)管理、授權(quán)信息和重定向處理這些功能都單獨(dú)實(shí)現(xiàn)。這樣用戶就可以更簡(jiǎn)單地拓展接口的功能(比如緩存響應(yīng)內(nèi)容)。

一般說(shuō)來(lái),HttpClient實(shí)際上就是一系列特殊的handler或者說(shuō)策略接口的實(shí)現(xiàn),這些handler(測(cè)試接口)負(fù)責(zé)著處理Http協(xié)議的某一方面,比如重定向、認(rèn)證處理、有關(guān)連接持久性和keep alive持續(xù)時(shí)間的決策。這樣就允許用戶使用自定義的參數(shù)來(lái)代替默認(rèn)配置,實(shí)現(xiàn)個(gè)性化的功能。

    ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() {        @Override        public long getKeepAliveDuration(            HttpResponse response,            HttpContext context) {                long keepAlive = super.getKeepAliveDuration(response, context);                if (keepAlive == -1) {                    //如果服務(wù)器沒(méi)有設(shè)置keep-alive這個(gè)參數(shù),我們就把它設(shè)置成5秒                    keepAlive = 5000;                }                return keepAlive;        }    };    //定制我們自己的httpclient    CloseableHttpClient httpclient = HttpClients.custom()            .setKeepAliveStrategy(keepAliveStrat)            .build();

1.2.1.HttpClient的線程安全性

HttpClient已經(jīng)實(shí)現(xiàn)了線程安全。所以希望用戶在實(shí)例化HttpClient時(shí),也要支持為多個(gè)請(qǐng)求使用。

1.2.2.HttpClient的內(nèi)存分配

當(dāng)一個(gè)CloseableHttpClient的實(shí)例不再被使用,并且它的作用范圍即將失效,和它相關(guān)的連接必須被關(guān)閉,關(guān)閉方法可以調(diào)用CloseableHttpClientclose()方法。

    CloseableHttpClient httpclient = HttpClients.createDefault();    try {        <...>    } finally {        //關(guān)閉連接        httpclient.close();    }

1.3.Http執(zhí)行上下文

最初,Http被設(shè)計(jì)成一種無(wú)狀態(tài)的、面向請(qǐng)求-響應(yīng)的協(xié)議。然而,在實(shí)際使用中,我們希望能夠在一些邏輯相關(guān)的請(qǐng)求-響應(yīng)中,保持狀態(tài)信息。為了使應(yīng)用程序可以保持Http的持續(xù)狀態(tài),HttpClient允許http連接在特定的Http上下文中執(zhí)行。如果在持續(xù)的http請(qǐng)求中使用了同樣的上下文,那么這些請(qǐng)求就可以被分配到一個(gè)邏輯會(huì)話中。HTTP上下文就和一個(gè)java.util.Map<String, Object>功能類(lèi)似。它實(shí)際上就是一個(gè)任意命名的值的集合。應(yīng)用程序可以在Http請(qǐng)求執(zhí)行前填充上下文的值,也可以在請(qǐng)求執(zhí)行完畢后檢查上下文。

HttpContext可以包含任意類(lèi)型的對(duì)象,因此如果在多線程中共享上下文會(huì)不安全。推薦每個(gè)線程都只包含自己的http上下文。

在Http請(qǐng)求執(zhí)行的過(guò)程中,HttpClient會(huì)自動(dòng)添加下面的屬性到Http上下文中:

  • HttpConnection的實(shí)例,表示客戶端與服務(wù)器之間的連接
  • HttpHost的實(shí)例,表示要連接的木包服務(wù)器
  • HttpRoute的實(shí)例,表示全部的連接路由
  • HttpRequest的實(shí)例,表示Http請(qǐng)求。在執(zhí)行上下文中,最終的HttpRequest對(duì)象會(huì)代表http消息的狀態(tài)。Http/1.0和Http/1.1都默認(rèn)使用相對(duì)的uri。但是如果使用了非隧道模式的代理服務(wù)器,就會(huì)使用絕對(duì)路徑的uri。
  • HttpResponse的實(shí)例,表示Http響應(yīng)
  • java.lang.Boolean對(duì)象,表示是否請(qǐng)求被成功的發(fā)送給目標(biāo)服務(wù)器
  • RequestConfig對(duì)象,表示http request的配置信息
  • java.util.List<Uri>對(duì)象,表示Http響應(yīng)中的所有重定向地址

我們可以使用HttpClientContext這個(gè)適配器來(lái)簡(jiǎn)化和上下文交互的過(guò)程。

    HttpContext context = <...>    HttpClientContext clientContext = HttpClientContext.adapt(context);    HttpHost target = clientContext.getTargetHost();    HttpRequest request = clientContext.getRequest();    HttpResponse response = clientContext.getResponse();    RequestConfig config = clientContext.getRequestConfig();

同一個(gè)邏輯會(huì)話中的多個(gè)Http請(qǐng)求,應(yīng)該使用相同的Http上下文來(lái)執(zhí)行,這樣就可以自動(dòng)地在http請(qǐng)求中傳遞會(huì)話上下文和狀態(tài)信息。
在下面的例子中,我們?cè)陂_(kāi)頭設(shè)置的參數(shù),會(huì)被保存在上下文中,并且會(huì)應(yīng)用到后續(xù)的http請(qǐng)求中(源英文中有個(gè)拼寫(xiě)錯(cuò)誤)。

    CloseableHttpClient httpclient = HttpClients.createDefault();    RequestConfig requestConfig = RequestConfig.custom()            .setSocketTimeout(1000)            .setConnectTimeout(1000)            .build();    HttpGet httpget1 = new HttpGet("http://www.yeetrack.com/1");    httpget1.setConfig(requestConfig);    CloseableHttpResponse response1 = httpclient.execute(httpget1, context);    try {        HttpEntity entity1 = response1.getEntity();    } finally {        response1.close();    }    //httpget2被執(zhí)行時(shí),也會(huì)使用httpget1的上下文    HttpGet httpget2 = new HttpGet("http://www.yeetrack.com/2");    CloseableHttpResponse response2 = httpclient.execute(httpget2, context);    try {        HttpEntity entity2 = response2.getEntity();    } finally {        response2.close();    }

1.4. 異常處理

HttpClient會(huì)被拋出兩種類(lèi)型的異常,一種是java.io.IOException,當(dāng)遇到I/O異常時(shí)拋出(socket超時(shí),或者socket被重置);另一種是HttpException,表示Http失敗,如Http協(xié)議使用不正確。通常認(rèn)為,I/O錯(cuò)誤時(shí)不致命、可修復(fù)的,而Http協(xié)議錯(cuò)誤是致命了,不能自動(dòng)修復(fù)的錯(cuò)誤。

1.4.1.HTTP傳輸安全

Http協(xié)議不能滿足所有類(lèi)型的應(yīng)用場(chǎng)景,我們需要知道這點(diǎn)。Http是個(gè)簡(jiǎn)單的面向協(xié)議的請(qǐng)求/響應(yīng)的協(xié)議,當(dāng)初它被設(shè)計(jì)用來(lái)支持靜態(tài)或者動(dòng)態(tài)生成的內(nèi)容檢索,之前從來(lái)沒(méi)有人想過(guò)讓它支持事務(wù)性操作。例如,Http服務(wù)器成功接收、處理請(qǐng)求后,生成響應(yīng)消息,并且把狀態(tài)碼發(fā)送給客戶端,這個(gè)過(guò)程是Http協(xié)議應(yīng)該保證的。但是,如果客戶端由于讀取超時(shí)、取消請(qǐng)求或者系統(tǒng)崩潰導(dǎo)致接收響應(yīng)失敗,服務(wù)器不會(huì)回滾這一事務(wù)。如果客戶端重新發(fā)送這個(gè)請(qǐng)求,服務(wù)器就會(huì)重復(fù)的解析、執(zhí)行這個(gè)事務(wù)。在一些情況下,這會(huì)導(dǎo)致應(yīng)用程序的數(shù)據(jù)損壞和應(yīng)用程序的狀態(tài)不一致。

即使Http當(dāng)初設(shè)計(jì)是不支持事務(wù)操作,但是它仍舊可以作為傳輸協(xié)議為某些關(guān)鍵程序提供服務(wù)。為了保證Http傳輸層的安全性,系統(tǒng)必須保證應(yīng)用層上的http方法的冪等性(To ensure HTTP transport layer safety the system must ensure the idempotency of HTTP methods on the application layer)。

1.4.2.方法的冪等性

HTTP/1.1規(guī)范中是這樣定義冪等方法的,Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request。用其他話來(lái)說(shuō),應(yīng)用程序需要正確地處理同一方法多次執(zhí)行造成的影響。添加一個(gè)具有唯一性的id就能避免重復(fù)執(zhí)行同一個(gè)邏輯請(qǐng)求,問(wèn)題解決。

請(qǐng)知曉,這個(gè)問(wèn)題不只是HttpClient才會(huì)有,基于瀏覽器的應(yīng)用程序也會(huì)遇到Http方法不冪等的問(wèn)題。

HttpClient默認(rèn)把非實(shí)體方法gethead方法看做冪等方法,把實(shí)體方法postput方法看做非冪等方法。

1.4.3.異常自動(dòng)修復(fù)

默認(rèn)情況下,HttpClient會(huì)嘗試自動(dòng)修復(fù)I/O異常。這種自動(dòng)修復(fù)僅限于修復(fù)幾個(gè)公認(rèn)安全的異常。

  • HttpClient不會(huì)嘗試修復(fù)任何邏輯或者h(yuǎn)ttp協(xié)議錯(cuò)誤(即從HttpException衍生出來(lái)的異常)。
  • HttpClient會(huì)自動(dòng)再次發(fā)送冪等的方法(如果首次執(zhí)行失敗。
  • HttpClient會(huì)自動(dòng)再次發(fā)送遇到transport異常的方法,前提是Http請(qǐng)求仍舊保持著連接(例如http請(qǐng)求沒(méi)有全部發(fā)送給目標(biāo)服務(wù)器,HttpClient會(huì)再次嘗試發(fā)送)。

1.4.4.請(qǐng)求重試handler

如果要自定義異常處理機(jī)制,我們需要實(shí)現(xiàn)HttpRequestRetryHandler接口。

    HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {        public boolean retryRequest(                IOException exception,                int executionCount,                HttpContext context) {            if (executionCount >= 5) {                // 如果已經(jīng)重試了5次,就放棄                return false;            }            if (exception instanceof InterruptedIOException) {                // 超時(shí)                return false;            }            if (exception instanceof UnknownHostException) {                // 目標(biāo)服務(wù)器不可達(dá)                return false;            }            if (exception instanceof ConnectTimeoutException) {                // 連接被拒絕                return false;            }            if (exception instanceof SSLException) {                // ssl握手異常                return false;            }            HttpClientContext clientContext = HttpClientContext.adapt(context);            HttpRequest request = clientContext.getRequest();            boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);            if (idempotent) {                // 如果請(qǐng)求是冪等的,就再次嘗試                return true;            }            return false;        }    };      CloseableHttpClient httpclient = HttpClients.custom()            .setRetryHandler(myRetryHandler)            .build();

1.5.終止請(qǐng)求

有時(shí)候由于目標(biāo)服務(wù)器負(fù)載過(guò)高或者客戶端目前有太多請(qǐng)求積壓,http請(qǐng)求不能在指定時(shí)間內(nèi)執(zhí)行完畢。這時(shí)候終止這個(gè)請(qǐng)求,釋放阻塞I/O的進(jìn)程,就顯得很必要。通過(guò)HttpClient執(zhí)行的Http請(qǐng)求,在任何狀態(tài)下都能通過(guò)調(diào)用HttpUriRequestabort()方法來(lái)終止。這個(gè)方法是線程安全的,并且能在任何線程中調(diào)用。當(dāng)Http請(qǐng)求被終止了,本線程(即使現(xiàn)在正在阻塞I/O)也會(huì)通過(guò)拋出一個(gè)InterruptedIOException異常,來(lái)釋放資源。

1.6. Http協(xié)議攔截器

HTTP協(xié)議攔截器是一種實(shí)現(xiàn)一個(gè)特定的方面的HTTP協(xié)議的代碼程序。通常情況下,協(xié)議攔截器會(huì)將一個(gè)或多個(gè)頭消息加入到接受或者發(fā)送的消息中。協(xié)議攔截器也可以操作消息的內(nèi)容實(shí)體—消息內(nèi)容的壓縮/解壓縮就是個(gè)很好的例子。通常,這是通過(guò)使用“裝飾”開(kāi)發(fā)模式,一個(gè)包裝實(shí)體類(lèi)用于裝飾原來(lái)的實(shí)體來(lái)實(shí)現(xiàn)。一個(gè)攔截器可以合并,形成一個(gè)邏輯單元。

協(xié)議攔截器可以通過(guò)共享信息協(xié)作——比如處理狀態(tài)——通過(guò)HTTP執(zhí)行上下文。協(xié)議攔截器可以使用Http上下文存儲(chǔ)一個(gè)或者多個(gè)連續(xù)請(qǐng)求的處理狀態(tài)。

通常,只要攔截器不依賴(lài)于一個(gè)特定狀態(tài)的http上下文,那么攔截執(zhí)行的順序就無(wú)所謂。如果協(xié)議攔截器有相互依賴(lài)關(guān)系,必須以特定的順序執(zhí)行,那么它們應(yīng)該按照特定的順序加入到協(xié)議處理器中。

協(xié)議處理器必須是線程安全的。類(lèi)似于servlets,協(xié)議攔截器不應(yīng)該使用變量實(shí)體,除非訪問(wèn)這些變量是同步的(線程安全的)。

下面是個(gè)例子,講述了本地的上下文時(shí)如何在連續(xù)請(qǐng)求中記錄處理狀態(tài)的:

    CloseableHttpClient httpclient = HttpClients.custom()            .addInterceptorLast(new HttpRequestInterceptor() {                public void process(                        final HttpRequest request,                        final HttpContext context) throws HttpException, IOException {                        //AtomicInteger是個(gè)線程安全的整型類(lèi)                    AtomicInteger count = (AtomicInteger) context.getAttribute("count");                    request.addHeader("Count", Integer.toString(count.getAndIncrement()));                }            })            .build();    AtomicInteger count = new AtomicInteger(1);    HttpClientContext localContext = HttpClientContext.create();    localContext.setAttribute("count", count);    HttpGet httpget = new HttpGet("http://www.yeetrack.com/");    for (int i = 0; i < 10; i++) {        CloseableHttpResponse response = httpclient.execute(httpget, localContext);        try {            HttpEntity entity = response.getEntity();        } finally {            response.close();        }    }

上面代碼在發(fā)送http請(qǐng)求時(shí),會(huì)自動(dòng)添加Count這個(gè)header,可以使用wireshark抓包查看。

1.7.1. 重定向處理

HttpClient會(huì)自動(dòng)處理所有類(lèi)型的重定向,除了那些Http規(guī)范明確禁止的重定向。See Other (status code 303) redirects on POST and PUT requests are converted to GET requests as required by the HTTP specification. 我們可以使用自定義的重定向策略來(lái)放松Http規(guī)范對(duì)Post方法重定向的限制。

    //LaxRedirectStrategy可以自動(dòng)重定向所有的HEAD,GET,POST請(qǐng)求,解除了http規(guī)范對(duì)post請(qǐng)求重定向的限制。    LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();    CloseableHttpClient httpclient = HttpClients.custom()            .setRedirectStrategy(redirectStrategy)            .build();

HttpClient在請(qǐng)求執(zhí)行過(guò)程中,經(jīng)常需要重寫(xiě)請(qǐng)求的消息。 HTTP/1.0和HTTP/1.1都默認(rèn)使用相對(duì)的uri路徑。同樣,原始的請(qǐng)求可能會(huì)被一次或者多次的重定向。最終結(jié)對(duì)路徑的解釋可以使用最初的請(qǐng)求和上下文。URIUtils類(lèi)的resolve方法可以用于將攔截的絕對(duì)路徑構(gòu)建成最終的請(qǐng)求。這個(gè)方法包含了最后一個(gè)分片標(biāo)識(shí)符或者原始請(qǐng)求。

    CloseableHttpClient httpclient = HttpClients.createDefault();    HttpClientContext context = HttpClientContext.create();    HttpGet httpget = new HttpGet("http://www.yeetrack.com:8080/");    CloseableHttpResponse response = httpclient.execute(httpget, context);    try {        HttpHost target = context.getTargetHost();        List<URI> redirectLocations = context.getRedirectLocations();        URI location = URIUtils.resolve(httpget.getURI(), target, redirectLocations);        System.out.println("Final HTTP location: " + location.toASCIIString());        // 一般會(huì)取得一個(gè)絕對(duì)路徑的uri    } finally {        response.close();    }
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
httpClient基礎(chǔ)
HttpComponents入門(mén)解析
Java使用HttpClient
HttpClient學(xué)習(xí)筆記
HttpClient4.0.1應(yīng)用指南
Android封裝的http請(qǐng)求實(shí)用工具類(lèi)
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服