HttpClient 4.5教程學(xué)習(xí)與翻譯——第1章:基礎(chǔ)

原文鏈接

第1章:基礎(chǔ)

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

HttpClient最重要的功能是執(zhí)行HTTP方法。 執(zhí)行HTTP方法涉及一個(gè)或多個(gè)HTTP請(qǐng)求/ HTTP響應(yīng)交換奇适,通常由HttpClient內(nèi)部處理况凉。 期望用戶提供要執(zhí)行的請(qǐng)求對(duì)象进鸠,并且HttpClient期望將請(qǐng)求發(fā)送到目標(biāo)服務(wù)器返回相應(yīng)的響應(yīng)對(duì)象,或者如果執(zhí)行不成功則拋出異常碱屁。

HttpClient API的主要切入點(diǎn)自然是定義了上述合約的HttpClient接口磷脯。

以下是最簡(jiǎn)單形式的請(qǐng)求執(zhí)行過(guò)程示例:

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

1.1.1. HTTP 請(qǐng)求

所有HTTP請(qǐng)求都有一個(gè)請(qǐng)求行,包括方法名稱娩脾,請(qǐng)求URI和HTTP協(xié)議版本赵誓。

HttpClient支持開(kāi)箱即用的HTTP / 1.1規(guī)范中定義的所有HTTP方法:GET,HEAD柿赊,POST俩功,PUT,DELETE碰声,TRACE和OPTIONS诡蜓。 每種方法類型都有一個(gè)特定的類:HttpGet,HttpHead胰挑,HttpPost蔓罚,HttpPut,HttpDelete瞻颂,HttpTrace和HttpOptions豺谈。

Request-URI是統(tǒng)一資源標(biāo)識(shí)符,用于標(biāo)識(shí)應(yīng)用請(qǐng)求的資源贡这。 HTTP請(qǐng)求URI由協(xié)議方案核无,主機(jī)名,可選端口藕坯,資源路徑,可選查詢和可選片段組成噪沙。

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

HttpClient提供了URIBuilder實(shí)用程序類炼彪,以簡(jiǎn)化請(qǐng)求URI的創(chuàng)建和修改。

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

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

1.1.2. HTTP 響應(yīng)

HTTP響應(yīng)是服務(wù)器在接收并解釋請(qǐng)求消息后發(fā)送回客戶端的消息正歼。 該消息的第一行包括協(xié)議版本辐马,后跟數(shù)字狀態(tài)代碼及其相關(guān)的文本短語(yǔ)。

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

1.1.3. 使用消息頭

HTTP消息可以包含許多描述消息屬性的消息頭局义,例如內(nèi)容長(zhǎng)度喜爷,內(nèi)容類型等冗疮。 HttpClient提供了檢索,添加檩帐,刪除和枚舉消息頭的方法术幔。

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接口。

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消息解析為單個(gè)頭元素的便捷方法湃密。

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

1.1.4. HTTP 實(shí)體

HTTP消息可以攜帶與請(qǐng)求或響應(yīng)相關(guān)聯(lián)的內(nèi)容實(shí)體诅挑。 實(shí)體可以在某些請(qǐng)求和某些響應(yīng)中找到,因?yàn)樗鼈兪强蛇x的泛源。 使用實(shí)體的請(qǐng)求稱為封閉請(qǐng)求的實(shí)體(entity enclosing requests)拔妥。 HTTP規(guī)范定義了兩個(gè)封閉請(qǐng)求方法的實(shí)體:POST和PUT。 通常期望響應(yīng)包含內(nèi)容實(shí)體达箍。 此規(guī)則有例外没龙,例如對(duì)HEAD方法的響應(yīng)和204 No Content,304 Not Modified缎玫,205 Reset Content響應(yīng)硬纤。

HttpClient區(qū)分三種實(shí)體,具體取決于其內(nèi)容的來(lái)源:

  • 流式:內(nèi)容從流中接收碘梢,或在運(yùn)行中生成咬摇。 特別是,此類別包括從HTTP響應(yīng)接收的實(shí)體煞躬。 流式實(shí)體通常不可重復(fù)肛鹏。
  • 自包含:內(nèi)容在內(nèi)存中或通過(guò)獨(dú)立于連接或其他實(shí)體的方式獲得。 自包含實(shí)體通常是可重復(fù)的恩沛。 這種類型的實(shí)體主要用于封閉HTTP請(qǐng)求的實(shí)體在扰。
  • 包裝:內(nèi)容從另一個(gè)實(shí)體獲得。

當(dāng)從HTTP響應(yīng)中流出內(nèi)容時(shí)雷客,這種區(qū)別對(duì)于連接管理很重要芒珠。 對(duì)于由應(yīng)用程序創(chuàng)建并僅使用HttpClient發(fā)送的請(qǐng)求實(shí)體,流式和自包含之間的差異并不重要搅裙。 在這種情況下皱卓,建議將不可重復(fù)的實(shí)體視為流式實(shí)體,將那些可重復(fù)的實(shí)體視為自包含實(shí)體部逮。

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

可重復(fù)實(shí)體意味著其內(nèi)容可以被多次讀取娜汁。 這僅適用于自包含實(shí)體(如ByteArrayEntity或StringEntity)。

1.1.4.2. 使用HTTP 實(shí)體

由于實(shí)體可以表示二進(jìn)制和字符內(nèi)容兄朋,因此它支持字符編碼(支持后者的掐禁,即字符內(nèi)容)。

在執(zhí)行帶有附加內(nèi)容的請(qǐng)求時(shí)或者請(qǐng)求成功并且使用響應(yīng)主體將結(jié)果發(fā)送回客戶端時(shí),將創(chuàng)建實(shí)體傅事。

要從實(shí)體讀取內(nèi)容缕允,可以通過(guò)HttpEntity#getContent()方法檢索輸入流,該方法返回java.io.InputStream蹭越,或者可以向HttpEntity#writeTo(OutputStream)方法提供輸出流障本, 一旦所有內(nèi)容都寫(xiě)入給定流,它將返回般又。

當(dāng)接收到具有傳入消息的實(shí)體時(shí)彼绷,方法HttpEntity#getContentType()和HttpEntity#getContentLength()可用于讀取公共元數(shù)據(jù),例如Content-Type和Content-Length消息頭(如果它們可用)茴迁。

由于Content-Type標(biāo)頭可以包含文本MIME類型(如text/plain或text/html)的字符編碼寄悯,因此HttpEntity#getContentEncoding()方法用于讀取此信息。 如果消息頭不可用堕义,則返回長(zhǎng)度-1猜旬,內(nèi)容類型為NULL。 如果Content-Type標(biāo)頭可用倦卖,則將返回Header對(duì)象洒擦。

在為傳出消息創(chuàng)建實(shí)體時(shí),該元數(shù)據(jù)必須由實(shí)體的創(chuàng)建者提供怕膛。

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. 確保低水平資源的釋放

為了確保正確釋放系統(tǒng)資源熟嫩,必須關(guān)閉與實(shí)體關(guān)聯(lián)的內(nèi)容流或響應(yīng)本身

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

關(guān)閉內(nèi)容流和關(guān)閉響應(yīng)之間的區(qū)別在于前者將嘗試通過(guò)使用實(shí)體內(nèi)容來(lái)保持底層連接處于活動(dòng)狀態(tài),而后者立即關(guān)閉并丟棄連接褐捻。

請(qǐng)注意掸茅,一旦實(shí)體完全寫(xiě)出,還需要HttpEntity#writeTo(OutputStream)方法來(lái)確保正確釋放系統(tǒng)資源柠逞。 如果通過(guò)調(diào)用HttpEntity#getContent()方法獲取了java.io.InputStream的實(shí)例昧狮,則還應(yīng)該在finally代碼塊中關(guān)閉該流。

使用流式實(shí)體時(shí)板壮,可以使用EntityUtils#consume(HttpEntity)方法確保實(shí)體內(nèi)容已完全消耗且基礎(chǔ)流已關(guān)閉逗鸣。

然而,可能存在這樣的情況:當(dāng)只需要檢索整個(gè)響應(yīng)內(nèi)容的一小部分并且消耗剩余內(nèi)容并使連接可重用時(shí)的性能損失太高绰精。在這種情況下撒璧,可以通過(guò)關(guān)閉響應(yīng)來(lái)終止內(nèi)容流。

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

連接不會(huì)被重用笨使,但它所擁有的所有的級(jí)別資源都將被正確釋放沪悲。

1.1.6. 消費(fèi)實(shí)體內(nèi)容

消費(fèi)實(shí)體內(nèi)容的推薦方法是使用其HttpEntity#getContent()或HttpEntity#writeTo(OutputStream)方法。 HttpClient還附帶了EntityUtils類阱表,它公開(kāi)了幾種靜態(tài)方法椰苟,以便更容易地從實(shí)體中讀取內(nèi)容或信息。 可以使用此類中的方法檢索字符串/字節(jié)數(shù)組中的整個(gè)內(nèi)容主體翩概,而不是直接讀取java.io.InputStream汪疮。 但是,強(qiáng)烈建議不要使用EntityUtils爱致,除非響應(yīng)實(shí)體來(lái)自可信HTTP服務(wù)器并且知曉響應(yīng)內(nèi)容長(zhǎng)度有限烤送。

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

在某些情況下,可能需要能夠多次讀取實(shí)體內(nèi)容糠悯。 在這種情況下帮坚,實(shí)體內(nèi)容必須以某種方式緩沖,無(wú)論是在內(nèi)存中還是在磁盤(pán)上互艾。 實(shí)現(xiàn)這一目標(biāo)的最簡(jiǎn)單方法是使用BufferedHttpEntity類包裝原始實(shí)體试和。 這將導(dǎo)致原始實(shí)體的內(nèi)容被讀入內(nèi)存緩沖區(qū)。 在所有其他方式中纫普,實(shí)體包裝器(entity wrapper)將具有原始的實(shí)體包裝器阅悍。

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

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

HttpClient提供了幾個(gè)類,可用于通過(guò)HTTP連接有效地流出內(nèi)容昨稼。 這些類的實(shí)例可以與封閉請(qǐng)求(如POST和PUT)的實(shí)體相關(guān)聯(lián)节视,以便將實(shí)體內(nèi)容封裝到要被傳出的HTTP請(qǐng)求中。 HttpClient為大多數(shù)常見(jiàn)數(shù)據(jù)容器提供了幾個(gè)類假栓,如字符串寻行,字節(jié)數(shù)組,輸入流和文件: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);

請(qǐng)注意棋凳,InputStreamEntity不可重復(fù)拦坠,因?yàn)樗荒軓幕A(chǔ)數(shù)據(jù)流中讀取一次。 通常剩岳,建議實(shí)現(xiàn)自定義的HttpEntity類贞滨,而不是使用通用的InputStreamEntity。 FileEntity可以是一個(gè)很好的起點(diǎn)拍棕。

1.1.7.1. HTML 表單

例如晓铆,許多應(yīng)用程序需要模擬提交HTML表單的過(guò)程,以便登錄Web應(yīng)用程序或提交輸入數(shù)據(jù)绰播。 HttpClient提供實(shí)體類UrlEncodedFormEntity以方便該過(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://localhost/handler.do");
httppost.setEntity(entity);

UrlEncodedFormEntity實(shí)例將使用所謂的URL編碼對(duì)參數(shù)進(jìn)行編碼并生成以下內(nèi)容:
param1=value1&param2=value2

1.1.7.2. 內(nèi)容分塊

通常,建議讓HttpClient根據(jù)要傳輸?shù)腍TTP消息的屬性選擇最合適的傳輸編碼蠢箩。 但是链蕊,通過(guò)將HttpEntit#setChunked()設(shè)置為true事甜,可以通知HttpClient優(yōu)先使用塊編碼。 請(qǐng)注意滔韵,HttpClient將僅使用此標(biāo)志作為提示逻谦。 使用不支持塊編碼的HTTP協(xié)議版本(例如HTTP / 1.0)時(shí),將忽略此值陪蜻。

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. 響應(yīng)處理

處理響應(yīng)的最簡(jiǎn)單和最方便的方法是使用ResponseHandler接口邦马,該接口包含handleResponse(HttpResponse response)方法。 該方法使用完全戶不必?fù)?dān)心連接管理宴卖。 使用ResponseHandler時(shí)滋将,無(wú)論請(qǐng)求執(zhí)行成功還是導(dǎo)致異常,HttpClient都會(huì)自動(dòng)確保將連接釋放回連接管理器症昏。

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請(qǐng)求執(zhí)行的最重要的約定随闽。 它對(duì)請(qǐng)求執(zhí)行過(guò)程沒(méi)有任何限制或特定細(xì)節(jié),并且將連接管理齿兔,狀態(tài)管理橱脸,身份驗(yàn)證和重定向處理的細(xì)節(jié)留給單個(gè)實(shí)現(xiàn)。 這應(yīng)該可以更容易地使用響應(yīng)內(nèi)容緩存等附加功能來(lái)裝飾界面分苇。

通常添诉,HttpClient的諸多配置實(shí)現(xiàn)了許多特殊用途處理程序或策略接口實(shí)現(xiàn)的功能,這些處理程序或策略接口實(shí)現(xiàn)負(fù)責(zé)處理HTTP協(xié)議的特定方面医寿,例如重定向或身份驗(yàn)證處理栏赴,或者決定連接持久性和持續(xù)時(shí)間。這使用戶能夠有選擇地將這些方面的默認(rèn)實(shí)現(xiàn)替換為定制的靖秩、特定于應(yīng)用程序的實(shí)現(xiàn)须眷。

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實(shí)現(xiàn)是線程安全的。 建議將此類的同一實(shí)例重用于多個(gè)請(qǐng)求執(zhí)行沟突。

1.2.2. HttpClient 資源釋放

當(dāng)不再需要實(shí)例CloseableHttpClient并且即將超出使用范圍時(shí)花颗,必須通過(guò)調(diào)用CloseableHttpClient#close()方法關(guān)閉與其關(guān)聯(lián)的連接管理器。

CloseableHttpClient httpclient = HttpClients.createDefault();
try {
    <...>
} finally {
    httpclient.close();
}

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

最初惠拭,HTTP被設(shè)計(jì)為無(wú)狀態(tài)扩劝,面向響應(yīng)請(qǐng)求的協(xié)議。 但是职辅,真實(shí)世界的應(yīng)用程序通常需要能夠通過(guò)幾個(gè)邏輯上相關(guān)的請(qǐng)求-響應(yīng)交換來(lái)持久保存狀態(tài)信息棒呛。 為了使應(yīng)用程序能夠維持處理狀態(tài),HttpClient允許在特定的執(zhí)行上下文中執(zhí)行HTTP請(qǐng)求域携,稱為HTTP上下文(HttpContext)簇秒。 如果在連續(xù)請(qǐng)求之間重用相同的上下文,則多個(gè)邏輯相關(guān)的請(qǐng)求可以參與邏輯會(huì)話秀鞭。 HTTP上下文函數(shù)與java.util.Map<String, Object>類似趋观。 它一個(gè)簡(jiǎn)單的名字-值的集合扛禽。 應(yīng)用程序可以在請(qǐng)求執(zhí)行之前填充上下文屬性,也可以在執(zhí)行完成后檢查上下文拆内。

HttpContext可以包含任意對(duì)象旋圆,因此在多個(gè)線程之間共享可能不安全。 建議每個(gè)執(zhí)行線程都維護(hù)自己的上下文麸恍。
在HTTP請(qǐng)求執(zhí)行過(guò)程中,HttpClient將以下屬性添加到執(zhí)行上下文中:

  • HttpConnection 實(shí)例表示與目標(biāo)服務(wù)器的實(shí)際連接搀矫。
  • HttpHost 實(shí)例表示連接的目標(biāo)抹沪。
  • HttpRoute 實(shí)例表示完整的連接路由
  • HttpReques t實(shí)例表示實(shí)際HTTP請(qǐng)求。 執(zhí)行上下文中的最終HttpRequest對(duì)象始終表示消息的狀態(tài)它與發(fā)送到目標(biāo)服務(wù)器的狀態(tài)完全相同瓤球。 默認(rèn)HTTP/1.0和HTTP/1.1使用相對(duì)請(qǐng)求URI融欧。 但是,如果請(qǐng)求是通過(guò)代理以非隧道模式發(fā)送的卦羡,那么URI將是絕對(duì)的噪馏。
  • HttpResponse實(shí)例表示實(shí)際的HTTP響應(yīng)。
  • java.lang.Boolean對(duì)象绿饵,表示指示實(shí)際請(qǐng)求是否已完全傳輸?shù)竭B接目標(biāo)的標(biāo)志欠肾。
  • RequestConfig對(duì)象表示實(shí)際的請(qǐng)求配置。
  • java.util.List <URI>對(duì)象拟赊,表示在請(qǐng)求執(zhí)行過(guò)程中收到的所有重定向位置的集合刺桃。

可以使用HttpClientContext適配器類來(lái)簡(jiǎn)化與上下文狀態(tài)的交互。

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

應(yīng)使用相同的HttpContext 實(shí)例執(zhí)行表示邏輯相關(guān)會(huì)話的多個(gè)請(qǐng)求序列吸祟,以確保在請(qǐng)求之間自動(dòng)傳播會(huì)話上下文和狀態(tài)信息瑟慈。

在以下示例中,初始請(qǐng)求設(shè)置的請(qǐng)求配置將保留在執(zhí)行上下文中屋匕,并傳播到共享相同上下文的連續(xù)請(qǐng)求葛碧。

CloseableHttpClient httpclient = HttpClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom()
        .setSocketTimeout(1000)
        .setConnectTimeout(1000)
        .build();

HttpGet httpget1 = new HttpGet("http://localhost/1");
httpget1.setConfig(requestConfig);
CloseableHttpResponse response1 = httpclient.execute(httpget1, context);
try {
    HttpEntity entity1 = response1.getEntity();
} finally {
    response1.close();
}
HttpGet httpget2 = new HttpGet("http://localhost/2");
CloseableHttpResponse response2 = httpclient.execute(httpget2, context);
try {
    HttpEntity entity2 = response2.getEntity();
} finally {
    response2.close();
}

1.4. HTTP 協(xié)議攔截器

HTTP協(xié)議攔截器是實(shí)現(xiàn)HTTP協(xié)議的特定方面的例程。通常过吻,期望協(xié)議攔截器作用于傳入消息的一個(gè)特定報(bào)頭或一組相關(guān)報(bào)頭进泼,或者用一個(gè)特定報(bào)頭或一組相關(guān)報(bào)頭填充傳出消息。協(xié)議攔截器也可以操縱用消息附帶的內(nèi)容實(shí)體-透明內(nèi)容壓縮/解壓縮就是一個(gè)很好的例子疮装。通常這是通過(guò)使用'Decorator'模式來(lái)完成的缘琅,其中包裝器實(shí)體類用于裝飾原始實(shí)體±疲可以組合幾個(gè)協(xié)議攔截器以形成一個(gè)邏輯單元刷袍。

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

通常呻纹,執(zhí)行攔截器的順序無(wú)關(guān)緊要堆生,只要它們不依賴于執(zhí)行上下文的特定狀態(tài)即可。 如果協(xié)議攔截器具有相互依賴性雷酪,因此必須按特定順序執(zhí)行淑仆,則應(yīng)按照與預(yù)期執(zhí)行順序相同的順序?qū)⑺鼈兲砑拥絽f(xié)議處理器中。

協(xié)議攔截器必須實(shí)現(xiàn)為線程安全的哥力。 與servlet類似蔗怠,協(xié)議攔截器不應(yīng)使用實(shí)例變量,除非同步訪問(wèn)這些變量吩跋。

這是一個(gè)如何使用本地上下文在連續(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 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://localhost/");
for (int i = 0; i < 10; i++) {
    CloseableHttpResponse response = httpclient.execute(httpget, localContext);
    try {
        HttpEntity entity = response.getEntity();
    } finally {
        response.close();
    }
}

1.5. 異常處理

HTTP 協(xié)議處理器可以拋出兩種類型的異常:java.io.IOException在I/O失敗的情況下拋出寞射,例如套接字超時(shí)或套接字重置和HttpException,它表示HTTP故障锌钮,例如違反HTTP協(xié)議桥温。 通常,I/O錯(cuò)誤被認(rèn)為是非致命和可恢復(fù)的梁丘,而HTTP協(xié)議錯(cuò)誤被認(rèn)為是致命的侵浸,無(wú)法自動(dòng)恢復(fù)。 請(qǐng)注意氛谜,HttpClient實(shí)現(xiàn)將HttpExceptions重新拋出為ClientProtocolException掏觉,它是java.io.IOException的子類。 這使HttpClient的用戶能夠從單個(gè)catch子句處理I / O錯(cuò)誤和協(xié)議違規(guī)混蔼。

1.5.1. HTTP 傳輸安全

重要的是要理解HTTP協(xié)議不適合所有類型的應(yīng)用程序履腋。 HTTP是一種簡(jiǎn)單的面向請(qǐng)求/響應(yīng)的協(xié)議,最初設(shè)計(jì)用于支持靜態(tài)或動(dòng)態(tài)生成的內(nèi)容檢索惭嚣。它從未打算支持交易操作遵湖。例如,如果HTTP服務(wù)器成功接收并處理請(qǐng)求晚吞,生成響應(yīng)并將狀態(tài)代碼發(fā)送回客戶端延旧,則HTTP服務(wù)器將考慮約定的一部分。如果客戶端由于讀取超時(shí)槽地,請(qǐng)求取消或系統(tǒng)崩潰而未能完全接收響應(yīng)迁沫,則服務(wù)器將不會(huì)嘗試回滾事務(wù)。如果客戶端決定重試相同的請(qǐng)求捌蚊,則服務(wù)器將不可避免地多次執(zhí)行同一事務(wù)集畅。在某些情況下,這可能會(huì)導(dǎo)致應(yīng)用程序數(shù)據(jù)損壞或應(yīng)用程序狀態(tài)不一致缅糟。

盡管HTTP從未被設(shè)計(jì)為支持事務(wù)處理挺智,但只要滿足某些條件,它仍可用作關(guān)鍵任務(wù)應(yīng)用程序的傳輸協(xié)議窗宦。為確保HTTP傳輸層安全赦颇,系統(tǒng)必須確保應(yīng)用層上HTTP方法的冪等性二鳄。

1.5.2. 冪等方法

HTTP/1.1規(guī)范將冪等方法定義為

[方法也可以具有“冪等”的屬性(除了錯(cuò)誤或過(guò)期問(wèn)題)N> 0個(gè)相同請(qǐng)求的副作用與單個(gè)請(qǐng)求相同]

換句話說(shuō),應(yīng)用程序應(yīng)該確保它準(zhǔn)備好處理同一方法的多次執(zhí)行的含義媒怯。 例如订讼,這可以通過(guò)提供唯一的事務(wù)id和通過(guò)避免執(zhí)行相同邏輯操作的其他手段來(lái)實(shí)現(xiàn)。

請(qǐng)注意扇苞,此問(wèn)題并非特定于HttpClient欺殿。 基于瀏覽器的應(yīng)用程序與HTTP方法非冪等性相關(guān)的問(wèn)題完全相同。

默認(rèn)情況下鳖敷,HttpClient假定只有非實(shí)體封閉方法(如GET和HEAD)是冪等的祈餐,而實(shí)體封閉方法(如POST和PUT)出于兼容性原因則不是。

1.5.3. 自動(dòng)異澈逄眨恢復(fù)

默認(rèn)情況下,HttpClient會(huì)嘗試從I / O異常中自動(dòng)恢復(fù)哺壶。 默認(rèn)的自動(dòng)恢復(fù)機(jī)制僅限于一些已知安全的異常情況屋吨。

  • HttpClient不會(huì)嘗試從任何邏輯或HTTP協(xié)議錯(cuò)誤(從HttpException類派生的錯(cuò)誤)中恢復(fù)。
  • HttpClient將自動(dòng)重試那些被認(rèn)為是冪等的方法山宾。
  • 當(dāng)HTTP請(qǐng)求仍在傳輸?shù)侥繕?biāo)服務(wù)器時(shí)(即請(qǐng)求尚未完全傳輸?shù)椒?wù)器)至扰,HttpClient將自動(dòng)重試那些因傳輸異常而失敗的方法。

1.5.4. 請(qǐng)求重試處理程序

為了啟用自定義異匙拭蹋恢復(fù)機(jī)制敢课,應(yīng)提供HttpRequestRetryHandler接口的實(shí)現(xiàn)。

HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {

    public boolean retryRequest(
            IOException exception,
            int executionCount,
            HttpContext context) {
        if (executionCount >= 5) {
            // Do not retry if over max retry count
            return false;
        }
        if (exception instanceof InterruptedIOException) {
            // Timeout
            return false;
        }
        if (exception instanceof UnknownHostException) {
            // Unknown host
            return false;
        }
        if (exception instanceof ConnectTimeoutException) {
            // Connection refused
            return false;
        }
        if (exception instanceof SSLException) {
            // SSL handshake exception
            return false;
        }
        HttpClientContext clientContext = HttpClientContext.adapt(context);
        HttpRequest request = clientContext.getRequest();
        boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
        if (idempotent) {
            // Retry if the request is considered idempotent
            return true;
        }
        return false;
    }

};
CloseableHttpClient httpclient = HttpClients.custom()
        .setRetryHandler(myRetryHandler)
        .build();

請(qǐng)注意绷杜,可以使用StandardHttpRequestRetryHandler而不是HttpClient 默認(rèn)使用的那個(gè)直秆,以便將RFC-2616定義為冪等的請(qǐng)求方法視為安全自動(dòng)重試:GET,HEAD鞭盟,PUT圾结,DELETE,OPTIONS和TRACE齿诉。

1.6. 中止請(qǐng)求

在某些情況下筝野,由于目標(biāo)服務(wù)器上的高負(fù)載或客戶端發(fā)出的并發(fā)請(qǐng)求太多,HTTP請(qǐng)求執(zhí)行無(wú)法在預(yù)期的時(shí)間范圍內(nèi)完成粤剧。 在這種情況下歇竟,可能需要提前終止請(qǐng)求并解除阻塞I / O操作中阻止的執(zhí)行線程。 通過(guò)調(diào)用HttpUriRequest#abort()方法抵恋,可以在執(zhí)行的任何階段中止由HttpClient執(zhí)行的HTTP請(qǐng)求焕议。 此方法是線程安全的,可以從任何線程調(diào)用馋记。當(dāng)HTTP請(qǐng)求被中止時(shí)号坡,它的執(zhí)行線程 - 即使當(dāng)前在I / O操作中被阻塞 - 也可以通過(guò)拋出InterruptedIOException來(lái)解除阻塞懊烤。

1.7. 重定向處理程序

HttpClient自動(dòng)處理所有類型的重定向,除了HTTP規(guī)范明確禁止的需要用戶干預(yù)的重定向宽堆。 請(qǐng)參閱其他(狀態(tài)代碼303)重定向POST腌紧,并將PUT請(qǐng)求轉(zhuǎn)換為HTTP規(guī)范要求的GET請(qǐng)求。 可以使用自定義重定向策略來(lái)放寬對(duì)HTTP規(guī)范強(qiáng)加的POST方法的自動(dòng)重定向的限制畜隶。

LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
CloseableHttpClient httpclient = HttpClients.custom()
        .setRedirectStrategy(redirectStrategy)
        .build();

HttpClient通常必須在執(zhí)行過(guò)程中重寫(xiě)請(qǐng)求消息壁肋。 默認(rèn)情況下,HTTP/1.0和HTTP/1.1通常使用相對(duì)請(qǐng)求URI籽慢。 同樣浸遗,原始請(qǐng)求可能會(huì)多次從一個(gè)位置重定向到另一個(gè)位置∠湟冢可以使用原始請(qǐng)求和上下文構(gòu)建最終解釋的絕對(duì)HTTP位置跛锌。實(shí)用程序URIUtils#resolution可構(gòu)建用于生成最終請(qǐng)求的解釋絕對(duì)URI。此方法包括重定向請(qǐng)求或原始請(qǐng)求中的最后一個(gè)片段標(biāo)識(shí)符届惋。

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpClientContext context = HttpClientContext.create();
HttpGet httpget = new HttpGet("http://localhost: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());
    // Expected to be an absolute URI
} finally {
    response.close();
}

上一章: 前言
下一章: 鏈接管理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末髓帽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子脑豹,更是在濱河造成了極大的恐慌郑藏,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,599評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瘩欺,死亡現(xiàn)場(chǎng)離奇詭異必盖,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)俱饿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)歌粥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人稍途,你說(shuō)我怎么就攤上這事阁吝。” “怎么了械拍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,084評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵突勇,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我坷虑,道長(zhǎng)甲馋,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,708評(píng)論 1 284
  • 正文 為了忘掉前任迄损,我火速辦了婚禮定躏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己痊远,他們只是感情好垮抗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著碧聪,像睡著了一般冒版。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逞姿,一...
    開(kāi)封第一講書(shū)人閱讀 50,021評(píng)論 1 291
  • 那天辞嗡,我揣著相機(jī)與錄音,去河邊找鬼滞造。 笑死续室,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的谒养。 我是一名探鬼主播挺狰,決...
    沈念sama閱讀 39,120評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼买窟!你這毒婦竟也來(lái)了她渴?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,866評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蔑祟,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后沉唠,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體疆虚,經(jīng)...
    沈念sama閱讀 44,308評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評(píng)論 2 327
  • 正文 我和宋清朗相戀三年满葛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了径簿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,768評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嘀韧,死狀恐怖篇亭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锄贷,我是刑警寧澤译蒂,帶...
    沈念sama閱讀 34,461評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站谊却,受9級(jí)特大地震影響柔昼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜炎辨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評(píng)論 3 317
  • 文/蒙蒙 一捕透、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦乙嘀、人聲如沸末购。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,850評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)盟榴。三九已至,卻和暖如春嘉冒,著一層夾襖步出監(jiān)牢的瞬間曹货,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,082評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工讳推, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留顶籽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,571評(píng)論 2 362
  • 正文 我出身青樓银觅,卻偏偏與公主長(zhǎng)得像礼饱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子究驴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 前言 超文本傳輸協(xié)議(HTTP)也許是當(dāng)今互聯(lián)網(wǎng)上使用的最重要的協(xié)議了镊绪。Web服務(wù),有網(wǎng)絡(luò)功能的設(shè)備和網(wǎng)絡(luò)計(jì)算的發(fā)...
    狂奔的蝸牛_wxc閱讀 5,505評(píng)論 0 12
  • 1.1 請(qǐng)求執(zhí)行 HttpClient 最重要的功能是執(zhí)行 HTTP 方法洒忧。執(zhí)行 HTTP 方法涉及一個(gè)或多個(gè) H...
    changhr2013閱讀 6,026評(píng)論 1 7
  • API定義規(guī)范 本規(guī)范設(shè)計(jì)基于如下使用場(chǎng)景: 請(qǐng)求頻率不是非常高:如果產(chǎn)品的使用周期內(nèi)請(qǐng)求頻率非常高蝴韭,建議使用雙通...
    有涯逐無(wú)涯閱讀 2,521評(píng)論 0 6
  • 第二章 連接管理 HttpClient有一個(gè)對(duì)連接初始化和終止,還有在活動(dòng)連接上I/O操作的完整控制熙侍。而連接操作的...
    狂奔的蝸牛_wxc閱讀 1,141評(píng)論 0 0
  • Apache HTTPClient Tutorial 深度學(xué)習(xí)(一) 超文本傳輸協(xié)議(HTTP)可能是當(dāng)今Inte...
    Noseparte_閱讀 739評(píng)論 0 0