第一章 基礎(chǔ)(2)

1.2 HttpClient 接口

HttpClient接口代表著HTTP請求執(zhí)行的最基本的契約翩迈。它對請求的執(zhí)行沒有什么限制與特定的細(xì)節(jié)澈灼,并且對于連接管理湿蛔、狀態(tài)管理创译、驗(yàn)證和重定向的處理抵知,是讓各自的實(shí)現(xiàn)來決定。這讓它更容易對接口進(jìn)行裝飾软族,添加額外的功能刷喜,如對響應(yīng)內(nèi)容進(jìn)行緩存。

通常立砸,HttpClient實(shí)現(xiàn)是當(dāng)作一個門面掖疮,可以包含一系列特殊目的的處理器或者策略接口,如重定向或驗(yàn)證處理颗祝,或決定連接持久性和弊巧粒活時長的策略接口。這使用戶能夠選擇性地替換對應(yīng)的默認(rèn)策略螺戳。

    @Test
    public void chapter1_2_0() {

        ConnectionKeepAliveStrategy keepAliveStrategy = 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(keepAliveStrategy)
                .build();

    }

1.2.1 HttpClient 線程安全

HttpClient實(shí)現(xiàn)是線程安全的搁宾。對于多個請求的執(zhí)行,推薦使用同一個實(shí)例倔幼。

1.2.2 HttpClient 資源釋放

當(dāng)一個CloseableHttpClient實(shí)例不再需要并且將超出連接管理器的作用范圍時盖腿,它必須調(diào)用方法CloseableHttpClient#close()來關(guān)閉。

    public void chapter1_2_2() throws IOException {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        try {
            // ...
        } finally {
            httpclient.close();
        }
    }

1.3 HTTP 執(zhí)行上下文

最初HTTP被設(shè)計成一個無狀態(tài)的损同,面向請求-響應(yīng)的協(xié)議翩腐。然而,真實(shí)世界的應(yīng)用程序經(jīng)常需要在幾個邏輯關(guān)聯(lián)的請求響應(yīng)交換中保持狀態(tài)信息膏燃。為了能夠讓應(yīng)用程序能夠維持狀態(tài)栗菜,HttpClient允許Http請求在一個特定的執(zhí)行上下文中執(zhí)行,該上下文被稱為HTTP上下文(HTTP context)蹄梢。當(dāng)相同的上下文在連續(xù)的請求中重用時,幾個邏輯關(guān)聯(lián)的請求就會參與一個邏輯的會話。HTTP上下文類似于java.util.Map<String, Object>禁炒。它就是一個簡單鍵值對的集合而咆。應(yīng)用程序能夠在執(zhí)行前獲取上下文,與能夠在執(zhí)行后檢查上下文幕袱。

HttpContext能夠包含任意對象暴备,所以在多個線程中共享可能是不安全的。推薦每個線程維護(hù)自己的上下文们豌。

在HTTP請求的執(zhí)行過程中涯捻,HttpClient會添加以下屬性到掛靠上下文中:

  • HttpConnection 實(shí)例,表示與目標(biāo)服務(wù)器的實(shí)際連接望迎。
  • HttpHost 實(shí)例障癌,表示連接的目標(biāo)。
  • HttpRoute實(shí)例辩尊,表示完整的連接路由涛浙。
  • HttpRequest實(shí)例,表示實(shí)際的HTTP請求摄欲。由final修飾的HttpRequest對象總是能正確表示報文的狀態(tài)轿亮,因?yàn)樗潜凰屯繕?biāo)服務(wù)器。對于缺省的HTTP/1.0和HTTP/1.1胸墙,它使用相對路徑的請求URI我注。然而,如果請求是通過一個非隧道模式的代理來發(fā)送的話迟隅,URI是絕對路徑的但骨。
  • HttpResponse實(shí)例,表示實(shí)際的HTTP響應(yīng)玻淑。
  • java.lang.Boolean對象嗽冒,表示的標(biāo)志,指明實(shí)際的請求是否被完全傳送到連接目標(biāo)补履。
  • ReqeustConfig對象添坊,表示實(shí)際請求的配置。
  • java.util.List<URI>對象箫锤,表示在執(zhí)行過程中收到的所有的重定向位置的集合贬蛙。

我們可以使用HttpClientContext適配器來簡化與上下文狀態(tài)的交互過程。

    public void chapter1_3() {
        HttpContext context = null; // init ...
        HttpClientContext clientContext = HttpClientContext.adapt(context);
        HttpHost target = clientContext.getTargetHost();
        HttpRequest request = clientContext.getRequest();
        HttpResponse response = clientContext.getResponse();
        RequestConfig requestConfig = clientContext.getRequestConfig();
    }

一組邏輯關(guān)聯(lián)的請求應(yīng)當(dāng)在相同的HttpContext實(shí)例中執(zhí)行谚攒,以保證在多個請求中自動傳遞會話上下文和狀態(tài)信息阳准。
在下面的例子中,由初始請求設(shè)置的請求配置會保存在執(zhí)行上下文中馏臭,并通過共享相同的上下文來傳遞給接下來的請求野蝇。

    public void chapter1_3_a() throws IOException {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(1000)
                .setConnectTimeout(1000)
                .build();

        HttpContext context = new BasicHttpContext();

        HttpGet httpGet1 = new HttpGet("http://www.baidu.com");
        httpGet1.setConfig(requestConfig);
        CloseableHttpResponse response1 = httpclient.execute(httpGet1, context);
        try {
            HttpEntity entity1 = response1.getEntity();
        } finally {
            response1.close();
        }
        
        HttpGet httpGet2 = new HttpGet("http://www.yy.com");
        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é)議攔截器期望作用于輸入報文的一個特定的頭部或一組相關(guān)的頭部绕沈,或給輸出報文填入一個特定的頭部或一組相關(guān)的頭部锐想。協(xié)議攔截器也能夠操作報文中的內(nèi)容實(shí)體,透明的內(nèi)容壓縮/解壓縮就是一個很好的例子乍狐。通常使用裝飾者模式來實(shí)現(xiàn)赠摇,用包裝的實(shí)體來裝飾原始的實(shí)體。多個協(xié)議攔截器能夠組合起來形成一個邏輯單元浅蚪。

協(xié)議攔截器在HTTP執(zhí)行上下文中通過共享信息來協(xié)作藕帜,如處理狀態(tài)。協(xié)議攔截器能夠使用HTTP上下文來保存一個請求或幾個連續(xù)請求的處理狀態(tài)惜傲。

通常攔截器的順序應(yīng)該是無關(guān)緊要的洽故,只要它們不依賴執(zhí)行上下文中的特定的狀態(tài)。如果協(xié)議接口器存在依賴必須按一定的順序執(zhí)行操漠,它們應(yīng)該按照該順序添加到協(xié)議處理器中收津。

協(xié)議接口器必須實(shí)現(xiàn)為線程安全的。與servlet類似浊伙,協(xié)議攔截器不應(yīng)該使用實(shí)例變量撞秋,除非訪問那些實(shí)例變量是同步的。

以下的例子展示的本地上下文在連續(xù)的請求中是如何被使用來保存處理狀態(tài)的:

    public void chapter1_4() throws IOException {

        CloseableHttpClient httpclient = HttpClients.custom()
                .addInterceptorLast(new HttpRequestInterceptor() {
                    public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
                        AtomicInteger count = (AtomicInteger) httpContext.getAttribute("count");
                        httpRequest.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錯誤如socket超時或者socket重置)和HttpException(表示HTTP錯誤如違反HTTP協(xié)議)嚣鄙。 通常I/O錯誤可以認(rèn)為是非致命的和可恢復(fù)的吻贿,而HTTP協(xié)議錯誤是致命的且不能自動恢復(fù)的。請注意HttpClient的實(shí)現(xiàn)把HttpException重新拋出為ClientProtocolException哑子,它是java.io.IOException的子類舅列。這使得用戶可以在一個catch子句中處理I/O錯誤和協(xié)議錯誤。

1.5.1 HTTP 傳輸安全

理解HTTP協(xié)議不是對所有應(yīng)用程序都適用是很重要的卧蜓。HTTP是一個簡單的面向請求/響應(yīng)的協(xié)議帐要,它最初被設(shè)計成支持靜態(tài)或動態(tài)產(chǎn)生的內(nèi)容的獲取。它從未打算支持事務(wù)操作弥奸。例如榨惠,HTTP服務(wù)器成功地接收并處理請求,產(chǎn)生一個響應(yīng)并發(fā)送狀態(tài)碼給回客戶端盛霎,那么服務(wù)器就會認(rèn)為它成功了赠橙。如果客戶端因?yàn)樽x超時、取消請求或系統(tǒng)崩潰愤炸,服務(wù)器不會試圖回滾這個事務(wù)期揪。如果客戶端決定進(jìn)行重試相同的請求,那么服務(wù)器不可避免地會重復(fù)執(zhí)行一次相同的請求规个。在某種情況下凤薛,這會導(dǎo)致數(shù)據(jù)損壞或程序狀態(tài)不一致姓建。

盡管HTTP從未被設(shè)計成支持事務(wù)處理,它仍然能夠被用作關(guān)鍵任務(wù)的傳輸協(xié)議枉侧,如果特定的條件滿足的話引瀑。為了保證HTTP傳輸層安全,系統(tǒng)必須保證在應(yīng)用層上的HTTP方法的冪等性榨馁。

1.5.2 冪等方法

HTTP/1.1規(guī)格中定義一個冪等方法為:

Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects os N > 0 identical requests is the same as for a single request.

也就是說,應(yīng)用程序應(yīng)當(dāng)確保它能夠多次執(zhí)行相同方法而不產(chǎn)生副作用帜矾。提供一個唯一的事務(wù)ID或用其他方法避免執(zhí)行相同邏輯操作就能達(dá)到這一目的翼虫。

請注意到這不僅限于HttpClient÷庞基于瀏覽器的應(yīng)用程序一樣有相同的問題珍剑。

HttpClient默認(rèn)認(rèn)為只有非包含實(shí)體的方法,如GETHEAD是冪等的死陆,包含實(shí)體的方法如POSTPUT招拙,基于兼容性原因,是非冪等的措译。

1.5.3 自動的異潮鸱铮恢復(fù)

HttpClient默認(rèn)會自動地從I/O異常中恢復(fù)。缺省的自動恢復(fù)機(jī)制僅適用于一些已知是安全的異常领虹。

  • HttpClient不會試圖從任務(wù)邏輯錯誤或HTTP協(xié)議錯誤(那些繼續(xù)HttpException的類)中自動恢復(fù)规哪。
  • HttpClient會自動重試那些認(rèn)為是冪等的方法。
  • HttpClient 會自動重試那些有傳輸異常的方法塌衰,如果該HTTP請求仍然在傳輸給目標(biāo)服務(wù)器(例如沒有完全傳輸?shù)椒?wù)的請求)诉稍。

1.5.4 請求重試處理器

為了實(shí)現(xiàn)自定義的恢復(fù)機(jī)制,我們可以提供一個HttprequestRetryHandler接口的實(shí)現(xiàn)最疆。

    public void chapter1_5_4() {
        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 ConnectTimeoutException) {
                    // Connection refused
                    return false;
                }
                if (exception instanceof InterruptedIOException) {
                    // Timeout
                    return false;
                }
                if (exception instanceof UnknownHostException) {
                    // Unknown host
                    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();
    }

請注意到我們可以使用StandardHttpRequestRetryHandler杯巨,而不是使用默認(rèn)的處理器,來自動重試那些被RFC-2616定義為冪等的那些請求方法:GET, HEAD, PUT, DELETE, OPTIONSTRACE努酸。

1.6 中止請求

在某些情況下服爷,HTTP請求在期望的時間內(nèi)未執(zhí)行成功,可能是因?yàn)槟繕?biāo)服務(wù)器的高負(fù)載或在客戶端有過多的并發(fā)請求蚊逢。這種情況下层扶,可能需要過早的結(jié)束請求并釋放被I/O操作阻塞的線程。任何被HttpClient執(zhí)行的HTTP請求能夠在任何階段通過調(diào)用HttpUriRequest#abort()方法來中止烙荷。這個方法是線程安全的且能被任何線程調(diào)用镜会。當(dāng)一個HTTP請求被中止,它的執(zhí)行線程(即使被I/O操作阻塞)也能夠保證解鎖(拋出異常InterruptedIOException)终抽。

1.7 重定向處理

HttpClient自動處理所有類型的重定向戳表,除了那些被HTTP規(guī)約顯示禁止的需要用戶干預(yù)的桶至。參考其他在POSTPUT上的重定向(狀態(tài)碼303),HTTP規(guī)約要求其轉(zhuǎn)換成GET請求匾旭。我們可以使用一個自定義的重定向策略來放寬HTTP規(guī)約對POST方法的限制镣屹。

    public void chapter1_7_a() {
        LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
        CloseableHttpClient httpclient = HttpClients.custom()
                .setRedirectStrategy(redirectStrategy)
                .build();
    }

HttpClient經(jīng)常在執(zhí)行的過程中重寫請求報文。對于默認(rèn)的HTTP/1.0和HTTP/1.1來說通常是使用相對路徑的請求URI价涝。同樣地女蜈,原始請求可能被多次重定向到另一個位置上。最終被解析的絕對路徑的HTTP地址色瘩,能夠使用原始請求和上下文來構(gòu)建伪窖。工具方法URIUtils#resolve就是用來構(gòu)建最終解析到的絕對路徑URI,從而生成最終的請求居兆。這個方法包含重定向請求中的最后一個段的標(biāo)識或原始請求覆山。

    @Test
    public void chapter1_7_b() throws IOException, URISyntaxException {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpClientContext context = HttpClientContext.create();
        HttpGet httpGet = new HttpGet("http://www.yy.com");
        CloseableHttpResponse response = httpclient.execute(httpGet, context);
        try {
            HttpHost target = context.getTargetHost();
            List<URI> redirectLocation = context.getRedirectLocations();
            URI location = URIUtils.resolve(httpGet.getURI(), target, redirectLocation);
            System.out.println("Final Http location: " + location.toASCIIString());
        } finally {
            response.close();
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市泥栖,隨后出現(xiàn)的幾起案子簇宽,更是在濱河造成了極大的恐慌,老刑警劉巖吧享,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件魏割,死亡現(xiàn)場離奇詭異,居然都是意外死亡耙蔑,警方通過查閱死者的電腦和手機(jī)见妒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甸陌,“玉大人须揣,你說我怎么就攤上這事∏恚” “怎么了耻卡?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長牲尺。 經(jīng)常有香客問我卵酪,道長,這世上最難降的妖魔是什么谤碳? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任溃卡,我火速辦了婚禮,結(jié)果婚禮上蜒简,老公的妹妹穿的比我還像新娘瘸羡。我一直安慰自己,他們只是感情好搓茬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布犹赖。 她就那樣靜靜地躺著队他,像睡著了一般。 火紅的嫁衣襯著肌膚如雪峻村。 梳的紋絲不亂的頭發(fā)上麸折,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天,我揣著相機(jī)與錄音粘昨,去河邊找鬼垢啼。 笑死,一個胖子當(dāng)著我的面吹牛雾棺,可吹牛的內(nèi)容都是我干的膊夹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼捌浩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了工秩?” 一聲冷哼從身側(cè)響起尸饺,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎助币,沒想到半個月后浪听,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡眉菱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年迹栓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俭缓。...
    茶點(diǎn)故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡克伊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出华坦,到底是詐尸還是另有隱情愿吹,我是刑警寧澤,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布惜姐,位于F島的核電站犁跪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏歹袁。R本人自食惡果不足惜坷衍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望条舔。 院中可真熱鬧枫耳,春花似錦、人聲如沸逞刷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至仑最,卻和暖如春扔役,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背警医。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工亿胸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人预皇。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓侈玄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吟温。 傳聞我的和親對象是個殘疾皇子序仙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評論 2 348

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

  • 原文鏈接 第1章:基礎(chǔ) 1.1. 執(zhí)行請求 HttpClient最重要的功能是執(zhí)行HTTP方法。 執(zhí)行HTTP方法...
    王德培閱讀 2,332評論 0 3
  • 前言 超文本傳輸協(xié)議(HTTP)也許是當(dāng)今互聯(lián)網(wǎng)上使用的最重要的協(xié)議了鲁豪。Web服務(wù)潘悼,有網(wǎng)絡(luò)功能的設(shè)備和網(wǎng)絡(luò)計算的發(fā)...
    狂奔的蝸牛_wxc閱讀 5,505評論 0 12
  • 1.1 請求執(zhí)行 HttpClient 最重要的功能是執(zhí)行 HTTP 方法。執(zhí)行 HTTP 方法涉及一個或多個 H...
    changhr2013閱讀 6,017評論 1 7
  • Apsara Clouder云安全專項(xiàng)技能認(rèn)證:網(wǎng)絡(luò)層安全認(rèn)知 網(wǎng)絡(luò)層安全認(rèn)知認(rèn)證旨在幫助學(xué)員理解云計算環(huán)境下網(wǎng)絡(luò)...
    xmvip01閱讀 197評論 0 0
  • 人生走的每一步都算數(shù) 既然人生有那么多彎路要走爬橡,那就認(rèn)真走完每一步治唤。 最近看了一本書《認(rèn)知差》其中有一篇文章是講述...
    L素記閱讀 713評論 0 0