HttpClient API 文檔:1. 基本原理

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

HttpClient 最重要的功能是執(zhí)行 HTTP 方法。執(zhí)行 HTTP 方法涉及一個(gè)或多個(gè) HTTP 請(qǐng)求/響應(yīng) 交換盟萨,通常由 HttpClient 內(nèi)部處理。

用戶期望提供一個(gè)請(qǐng)求對(duì)象來(lái)執(zhí)行兰伤,并且希望 HttpClient 將請(qǐng)求發(fā)送到目標(biāo)服務(wù)器返回相應(yīng)的響應(yīng)對(duì)象内颗,如果執(zhí)行失敗則拋出異常。

很自然敦腔,HttpClient API 的主要入口點(diǎn)是定義上述協(xié)議的 HttpClient 接口均澳。
以下是一個(gè)最基本的請(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)求行,包括方法名稱(chēng)符衔,請(qǐng)求 URI 和 HTTP 協(xié)議版本负懦。

HttpClient 支持了在 HTTP / 1.1 規(guī)范中定義的所有 HTTP 方法:GETHEAD柏腻,POST纸厉,PUTDELETE五嫂,TRACEOPTIONS颗品。

對(duì)于每個(gè)方法類(lèi)型,都有一個(gè)特定的類(lèi)來(lái)支持:HttpGet沃缘, HttpHead躯枢,HttpPostHttpPut槐臀,HttpDelete锄蹂, HttpTrace,和 HttpOptions水慨。

Request-URI 是統(tǒng)一資源標(biāo)識(shí)符得糜,用于標(biāo)識(shí)應(yīng)用請(qǐng)求的資源。HTTP 請(qǐng)求 URI 由協(xié)議方案晰洒,主機(jī)名朝抖,可選端口,資源路徑谍珊,可選查詢(xún)和可選片段組成治宣。

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

HttpClient 提供 URIBuilder 實(shí)用工具類(lèi)來(lái)簡(jiǎn)化請(qǐng)求 URI 的創(chuàng)建和修改。

stdout>
http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=
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());

1.1.2 HTTP 響應(yīng)

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

 stdout >
 HTTP/1.1
 200
 OK
 HTTP/1.1 200 OK
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());

1.1.3 處理消息頭

HTTP 消息可以包含描述消息的屬性的多個(gè)標(biāo)題贝润,例如內(nèi)容長(zhǎng)度绊茧,內(nèi)容類(lèi)型等。HttpClient 提供了檢索题暖,添加按傅,刪除和枚舉消息頭的方法。

 stdout >
 Set-Cookie: c1=a; path=/; domain=localhost
 Set-Cookie: c2=b; path="/", c3=c; domain="localhost"
 2
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);

獲取給定類(lèi)型的所有標(biāo)頭的最有效的方法是使用 HeaderIterator 接口胧卤。

 stdout >
 Set-Cookie: c1=a; path=/; domain=localhost
 Set-Cookie: c2=b; path="/", c3=c; domain="localhost"
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());
}

它還提供了便捷的方法來(lái)將 HTTP 消息解析為單獨(dú)的頭元素唯绍。

 stdout >
 c1 = a
 path=/
 domain=localhost
 c2 = b
 path=/
 c3 = c
 domain=localhost
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 heit = new BasicHeaderElementIterator(
        response.headerIterator("Set-Cookie"));

while (heit.hasNext()) {
    HeaderElement element = heit.nextElement();
    System.out.println(element.getName() + "=" + element.getValue());
    NameValuePair[] params = element.getParameters();
    for (int i = 0; i < params.length; i++) {
        System.out.println("" + params[i]);
    }
}

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)求被稱(chēng)為實(shí)體封裝請(qǐng)求。
HTTP 規(guī)范定義了兩個(gè)實(shí)體封裝請(qǐng)求的方法:POST 和 PUT叶撒。
HTTP 響應(yīng)通常期望包含有內(nèi)容實(shí)體绝骚。
當(dāng)然也有例外的情況,例如響應(yīng)為 204 No Content祠够, 304 Not Modified压汪,205 Reset Content 的時(shí)候不包含內(nèi)容實(shí)體。
HttpClient 根據(jù)其內(nèi)容來(lái)源區(qū)分三種實(shí)體:

  1. 流式傳輸(streamed):內(nèi)容是從流中接收到的古瓤,或者即時(shí)生成止剖。特別地,該類(lèi)別包括從 HTTP 響應(yīng)接收到的實(shí)體落君。流式實(shí)體通常不可重復(fù)穿香。
  2. 自包含(self-contained):內(nèi)容在內(nèi)存中或通過(guò)獨(dú)立于連接或其他實(shí)體的方式獲取。自包含的實(shí)體通常是可重復(fù)的绎速。這種類(lèi)型的實(shí)體將主要用于封閉 HTTP 請(qǐng)求的實(shí)體皮获。
  3. 包裝(wrapping):內(nèi)容是從另一個(gè)實(shí)體獲得的。

當(dāng)從 HTTP 響應(yīng)流出內(nèi)容時(shí)纹冤,這個(gè)區(qū)別對(duì)于連接管理很重要洒宝。對(duì)于由應(yīng)用程序創(chuàng)建并且僅使用 HttpClient 發(fā)送的請(qǐng)求實(shí)體,流和自包含的區(qū)別并不是重要萌京。因而可以通過(guò)是否可重復(fù)來(lái)區(qū)分它們待德,將不可重復(fù)的實(shí)體視為流式的,將可重復(fù)的實(shí)體視為自包含的枫夺。

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

一個(gè)實(shí)體可以重復(fù)将宪,這意味著它的內(nèi)容可以被讀取多次。這種情況僅僅可能出現(xiàn)在自包含的實(shí)體中(例如 ByteArrayEntity 或 StringEntity)橡庞。

1.1.4.2 使用 HTTP 實(shí)體

由于實(shí)體可以表示二進(jìn)制字符內(nèi)容较坛,因此它支持字符編碼(為了支持字符內(nèi)容)。

當(dāng)執(zhí)行帶有封閉內(nèi)容的請(qǐng)求時(shí)扒最,或者當(dāng)請(qǐng)求成功后使用響應(yīng)主體將結(jié)果發(fā)送回客戶端時(shí)丑勤,實(shí)體被創(chuàng)建。

要從實(shí)體讀取內(nèi)容吧趣,可以通過(guò) HttpEntity#getContent() 方法來(lái)檢索輸入流法竞,該方法返回一個(gè) java.io.InputStream耙厚,或者可以向 HttpEntity#writeTo(OutputStream) 方法提供輸出流,一旦所有內(nèi)容都被寫(xiě)入給定流岔霸,該方法將返回薛躬。

當(dāng)實(shí)體和傳入消息已經(jīng)被接收,方法 HttpEntity#getContentType()
HttpEntity#getContentLength() 可用于讀取公共 metadata呆细,例如 Content-Type 與 Content-Length 報(bào)頭(如果可用的話)型宝。
Content-Type 報(bào)頭可以包含 text/plaintext/html 等文本 MIME 類(lèi)型的字符編碼,方法 HttpEntity#getContentEncoding() 可以用于讀取此信息絮爷。
如果報(bào)頭不可用趴酣,則返回 Content-Length 為 -1,Content-Type 為 NULL坑夯。如果 Content-Type 報(bào)頭可用岖寞,將返回一個(gè) Header 對(duì)象。

當(dāng)為外發(fā)消息創(chuàng)建實(shí)體時(shí)柜蜈,該 metadata 必須由實(shí)體的創(chuàng)建者來(lái)提供慎璧。

 stdout >
 Content-Type: text/plain; charset=utf-8
 17
 important message
 17
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);

1.1.5 確保釋放低級(jí)別的資源

為了確保正確釋放系統(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 {
            //做一些有用的事情
        } finally {
            instream.close();
        }
    }
} finally {
    response.close();
}

關(guān)閉內(nèi)容流和關(guān)閉響應(yīng)之間的區(qū)別是胸私,前者將嘗試通過(guò)消費(fèi)實(shí)體內(nèi)容來(lái)保持底層的連接,而后者會(huì)立即關(guān)閉并丟棄當(dāng)前連接鳖谈。

請(qǐng)注意岁疼,一旦方法 HttpEntity#writeTo(OutputStream) 的實(shí)體完全寫(xiě)出,也需要確保合適的釋放系統(tǒng)資源缆娃。如果這個(gè)方法通過(guò)調(diào)用 HttpEntity#getContent() 獲取一個(gè) java.io.InputStream 實(shí)例捷绒,那么也應(yīng)該在 finally 子句中關(guān)閉流。

當(dāng)處理流實(shí)體時(shí)贯要,可以使用 EntityUtils#consume(HttpEntity) 方法來(lái)確保實(shí)體內(nèi)容已被完全消耗暖侨,并且底層流已經(jīng)被關(guān)閉。

然而崇渗,可能會(huì)有一種情況字逗,當(dāng)僅需要檢索整個(gè)響應(yīng)內(nèi)容的一小部分時(shí),消耗剩余內(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();
        //不需要休息
    }
} finally {
    response.close();
}

連接不會(huì)被重用,但由其持有的所有級(jí)別的資源將會(huì)被正確地釋放跟狱。

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

消費(fèi)實(shí)體內(nèi)容推薦使用 HttpEntity#getContent()HttpEntity#writeTo(OutputStream) 方法俭厚。

HttpClient 還附帶了 EntityUtils 類(lèi),它暴露了幾種靜態(tài)方法驶臊,來(lái)方便我們從實(shí)體中讀取內(nèi)容或信息挪挤〉鸪螅可以通過(guò)使用這個(gè)類(lèi)的方法取回整個(gè)內(nèi)容體的字符串或者字節(jié)數(shù)組,而不是通過(guò)去直接讀取 java.io.InputStream 來(lái)獲取內(nèi)容扛门。但是鸠信,除非響應(yīng)實(shí)體來(lái)自受信任的 HTTP 服務(wù)器,并且已知其長(zhǎng)度有限尖飞,否則強(qiáng)烈建議不要使用 EntityUtils 的方法症副。

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 {
            //流內(nèi)容
        }
    }
} finally {
    response.close();
}

在某些情況下店雅,可能需要多次讀取實(shí)體內(nèi)容政基。在這種情況下,實(shí)體內(nèi)容必須以某種方式緩存闹啦,無(wú)論是在內(nèi)存還是在磁盤(pán)上沮明。

最簡(jiǎn)單的方式是通過(guò)使用 BufferedHttpEntity 類(lèi)包裝原始實(shí)體。這會(huì)使原始實(shí)體的內(nèi)容被讀入內(nèi)存緩沖區(qū)窍奋。在所有其他方式中荐健,實(shí)體包裝器將具有原始內(nèi)容。

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

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

HttpClient 提供了幾個(gè)類(lèi)琳袄,可以通過(guò) HTTP 連接高效地流出內(nèi)容江场。這些類(lèi)的實(shí)例可以將那些需要包裝其關(guān)聯(lián)的實(shí)體內(nèi)容的 HTTP 請(qǐng)求(例如 POST 和 PUT)包裝實(shí)體去流出 HTTP 請(qǐng)求端。HttpClient 為最常見(jiàn)的數(shù)據(jù)的容器(如字符串窖逗,字節(jié)數(shù)組址否,輸入流和文件)提供了幾個(gè)類(lèi):StringEntityByteArrayEntity碎紊,InputStreamEntityFileEntity佑附。

請(qǐng)注意 InputStreamEntity 不可重復(fù),因?yàn)樗荒軓幕A(chǔ)數(shù)據(jù)流中讀取一次仗考。通常建議實(shí)現(xiàn)一個(gè)自包含 HttpEntity 的自定義類(lèi)音同,而不是使用一般的 InputStreamEntity。 FileEntity 可以是一個(gè)很好的開(kāi)始秃嗜。

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

1.1.7.1 HTML 表單

許多應(yīng)用程序需要模擬提交 HTML 表單的過(guò)程权均,例如,登錄到 Web 應(yīng)用程序或提交輸入數(shù)據(jù)锅锨。HttpClient 提供實(shí)體類(lèi) UrlEncodedFormEntity 來(lái)簡(jiǎn)化這個(gè)過(guò)程螺句。
UrlEncodedFormEntity 實(shí)例將使用所謂的 URL 編碼參數(shù)進(jìn)行編碼并生成以下內(nèi)容:

param1=value1&param2=value2
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);

1.1.7.2 內(nèi)容分塊

一般建議讓 HttpClient 根據(jù)正在傳輸?shù)?HTTP 消息的屬性選擇最合適的傳輸編碼。然而橡类,可以通知 HttpClient蛇尚,通過(guò)設(shè)置 HttpEntity#setChunked() 為 true,優(yōu)先選擇塊編碼顾画。

請(qǐng)注意取劫,HttpClient 只會(huì)使用此標(biāo)志作為提示匆笤。當(dāng)使用不支持塊編碼的 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) 方法。該方法完全消除用戶對(duì)連接管理的擔(dān)心惦银。

使用 ResponseHandler 時(shí)咆课,無(wú)論請(qǐng)求執(zhí)行成功還是出現(xiàn)異常,HttpClient 都將自動(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í)行最基本的協(xié)議书蚪。它對(duì)請(qǐng)求執(zhí)行過(guò)程沒(méi)有施加任何限制或特定細(xì)節(jié),并將連接管理迅栅,狀態(tài)管理殊校,認(rèn)證和重定向處理的具體細(xì)節(jié)留給一個(gè)具體實(shí)現(xiàn)。這使得可以更容易使用附加功能(如響應(yīng)內(nèi)容緩存)來(lái)裝飾接口读存。

一般來(lái)說(shuō)为流,HttpClient 實(shí)現(xiàn)作為一個(gè)特殊目的處理程序或策略接口實(shí)現(xiàn)的立面,負(fù)責(zé)處理 HTTP 協(xié)議的特定方面让簿,如重定向或身份驗(yàn)證處理或決定連接持久性并保持活動(dòng)持續(xù)時(shí)間敬察。這使得用戶能夠選擇性地將這些方面的默認(rèn)實(shí)現(xiàn)替換為自己的實(shí)現(xiàn)或者為特定應(yīng)用程序設(shè)計(jì)的實(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-alive 的值沒(méi)有被服務(wù)器顯式設(shè)置尔当,就設(shè)置為 5 秒
            keepAlive = 5000;
        }
        return keepAlive;
    }
};

CloseableHttpClient httpclient = HttpClients.custom()
        .setKeepAliveStrategy(keepAliveStrat)
        .build();

1.2.1 HttpClient 線程安全

HttpClient 的實(shí)現(xiàn)是線程安全的莲祸。因此建議將此類(lèi)的同一個(gè)實(shí)例重用于多個(gè)請(qǐng)求執(zhí)行。

1.2.2 HttpClient 資源釋放

當(dāng)一個(gè) CloseableHttpClient 實(shí)例不再需要并且即將超出作用域時(shí)居凶,與它關(guān)聯(lián)的連接管理器必須通過(guò)調(diào)用 CloseableHttpClient#close() 方法來(lái)關(guān)閉虫给。

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

1.3 HTTP 執(zhí)行上下文

最初 HTTP 被設(shè)計(jì)為無(wú)狀態(tài)的,請(qǐng)求-響應(yīng)的協(xié)議侠碧。然而抹估,現(xiàn)實(shí)世界的應(yīng)用程序通常需要通過(guò)幾個(gè)邏輯相關(guān)的請(qǐng)求-響應(yīng)交換來(lái)保持狀態(tài)信息。

為了使應(yīng)用程序能夠保持處理狀態(tài)弄兜,HttpClient 允許在特定執(zhí)行上下文(稱(chēng)為 HTTP context)中執(zhí)行 HTTP 請(qǐng)求药蜻。

如果在連續(xù)請(qǐng)求之間重復(fù)使用相同的上下文,則多個(gè)邏輯相關(guān)請(qǐng)求可以參與邏輯會(huì)話替饿。HTTP 上下文功能類(lèi)似于一個(gè) java.util.Map<String, Object>县忌。它只是一個(gè)任意命名值的集合乔外。應(yīng)用程序可以在請(qǐng)求執(zhí)行之前填充上下文屬性底循,或者在執(zhí)行完成后檢查上下文嘉裤。

HttpContext 可以包含任意對(duì)象,因此可能不安全地在多個(gè)線程之間共享。建議每個(gè)執(zhí)行線程維護(hù)自己的上下文惋砂。

在 HTTP 請(qǐng)求執(zhí)行過(guò)程中妒挎,HttpClient 將以下屬性添加到執(zhí)行上下文中:

  • HttpConnection 表示與目標(biāo)服務(wù)器的實(shí)際連接的實(shí)例。
  • HttpHost 表示連接目標(biāo)的實(shí)例西饵。
  • HttpRoute 表示完整的連接路由的實(shí)例
  • HttpRequest 表示實(shí)際 HTTP 請(qǐng)求的實(shí)例酝掩。執(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í)際的 HTTP 響應(yīng)镶苞。
  • java.lang.Boolean 表示實(shí)際請(qǐng)求是否被完全發(fā)送到連接目標(biāo)的標(biāo)志的對(duì)象。
  • RequestConfig 表示實(shí)際請(qǐng)求配置的對(duì)象宙拉。
  • java.util.List<URI> 表示在請(qǐng)求執(zhí)行過(guò)程中接收到的所有重定向位置的集合的對(duì)象宾尚。

可以使用 HttpClientContext 適配器類(lèi)來(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();

表示邏輯相關(guān)會(huì)話的多個(gè)請(qǐng)求序列應(yīng)該使用相同的 HttpContext 實(shí)例執(zhí)行谢澈,以確保在請(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é)議攔截器是一個(gè)實(shí)現(xiàn) HTTP 協(xié)議特定切面的程序怠肋。通常協(xié)議攔截器期望作用于輸入消息的一個(gè)特定頭部或一組相關(guān)頭部敬鬓,或者使用一個(gè)特定頭部或一組相關(guān)頭部填充傳出的消息。

協(xié)議攔截器還可以操縱包含消息的內(nèi)容實(shí)體 - 顯式內(nèi)容壓縮/解壓縮就是一個(gè)很好的例子笙各。通常這是通過(guò)使用“裝飾器”模式來(lái)實(shí)現(xiàn)的钉答,其中包裝器實(shí)體類(lèi)用于裝飾原始實(shí)體。幾個(gè)協(xié)議攔截器可以組合形成一個(gè)邏輯單元杈抢。

協(xié)議攔截器可以通過(guò) HTTP 執(zhí)行上下文共享信息(如處理狀態(tài))進(jìn)行協(xié)作数尿。協(xié)議攔截器可以使用 HTTP 上下文來(lái)存儲(chǔ)一個(gè)請(qǐng)求或多個(gè)連續(xù)請(qǐng)求的處理狀態(tài)。通常執(zhí)行攔截器的順序并不重要惶楼,只要它們不依賴(lài)執(zhí)行上下文的特定狀態(tài)即可右蹦。如果協(xié)議攔截器具有相互依賴(lài)關(guān)系,那么必須以特定順序執(zhí)行歼捐,應(yīng)將它們按照與其預(yù)期執(zhí)行順序相同的順序添加到協(xié)議處理器中何陆。

協(xié)議攔截器必須實(shí)現(xiàn)為線程安全。與 servlet 類(lèi)似豹储,協(xié)議攔截器不應(yīng)該使用實(shí)例變量贷盲,除非對(duì)這些變量的訪問(wèn)是同步(synchronized)的。

下面是一個(gè)例子剥扣,說(shuō)明如何使用本地上下文來(lái)持續(xù)連續(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é)議處理器可以拋出兩種類(lèi)型的異常:java.io.IOException 表示 I/O 故障(例如套接字超時(shí)或套接字復(fù)位)巩剖,而 HttpException 表示 HTTP 失敗慨灭,例如違反 HTTP 協(xié)議。通常 I/O 錯(cuò)誤被認(rèn)為是非致命和可恢復(fù)的球及,而 HTTP 協(xié)議錯(cuò)誤被認(rèn)為是致命的氧骤,不能自動(dòng)恢復(fù)。

請(qǐng)注意吃引,HttpClient 實(shí)現(xiàn)重新拋出 HttpException 作為 ClientProtocolException筹陵,它是一個(gè) java.io.IOException 的子類(lèi) 。這使得 HttpClient 的用戶能夠在單個(gè) catch 子句同時(shí)處理 I/O 錯(cuò)誤和協(xié)議錯(cuò)誤镊尺。

1.5.1 HTTP 傳輸安全

重要的是要了解 HTTP 協(xié)議并不適合所有類(lèi)型的應(yīng)用程序朦佩。HTTP 是一種簡(jiǎn)單的面向 請(qǐng)求/響應(yīng) 的協(xié)議,最初被設(shè)計(jì)為支持靜態(tài)或動(dòng)態(tài)生成的內(nèi)容檢索庐氮。它從來(lái)沒(méi)有意圖支持事務(wù)性操作语稠。

例如:

  • 如果 HTTP 服務(wù)器成功接收和處理請(qǐng)求,生成響應(yīng)并將狀態(tài)代碼發(fā)送回客戶端弄砍,則 HTTP 服務(wù)器將考慮其部分合同仙畦。
  • 如果客戶端由于讀取超時(shí),請(qǐng)求取消或系統(tǒng)崩潰而無(wú)法全部收到響應(yīng)音婶,服務(wù)器將不會(huì)嘗試回滾事務(wù)慨畸。
  • 如果客戶端決定重試相同的請(qǐng)求,服務(wù)器將不可避免地最終不止一次地執(zhí)行相同的事務(wù)衣式。

在某些情況下寸士,這可能導(dǎo)致應(yīng)用程序數(shù)據(jù)損壞或應(yīng)用程序狀態(tài)不一致。

即使 HTTP 從未被設(shè)計(jì)為支持事務(wù)處理碴卧,但是如果滿足某些條件弱卡,則仍然可以將其用作任務(wù)關(guān)鍵應(yīng)用程序的傳輸協(xié)議。

為了確保 HTTP 傳輸層的安全住册,系統(tǒng)必須確保應(yīng)用層上的 HTTP 方法的冪等性婶博。

1.5.2 冪等方法

HTTP/1.1 規(guī)范定義了一種冪等方法(方法也可以具有“冪等”的屬性(除了錯(cuò)誤或到期問(wèn)題),N > 0 相同請(qǐng)求的副作用與單個(gè)請(qǐng)求相同)界弧。換句話說(shuō)凡蜻,應(yīng)用程序應(yīng)該確保它準(zhǔn)備好處理多次執(zhí)行相同方法的含義。

這可以通過(guò)例如提供唯一的事務(wù) ID 和避免執(zhí)行相同邏輯操作的其他手段來(lái)實(shí)現(xiàn)垢箕。

請(qǐng)注意划栓,這個(gè)問(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 嘗試自動(dòng)從 I/O 異臣チ冢恢復(fù)迫靖。默認(rèn)的自動(dòng)恢復(fù)機(jī)制僅限于一些已知是安全的異常。

  • HttpClient 將不會(huì)嘗試從任何邏輯或 HTTP 協(xié)議錯(cuò)誤(派生自 HttpException 類(lèi))中恢復(fù)兴使。

  • HttpClient 會(huì)自動(dòng)重試那些假定為冪等的方法系宜。

  • 當(dāng) HTTP 請(qǐng)求仍被傳送到目標(biāo)服務(wù)器(即請(qǐng)求尚未完全傳輸?shù)椒?wù)器)時(shí),HttpClient 將自動(dòng)重試那些失敗的傳輸異常的方法发魄。

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

為了啟用自定義異稠锬粒恢復(fù)機(jī)制,應(yīng)該提供 HttpRequestRetryHandler 接口的實(shí)現(xiàn)励幼。

請(qǐng)注意汰寓,可以使用 StandardHttpRequestRetryHandler 替代默認(rèn)使用的一個(gè),以匹配由 RFC-2616 定義為冪等安全要求的那些方法來(lái)自動(dòng)重試:GET赏淌,HEAD踩寇,PUT啄清,DELETE六水,OPTIONS 和 TRACE。

HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {

public boolean retryRequest(
        IOException exception,
        int executionCount,
        HttpContext context) {
    if (executionCount >= 5) {
        //如果超過(guò)最大重試計(jì)數(shù)辣卒,請(qǐng)不要重試
        return false;
    }
    if (exception instanceof InterruptedIOException) {
        // 時(shí)間到
        return false;
    }
    if (exception instanceof UnknownHostException) {
        // 未知主機(jī)
        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)求被認(rèn)為是冪等掷贾,則重試
            return true;
        }
        return false;
    }

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

1.6 中止請(qǐng)求

在某些情況下,由于目標(biāo)服務(wù)器的負(fù)載過(guò)高或在客戶端發(fā)出的太多并發(fā)請(qǐng)求荣茫,HTTP 請(qǐng)求執(zhí)行在預(yù)期時(shí)間內(nèi)無(wú)法完成想帅。在這種情況下,可能需要提前終止請(qǐng)求啡莉,并在 I/O 操作中解除阻塞執(zhí)行線程港准。

HttpClient 執(zhí)行的 HTTP 請(qǐng)求可以通過(guò)調(diào)用 HttpUriRequest#abort() 方法在執(zhí)行的任何階段中止 。該方法是線程安全的咧欣,可以從任何線程調(diào)用浅缸。
當(dāng)一個(gè) HTTP 請(qǐng)求中止時(shí),它的執(zhí)行線程 - 即使當(dāng)前在 I/O 操作中被阻塞 - 也可以通過(guò)拋出一個(gè) InterruptedIOException 來(lái)疏通阻塞線程魄咕。

1.7 重定向處理

HttpClient 會(huì)自動(dòng)處理所有類(lèi)型的重定向衩椒,除了需要用戶干預(yù)的 HTTP 規(guī)范明確禁止的重定向之外。

See Other(狀態(tài)碼 303)重定向 POST 和 PUT 請(qǐng)求轉(zhuǎn)換為 GET 請(qǐng)求作為 HTTP 規(guī)范的要求。

可以使用自定義重定向策略來(lái)放對(duì) HTTP 規(guī)范強(qiáng)加的 POST 方法的自動(dòng)重定向的限制毛萌。

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

HttpClient 經(jīng)常在其執(zhí)行過(guò)程中重寫(xiě)請(qǐng)求消息苟弛。默認(rèn)情況下,HTTP/1.0 和 HTTP/1.1 通常使用相對(duì)請(qǐng)求URI阁将。

同樣膏秫,原始請(qǐng)求可能會(huì)從一個(gè)地址重定向到另一個(gè)地址多次。最終絕對(duì) HTTP 地址定位可以使用原始請(qǐng)求和上下文構(gòu)建出來(lái)做盅。

一個(gè)實(shí)用的方法 URIUtils#resolve 可用于構(gòu)建用于生成最終 HTTP 請(qǐng)求絕對(duì)的 URI荔睹。該方法包括來(lái)自重定向請(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());
    //預(yù)期是絕對(duì)的URI
} finally {
    response.close();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末言蛇,一起剝皮案震驚了整個(gè)濱河市僻他,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌腊尚,老刑警劉巖吨拗,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異婿斥,居然都是意外死亡劝篷,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)民宿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)娇妓,“玉大人,你說(shuō)我怎么就攤上這事活鹰」。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵志群,是天一觀的道長(zhǎng)着绷。 經(jīng)常有香客問(wèn)我,道長(zhǎng)锌云,這世上最難降的妖魔是什么荠医? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮桑涎,結(jié)果婚禮上彬向,老公的妹妹穿的比我還像新娘。我一直安慰自己攻冷,他們只是感情好娃胆,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著讲衫,像睡著了一般缕棵。 火紅的嫁衣襯著肌膚如雪孵班。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,394評(píng)論 1 310
  • 那天招驴,我揣著相機(jī)與錄音篙程,去河邊找鬼。 笑死别厘,一個(gè)胖子當(dāng)著我的面吹牛虱饿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播触趴,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼氮发,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了冗懦?” 一聲冷哼從身側(cè)響起爽冕,我...
    開(kāi)封第一講書(shū)人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎披蕉,沒(méi)想到半個(gè)月后颈畸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡没讲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年眯娱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爬凑。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡徙缴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嘁信,到底是詐尸還是另有隱情于样,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布吱抚,位于F島的核電站百宇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏秘豹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一昌粤、第九天 我趴在偏房一處隱蔽的房頂上張望既绕。 院中可真熱鬧,春花似錦涮坐、人聲如沸凄贩。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)疲扎。三九已至昵时,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間椒丧,已是汗流浹背壹甥。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留壶熏,地道東北人句柠。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像棒假,于是被迫代替她去往敵國(guó)和親溯职。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

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

  • API定義規(guī)范 本規(guī)范設(shè)計(jì)基于如下使用場(chǎng)景: 請(qǐng)求頻率不是非常高:如果產(chǎn)品的使用周期內(nèi)請(qǐng)求頻率非常高帽哑,建議使用雙通...
    有涯逐無(wú)涯閱讀 2,557評(píng)論 0 6
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理谜酒,服務(wù)發(fā)現(xiàn),斷路器妻枕,智...
    卡卡羅2017閱讀 134,704評(píng)論 18 139
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架甚带,建立于...
    Hsinwong閱讀 22,435評(píng)論 1 92
  • 仟佑21閱讀 209評(píng)論 0 0
  • 一、7條人生守則:采用對(duì)標(biāo)的方法 1佳头、早睡早起:晚十早五鹰贵,午休5-15分鐘。 2康嘉、真誠(chéng)反思:日反思碉输,周檢視,月總結(jié)...
    員子圓夢(mèng)閱讀 169評(píng)論 0 0