HttpClient4.5教程

前言

舊版本的HttpClient已經停止維護了,它已經被Apache HttpComponents項目的HttpClient和HttpCore模塊替代。Hyper-Text Transfer Protocol (HTTP)也許是當今互聯網上使用的最爲重要的協議。雖然java.net package提供了基本的從HTTP獲取資源的功能,但是它不提供全面的靈活性或許多應用程序所需的功能。HttpClient試圖填補這一空白,通過提供一個有效的、最新的、功能豐富的方案實現客戶端最近的HTTP標準和建議。注意HttpClient不是瀏覽器,而是一個客戶端HTTP傳輸庫,目的是傳送和接收HTTP信息,它缺少瀏覽器需要的UI, HTML渲染器和JavaScript引擎。
可以直接到官網下載,或者使用maven配置。HttpClient 4.5 需要Java 1.5或者更新的版本。
 <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore</artifactId>
    <version>4.4.6</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.3</version>
</dependency>

HttpClient最基本的功能是執行HTTP方法,執行一個HTTP方法包含了一個或者幾個的HTTP REQUEST/HTTP RESPONSE交換,這通常在HttpClient內部處理。用戶提供一個被執行的請求對象,HttpClient傳送這個請求到目標服務器並且返回一個對應的響應對象,或者當請求不成功時拋出一個異常。所有的HTTP請求都有一行,它包含了請求請求方法名、請求URI和HTTP協議版本(1.0或者1.1)。


請求

HttpClient支持開箱即用的,所有定義在HTTP/1.1規範中的方法:GET、HEAD、POST、PUT、DELETE、TRACE、OPTIONS。每個方法類型都有一個指定的類:HttpGet、HttpHead、HttpPost、HttpPut、HttpDelete、HttpTrace、HttpOptions。

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

HttpClient也提供了URIBuilder用於構建URI。

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=


響應

HTTP響應是服務端在接收和處理客戶端消息後返回給客戶端的消息。消息的第一行包含了協議版本,緊接着是狀態碼和狀態碼描述文字。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");

System.out.println(response.getProtocolVersion());//HTTP/1.1
System.out.println(response.getStatusLine().getStatusCode());//200
System.out.println(response.getStatusLine().getReasonPhrase());//OK
System.out.println(response.getStatusLine().toString());//HTTP/1.1 200 OK


消息頭

HTTP消息可以包含一些用來描述消息屬性的頭部消息,例如content length, content type等等。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);//Set-Cookie: c1=a; path=/; domain=localhost
Header h2 = response.getLastHeader("Set-Cookie");
System.out.println(h2);//Set-Cookie: c2=b; path="/", c3=c; domain="localhost"
Header[] hs = response.getHeaders("Set-Cookie");
System.out.println(hs.length);//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());
}
st
同樣,它也提供了將頭部消息解析成單個元素的方法

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

HTTP實體

HTTP消息可以包含內容實體,HTTP定義了兩個實體封裝請求方法:PUT和POST。HttpClient依靠內容的來源來區分三種實體。
streamed:內容來源於流或者動態生成,特別是,包含從HTTP響應接收的實體,streamed實體一般不可重複生成的。
self-contained:內容位於內存中或者是可獲得的,意味着它是獨立於連接和其他實體的,Self-contained實體一般可重複,這種類型的實體大都用於HTTP請求的封裝。
wrapping:內容來源於其他實體。
對於連接管理來說,當從HTTP響應中用流輸出內容的時候這些區分的重要的。對於僅僅由應用程序創建並且用HttpClient發送的請求實體來說,streamed和self-contained的區別是不重要的。既然如此,那麼就認爲不可重複的實體是streamed,可重複的實體是self-contained。
可重複的實體,表示它的內容可以不止一次被讀取,例如ByteArrayEntity和StringEntity。爲了讀取內容,任何人都可以使用HttpEntity#getContent()返回java.io.InputStream,或者用HttpEntity#writeTo(OutputStream)提供給輸出流。
當實體通過一個收到的報文獲取時,HttpEntity#getContentType()方法和HttpEntity#getContentLength()方法可以用來讀取通用的元數據,如Content-Type和Content-Length頭部信息(如果它們是可用的)。因爲頭部信息Content-Type可以包含對文本MIME類型的字符編碼,比如text/plain或text/html,HttpEntity#getContentEncoding()方法用來讀取這個信息。如果頭部信息Content-Length不可用,那麼就返回長度-1,而對於內容類型返回NULL。如果頭部信息Content-Type是可用的,那麼就會返回一個Header對象。

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


確保低級別資源釋放

爲了確保適當地釋放系統資源,任何人必須關閉實體相關的流或者response本身。關閉實體相關的流和response的區別是:前者是通過消耗實體內容保持連接可用,後者直接關閉和拋棄連接。請記住,HttpEntity#writeTo(OutputStream)一旦實體被完全寫出,也需要保證系統資源適當地釋放。 EntityUtils#consume(HttpEntity)可以保證實體內容被完全消耗並且底層的流被關閉。
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();
}
有些情況下,僅僅response的一小部分需要被取回並且消耗內容的剩餘部分且保持連接可用的性能代價是很高的,這種情況下可以直接關閉response。
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();
}

消耗實體內容

推薦的消耗實體內容的方法是HttpEntity#getContent()和HttpEntity#writeTo(OutputStream),但是EntityUtils類有一些靜態方法,這些方法可以更加容易地從實體中讀取內容或信息。代替直接讀取java.io.InputStream,也可以使用這個類中的方法以字符串/字節數組的形式獲取整個內容體。然而,EntityUtils的使用是強烈不鼓勵的,除非響應實體源自可靠的HTTP服務器和已知的長度限制。
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();
}
在一些情況下可能會不止一次的讀取實體。此時實體內容必須以某種方式在內存或磁盤上被緩衝起來。最簡單的方法是通過使用BufferedHttpEntity類來包裝源實體完成。這會引起源實體內容被讀取到內存的緩衝區中。在其它所有方式中,實體包裝器將會得到源實體。
CloseableHttpResponse response = <...>
HttpEntity entity = response.getEntity();
if (entity != null) {
    entity = new BufferedHttpEntity(entity);
}

生成實體內容

HttpClient提供一些類,它們可以用於通過HTTP連接有效地生成內容。這些類的實例可以和實體包裝請求例如POST和PUT相關聯以便包裝實體內容。HttpClient提供了幾個最爲常見的數據容器,比如字符串,字節數組,輸入流和文件提供了一些類:StringEntity,ByteArrayEntity,InputStreamEntity和FileEntity。請注意InputStreamEntity是不可重複的,因爲它僅僅能從低層數據流中讀取一次內容。通常來說,我們推薦實現一個定製的HttpEntity類,這是自我包含式的,用來代替使用通用的InputStreamEntity。FileEntity是一個很好的起點,FileEntity繼承HttpEntity。
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);

HTML表單

許多應用需要模擬表單提交,比如,想要記錄一個Web應用程序或提交輸入數據。HttpClient提供了實體類UrlEncodedFormEntity使之更容易實現。
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair("param1", "value1"));
formparams.add(new BasicNameValuePair("param2", "value2"));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
HttpPost httppost = new HttpPost("http://localhost/handler.do");
httppost.setEntity(entity);

內容分塊

一般來說,推薦讓HttpClient自己根據Http消息傳遞的特徵來選擇最合適的傳輸編碼。當然,如果非要手動控制也是可以的,設置HttpEntity#setChunked()爲true,可以分塊傳輸,但是如果是HTTP/1.0,那麼這個設置就會被忽略,值有HTTP/1.1才支持。
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);

response處理

最簡單也是最方便的處理http響應的方法就是使用ResponseHandler接口,這個接口中有handleResponse(HttpResponse response)方法。使用這個方法,用戶完全不用關心http連接管理器。當使用ResponseHandler時,HttpClient會自動地將Http連接釋放給Http管理器,即使http請求失敗了或者拋出了異常。
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);

HttpClient的接口

HttpClient的接口代表了請求執行過程中最基本的契約,HttpClient接口沒有對Http請求的過程做特別的限制和詳細的規定,連接管理、狀態管理、認證處理和重定向處理這些功能都單獨實現。這樣就能更加容易地給接口添加額外的功能例如響應內容緩存。一般說來,HttpClient實際上就是一系列特殊的handler或者說策略接口的實現,這些handler(測試接口)負責着處理Http協議的某一方面,比如重定向、認證處理、有關連接持久性和keep alive持續時間的決策。這樣就允許用戶使用自定義的參數來代替默認配置,實現個性化的功能。
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();

HTTPCLIENT的線程安全性

HttpClient已經實現了線程安全。因此建議一個實例被多個請求重用。
 

HTTPCLIENT資源分配

當一個CloseableHttpClient的實例不再被使用,並且它的作用範圍即將失效,和它相關的連接必須被關閉,關閉方法可以調用CloseableHttpClient的close()方法。
CloseableHttpClient httpclient = HttpClients.createDefault();
try {
    <...>
} finally {
    httpclient.close();
}

Http執行上下文

最初,Http被設計成一種無狀態的、面向請求-響應的協議。然而,在實際使用中,我們希望能夠在一些邏輯相關的請求-響應中,保持狀態信息。爲了使應用程序可以保持Http的持續狀態,HttpClient允許http連接在特定的Http上下文中執行。如果在持續的http請求中使用了同樣的上下文,那麼這些請求就可以被分配到一個邏輯會話中。HTTP上下文就和一個java.util.Map<String, Object>功能類似。它實際上就是一個任意命名的值的集合。應用程序可以在Http請求執行前填充上下文的值,也可以在請求執行完畢後檢查上下文。
HttpContext可以包含任意類型的對象,因此如果在多線程中共享上下文會不安全。建議每個線程都只包含自己的http上下文。
在Http請求執行的過程中,HttpClient會自動添加下面的屬性到Http上下文中:
HttpConnection的實例,表示客戶端與服務器之間的連接
HttpHost的實例,表示要連接的目標服務器
HttpRoute的實例,表示全部的連接路由
HttpRequest的實例,表示Http請求。在執行上下文中,最終的HttpRequest對象會代表http消息的狀態。Http/1.0和Http/1.1都默認使用相對的uri。但是如果使用了非隧道模式的代理服務器,就會使用絕對路徑的uri。
HttpResponse的實例,表示Http響應
java.lang.Boolean對象,表示是否請求被成功的發送給目標服務器
RequestConfig對象,表示http request的配置信息
java.util.List<Uri>對象,表示Http響應中的所有重定向地址
可以使用HttpClientContext這個適配器來簡化和上下文狀態交互的過程。
HttpContext context = <...>
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpHost target = clientContext.getTargetHost();
HttpRequest request = clientContext.getRequest();
HttpResponse response = clientContext.getResponse();
RequestConfig config = clientContext.getRequestConfig();
同一個邏輯會話中的多個Http請求,應該使用相同的Http上下文來執行,這樣就可以自動地在http請求中傳遞會話上下文和狀態信息。
在下面的例子中,我們在開頭設置的參數,會被保存在上下文中,並且會應用到後續的http請求中。
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();
}

HTTP協議攔截器

HTTP協議攔截器是一種實現一個特定的方面的HTTP協議的代碼程序。通常情況下,協議攔截器會將一個或多個頭消息加入到接收或者發送的消息中。協議攔截器也可以操作消息的內容實體—消息內容的壓縮/解壓縮就是個很好的例子。通常,這是通過使用“裝飾”開發模式,一個包裝實體類用於裝飾原來的實體來實現。一個攔截器可以合併,形成一個邏輯單元。協議攔截器可以通過共享信息協作——比如處理狀態——通過HTTP執行上下文。協議攔截器可以使用Http上下文存儲一個或者多個連續請求的處理狀態。通常,只要攔截器不依賴於一個特定狀態的http上下文,那麼攔截執行的順序就無所謂。如果協議攔截器有相互依賴關係,必須以特定的順序執行,那麼它們應該按照特定的順序加入到協議處理器中。協議處理器必須是線程安全的。類似於servlets,協議攔截器不應該使用實例變量,除非訪問這些實例變量是同步的(線程安全的)。
下面是個例子,講述了本地的上下文時如何在連續請求中記錄處理狀態的:
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();
    }
}

異常處理

HttpClient會被拋出兩種類型的異常,一種是java.io.IOException,當遇到I/O異常時拋出(socket超時,或者socket被重置);另一種是HttpException,表示Http失敗,如Http協議使用不正確。通常認爲,I/O錯誤時不致命、可修復的,而Http協議錯誤是致命了,不能自動修復的錯誤。請注意,HttpException如果被重新拋出成IOException的子類ClientProtocolException,就可以讓用戶在一個catch語句裏同時處理I/O錯誤和協議錯誤。

HTTP傳輸安全

Http協議不能滿足所有類型的應用場景,我們需要知道這點。Http是個簡單的面向協議的請求/響應的協議,當初它被設計用來支持靜態或者動態生成的內容檢索,之前從來沒有人想過讓它支持事務性操作。例如,Http服務器成功接收、處理請求後,生成響應消息,並且把狀態碼發送給客戶端,這個過程是Http協議應該保證的。但是,如果客戶端由於讀取超時、取消請求或者系統崩潰導致接收響應失敗,服務器不會回滾這一事務。如果客戶端重新發送這個請求,服務器就會重複的解析、執行這個事務。在一些情況下,這會導致應用程序的數據損壞和應用程序的狀態不一致。即使Http當初設計是不支持事務操作,但是它仍舊可以作爲傳輸協議爲某些關鍵程序提供服務。爲了保證Http傳輸層的安全性,系統必須保證應用層上的http方法的冪等性。

方法的冪等性

HTTP/1.1規範中是這樣定義冪等方法的,[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]。用其他話來說,應用程序需要做好準備,處理同一方法多次執行造成的影響。添加一個具有唯一性的id就能避免重複執行同一個邏輯請求,問題解決。請知曉,這個問題不只是HttpClient纔會有,基於瀏覽器的應用程序也會遇到Http方法不冪等的問題。HttpClient默認把非實體方法get、head方法看做冪等方法,把實體方法post、put方法看做非冪等方法。

異常自動修復

默認情況下,HttpClient會嘗試自動修復I/O異常。這種自動修復僅限於修復幾個公認安全的異常。
HttpClient不會嘗試修復任何邏輯或者http協議錯誤(即從HttpException衍生出來的異常)。
HttpClient會自動再次發送冪等的方法(如果首次執行失敗)。
HttpClient會自動再次發送遇到transport異常的方法,前提是Http請求仍舊保持着連接(例如http請求沒有全部發送給目標服務器,HttpClient會再次嘗試發送)。

請求重試HANDLER

如果要自定義異常處理機制,我們需要實現HttpRequestRetryHandler接口。請注意,可以使用StandardHttpRequestRetryHandler替代默認的接口以便使那些在 RFC-2616定義的冪等性的請求方法安全的自動重試:GET, HEAD, PUT, DELETE, OPTIONS, TRACE。
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();

中斷請求

有時候由於目標服務器負載過高或者客戶端目前有太多請求積壓,http請求不能在指定時間內執行完畢。這時候終止這個請求,釋放阻塞I/O的進程,就顯得很必要。通過HttpClient執行的Http請求,在任何狀態下都能通過調用HttpUriRequest的abort()方法來終止。這個方法是線程安全的,並且能在任何線程中調用。當Http請求被終止了,本線程(即使現在正在阻塞I/O)也會通過拋出一個InterruptedIOException異常,來釋放資源。

重定向處理

HttpClient會自動處理所有類型的重定向,除了那些Http規範明確禁止的重定向。See Other (status code 303) redirects on POST and PUT requests are converted to GET requests as required by the HTTP specification. 可以使用自定義的重定向策略來放鬆Http規範對Post方法重定向的限制。
LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
CloseableHttpClient httpclient = HttpClients.custom()
        .setRedirectStrategy(redirectStrategy)
        .build();
HttpClient在請求執行過程中,經常需要重寫請求的消息。 HTTP/1.0和HTTP/1.1都默認使用相對的uri路徑。同樣,原始的請求可能會被者多次的重定向。最終絕對路徑可以使用原始的請求和上下文來構建。URIUtils#resolve可以用於構建絕對路徑,產生最終的請求。這個方法包含了最後一個分片標識符或者原始請求。
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();
}

發佈了40 篇原創文章 · 獲贊 36 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章