HttpClient 4.5.2 文檔的中英文參考(第一章) Chapter 1 Fundamentals

轉譯自:http://hc.apache.org/httpcomponents-client-ga/tutorial/html/fundamentals.html
本文在已發佈在GitHub(https://github.com/clxering/Apache-HttpComponents-Doc-Chinese-English-bilingual/tree/dev/HttpClient/HttpClient-Tutorial/1-Fundamentals#chapter-1-fundamentals)歡迎糾錯訂正

Chapter 1 Fundamentals

1.1 Request execution

The most essential function of HttpClient is to execute HTTP methods. Execution of an HTTP method involves one or several HTTP request / HTTP response exchanges, usually handled internally by HttpClient. The user is expected to provide a request object to execute and HttpClient is expected to transmit the request to the target server return a corresponding response object, or throw an exception if execution was unsuccessful.

HttpClient 最基本的功能是執行 HTTP 方法。HTTP 方法的執行涉及一個或多個 HTTP 請求或 HTTP 響應的交互,這通常在 HttpClient 內部處理。用戶需要提供一個要執行的請求對象,HttpClient 將請求傳輸到目標服務器並返回相應的響應對象,如果執行不成功,則拋出異常。

Quite naturally, the main entry point of the HttpClient API is the HttpClient interface that defines the contract described above.

顯而易見,HttpClient API 的主要入口點是 HttpClient 接口,它定義了上面描述的約定。

Here is an example of request execution process in its simplest form:

下面這個例子,演示了一個請求的最簡執行過程:

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

1.1.1 HTTP request

All HTTP requests have a request line consisting a method name, a request URI and an HTTP protocol version.

所有 HTTP 請求都有一個請求行,其中包含方法名、請求 URI 和 HTTP 協議版本。

HttpClient supports out of the box all HTTP methods defined in the HTTP/1.1 specification: GET, HEAD, POST, PUT, DELETE, TRACE and OPTIONS. There is a specific class for each method type.: HttpGet, HttpHead, HttpPost, HttpPut, HttpDelete, HttpTrace, and HttpOptions.

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

The Request-URI is a Uniform Resource Identifier that identifies the resource upon which to apply the request. HTTP request URIs consist of a protocol scheme, host name, optional port, resource path, optional query, and optional fragment.

Request-URI 是統一資源標識符,它標識要請求的資源。HTTP Request-URI 由協議方案、主機名、端口(可選)、資源路徑、查詢條件(可選)和 fragment 標識(可選)組成。

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

HttpClient provides URIBuilder utility class to simplify creation and modification of request URIs.

HttpClient 提供了 URIBuilder 實用工具類來簡化 Request-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());

stdout >

輸出如下結果:

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

1.1.2 HTTP response

HTTP response is a message sent by the server back to the client after having received and interpreted a request message. The first line of that message consists of the protocol version followed by a numeric status code and its associated textual phrase.

HTTP 響應是服務器在接收並解析請求消息之後發送回客戶端的消息。該消息的第一行由協議版本、數字狀態代碼及其相關的文本短語組成。

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

stdout >

輸出如下結果:

HTTP/1.1
200
OK
HTTP/1.1 200 OK

1.1.3 Working with message headers

An HTTP message can contain a number of headers describing properties of the message such as the content length, content type and so on. HttpClient provides methods to retrieve, add, remove and enumerate headers.

HTTP 消息可以包含許多消息頭,它們描述了消息屬性(如內容長度、內容類型等)。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);

stdout >

輸出如下結果:

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

The most efficient way to obtain all headers of a given type is by using the HeaderIterator interface.

獲取給定類型的所有消息頭,最有效方法是使用 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());
}

stdout >

輸出如下:

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

It also provides convenience methods to parse HTTP messages into individual header elements.

HttpClient 還提供了便捷的方法來將 HTTP 消息解析爲獨立的消息頭元素。

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

stdout >

輸出如下:

c1 = a
path=/
domain=localhost
c2 = b
path=/
c3 = c
domain=localhost

1.1.4 HTTP entity

HTTP messages can carry a content entity associated with the request or response. Entities can be found in some requests and in some responses, as they are optional. Requests that use entities are referred to as entity enclosing requests. The HTTP specification defines two entity enclosing request methods: POST and PUT. Responses are usually expected to enclose a content entity. There are exceptions to this rule such as responses to HEAD method and 204 No Content, 304 Not Modified, 205 Reset Content responses.

HTTP 消息可以攜帶與請求或響應相關聯的內容實體。實體可以在某些請求和響應中找到,因爲它們是可選的。使用實體的請求稱爲 entity enclosing request。HTTP 規範定義了兩個 entity enclosing request 的方法:POST 和 PUT。通常認爲響應包含一個內容實體。這個規則也有例外,比如 HEAD 方法的響應和幾種常見的響應:204 No Content、304 Not Modified、205 Reset Content。

HttpClient distinguishes three kinds of entities, depending on where their content originates:

HttpClient 根據內容的來源將實體分爲三種:

  • streamed: The content is received from a stream, or generated on the fly. In particular, this category includes entities being received from HTTP responses. Streamed entities are generally not repeatable.

streamed:內容從流中接收,或即時生成的。尤其應注意的是,這個類別也包括從 HTTP 響應接收的實體。streamed 實體通常是不可重複的。

  • self-contained: The content is in memory or obtained by means that are independent from a connection or other entity. Self-contained entities are generally repeatable. This type of entities will be mostly used for entity enclosing HTTP requests.

self-contained:這些內容在內存中,或者 obtained by means that are independent from a connection or other entity。self-contained 實體通常是可重複的。這種類型的實體主要用於封裝 HTTP 請求的實體。

  • wrapping: The content is obtained from another entity.

wrapping:內容從另一個實體獲得。

This distinction is important for connection management when streaming out content from an HTTP response. For request entities that are created by an application and only sent using HttpClient, the difference between streamed and self-contained is of little importance. In that case, it is suggested to consider non-repeatable entities as streamed, and those that are repeatable as self-contained.

當從 HTTP 響應中輸出內容時,這些區別對於連接管理非常重要。對於由應用程序創建且僅使用 HttpClient 發送的請求實體,streamed 和 self-contained 的區別並不重要。在這種情況下,建議將不可重複的實體視爲 streamed,將可重複的實體視爲 self-contained。

1.1.4.1 Repeatable entities

An entity can be repeatable, meaning its content can be read more than once. This is only possible with self contained entities (like ByteArrayEntity or StringEntity)

一個實體可重複,這意味着它的內容可以被多次讀取。這僅適用於 self-contained 實體(如 ByteArrayEntity 或 StringEntity)

1.1.4.2 Using HTTP entities

Since an entity can represent both binary and character content, it has support for character encodings (to support the latter, ie. character content).

由於實體既可以表示二進制內容,也可以表示字符內容,所以它支持字符編碼(to support the latter, ie. character content)。

The entity is created when executing a request with enclosed content or when the request was successful and the response body is used to send the result back to the client.

實體是在執行 enclosed 內容的請求,或請求成功時創建的,響應體用於將結果發送回客戶端。

To read the content from the entity, one can either retrieve the input stream via the HttpEntity#getContent() method, which returns an java.io.InputStream, or one can supply an output stream to the HttpEntity#writeTo(OutputStream) method, which will return once all content has been written to the given stream.

要從實體中讀取內容,可以通過 HttpEntity#getContent() 方法,它返回 java.io.InputStream,也可以向 HttpEntity#writeTo(OutputStream) 方法提供輸出流,該方法將所有內容寫入給定流後返回。

When the entity has been received with an incoming message, the methods HttpEntity#getContentType() and HttpEntity#getContentLength() methods can be used for reading the common metadata such as Content-Type and Content-Length headers (if they are available). Since the Content-Type header can contain a character encoding for text mime-types like text/plain or text/html, the HttpEntity#getContentEncoding() method is used to read this information. If the headers aren't available, a length of -1 will be returned, and NULL for the content type. If the Content-Type header is available, a Header object will be returned.

當接收到帶有傳入消息的實體時,方法 HttpEntity#getContentType() 和 HttpEntity#getContentLength() 可用於讀取常見的元數據,如 Content-Type 和 Content-Length 頭信息(如果它們可用)。由於 Content-Type 頭可以包含文本 mime-type(如 text/plain 或 text/html)的字符編碼,因此使用 HttpEntity#getContentEncoding() 方法來讀取此信息。如果頭不可用,則返回長度 -1,內容類型爲 NULL。如果 Content-Type 頭可用,則返回一個 Header 對象。

When creating an entity for a outgoing message, this meta data has to be supplied by the creator of the entity.

在爲傳出消息創建實體時,必須由實體的創建者提供元數據。

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

stdout >

輸出如下:

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

1.1.5 Ensuring release of low level resources

In order to ensure proper release of system resources one must close either the content stream associated with the entity or the response itself

爲了確保系統資源正確釋放,必須關閉與實體關聯的內容流或響應本身。

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

The difference between closing the content stream and closing the response is that the former will attempt to keep the underlying connection alive by consuming the entity content while the latter immediately shuts down and discards the connection.

關閉內容流和關閉響應之間的區別在於,前者使用實體內容後,能保持底層連接,而後者將立即關閉並丟棄連接。

Please note that the HttpEntity#writeTo(OutputStream) method is also required to ensure proper release of system resources once the entity has been fully written out. If this method obtains an instance of java.io.InputStream by calling HttpEntity#getContent(), it is also expected to close the stream in a finally clause.

請注意,HttpEntity#writeTo(OutputStream) 方法也需要確保在實體被完全寫入之後釋放系統資源。如果該方法通過調用 HttpEntity#getContent() 獲得 java.io.InputStream 實例,則還應該在 finally 子句中關閉流。

When working with streaming entities, one can use the EntityUtils#consume(HttpEntity) method to ensure that the entity content has been fully consumed and the underlying stream has been closed.

在處理流實體時,可以使用 EntityUtils#consume(HttpEntity) 方法來確保實體內容已被完全消費,並且底層流已被關閉。

There can be situations, however, when only a small portion of the entire response content needs to be retrieved and the performance penalty for consuming the remaining content and making the connection reusable is too high, in which case one can terminate the content stream by closing the 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();
}

The connection will not be reused, but all level resources held by it will be correctly deallocated.

連接將不會被重用,它所擁有的所有層級的資源都將被正確釋放。

1.1.6 Consuming entity content

The recommended way to consume the content of an entity is by using its HttpEntity#getContent() or HttpEntity#writeTo(OutputStream) methods. HttpClient also comes with the EntityUtils class, which exposes several static methods to more easily read the content or information from an entity. Instead of reading the java.io.InputStream directly, one can retrieve the whole content body in a string / byte array by using the methods from this class. However, the use of EntityUtils is strongly discouraged unless the response entities originate from a trusted HTTP server and are known to be of limited length.

推薦消費實體內容的方法是使用 HttpEntity#getContent() 或 HttpEntity#writeTo(OutputStream) 方法。HttpClient 還附帶了 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();
}

In some situations it may be necessary to be able to read entity content more than once. In this case entity content must be buffered in some way, either in memory or on disk. The simplest way to accomplish that is by wrapping the original entity with the BufferedHttpEntity class. This will cause the content of the original entity to be read into a in-memory buffer. In all other ways the entity wrapper will be have the original one.

在某些情況下,可能需要多次讀取實體內容。在這種情況下,實體內容必須以某種方式緩衝在內存或磁盤。最簡單的方法是使用 BufferedHttpEntity 類包裝原始實體。這將導致原始實體的內容被讀入內存緩衝區。In all other ways the entity wrapper will be have the original one.

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

1.1.7 Producing entity content

HttpClient provides several classes that can be used to efficiently stream out content throught HTTP connections. Instances of those classes can be associated with entity enclosing requests such as POST and PUT in order to enclose entity content into outgoing HTTP requests. HttpClient provides several classes for most common data containers such as string, byte array, input stream, and file: StringEntity, ByteArrayEntity, InputStreamEntity, and FileEntity.

HttpClient 提供了幾個類用於通過 HTTP 連接高效地輸出內容。這些類的實例可以與諸如 POST 和 PUT 等 entity enclosing requests 的實體相關聯,以便將實體內容封裝到傳出 HTTP 請求中。HttpClient 爲最常見的數據容器提供了幾個類(如字符串、字節數組、輸入流和文件):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);

Please note InputStreamEntity is not repeatable, because it can only read from the underlying data stream once. Generally it is recommended to implement a custom HttpEntity class which is self-contained instead of using the generic InputStreamEntity. FileEntity can be a good starting point.

請注意 InputStreamEntity 是不可重複的,因爲它只能從底層數據流讀取一次。通常情況下,建議實現自定義的 HttpEntity 類,該類是 self-contained 的,而不是使用通用的 InputStreamEntity。FileEntity 可以作爲一個很好的參考。

1.1.7.1 HTML forms

Many applications need to simulate the process of submitting an HTML form, for instance, in order to log in to a web application or submit input data. HttpClient provides the entity class UrlEncodedFormEntity to facilitate the process.

許多應用程序需要模擬提交 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);

The UrlEncodedFormEntity instance will use the so called URL encoding to encode parameters and produce the following content:

UrlEncodedFormEntity 實例將使用所謂的 URL 編碼對參數進行編碼,並生成以下內容:

param1=value1&param2=value2

1.1.7.2 Content chunking

Generally it is recommended to let HttpClient choose the most appropriate transfer encoding based on the properties of the HTTP message being transferred. It is possible, however, to inform HttpClient that chunk coding is preferred by setting HttpEntity#setChunked() to true. Please note that HttpClient will use this flag as a hint only. This value will be ignored when using HTTP protocol versions that do not support chunk coding, such as HTTP/1.0.

通常情況下,建議讓 HttpClient 根據要傳輸的 HTTP 消息的屬性選擇最合適的傳輸編碼。但是,可以通過將 HttpEntity#setChunked() 設置爲 true 來通知 HttpClient 首選 Chunked 編碼。請注意 HttpClient 將只使用此標誌作爲提示。當使用不支持 Chunked 編碼的 HTTP 協議版本(如 HTTP/1.0)時,此值將被忽略。

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 Response handlers

The simplest and the most convenient way to handle responses is by using the ResponseHandler interface, which includes the handleResponse(HttpResponse response) method. This method completely relieves the user from having to worry about connection management. When using a ResponseHandler, HttpClient will automatically take care of ensuring release of the connection back to the connection manager regardless whether the request execution succeeds or causes an exception.

處理響應最簡單和最方便的方法是使用 ResponseHandler 接口,其中包括 handleResponse(HttpResponse response) 方法。這種方法完全讓用戶不必擔心連接管理。當使用 ResponseHandler 時,HttpClient 將確保連接釋放回連接管理器,無論請求執行是否成功或導致異常。

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 interface

HttpClient interface represents the most essential contract for HTTP request execution. It imposes no restrictions or particular details on the request execution process and leaves the specifics of connection management, state management, authentication and redirect handling up to individual implementations. This should make it easier to decorate the interface with additional functionality such as response content caching.

HttpClient 接口代表 HTTP 請求執行的最基本約定。它對請求執行過程沒有任何限制或指明具體細節,而將連接管理、狀態管理、身份驗證和重定向處理的細節留給各個實現。這讓使用附加功能(如響應內容緩存)裝飾接口變得更容易。

Generally HttpClient implementations act as a facade to a number of special purpose handler or strategy interface implementations responsible for handling of a particular aspect of the HTTP protocol such as redirect or authentication handling or making decision about connection persistence and keep alive duration. This enables the users to selectively replace default implementation of those aspects with custom, application specific ones.

通常,HttpClient 實現充當許多專用處理程序或策略接口實現的基礎,這些處理程序或策略接口實現負責處理 HTTP 協議的特定實現,比如重定向或身份驗證處理,或者決定連接持久性和保持活動持續時間。這使用戶能夠選擇定製的、特定於應用程序的實現來替換默認實現。

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 thread safety

HttpClient implementations are expected to be thread safe. It is recommended that the same instance of this class is reused for multiple request executions.

HttpClient 實現應該是線程安全的。建議在多個請求執行中重用該類的同一個實例。

1.2.2 HttpClient resource deallocation

When an instance CloseableHttpClient is no longer needed and is about to go out of scope the connection manager associated with it must be shut down by calling the CloseableHttpClient#close() method.

當不再需要實例 CloseableHttpClient,並且超出作用域時,必須通過調用 CloseableHttpClient#close() 方法關閉與它關聯的連接管理器。

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

1.3 HTTP execution context

Originally HTTP has been designed as a stateless, response-request oriented protocol. However, real world applications often need to be able to persist state information through several logically related request-response exchanges. In order to enable applications to maintain a processing state HttpClient allows HTTP requests to be executed within a particular execution context, referred to as HTTP context. Multiple logically related requests can participate in a logical session if the same context is reused between consecutive requests. HTTP context functions similarly to a java.util.Map<String, Object>. It is simply a collection of arbitrary named values. An application can populate context attributes prior to request execution or examine the context after the execution has been completed.

最初,HTTP 被設計爲一種無狀態的、面向響應請求的協議。然而,應用程序常需要能夠通過幾個邏輯上相關的請求響應交換來持久化狀態信息。爲了使應用程序能夠保持處理狀態,HttpClient 允許在特定的執行環境(稱爲 HTTP 上下文)中執行 HTTP 請求。如果在連續的請求之間重用相同的執行上下文,則多個邏輯相關的請求可以參與邏輯會話。HTTP 上下文的功能類似於 java.util.Map<String, Object>。它只是任意命名值的集合。應用程序可以在請求執行之前填充上下文屬性,或者在執行完成後檢查上下文。

HttpContext can contain arbitrary objects and therefore may be unsafe to share between multiple threads. It is recommended that each thread of execution maintains its own context.

HttpContext 可以包含任意對象,因此在多個線程之間共享可能是不安全的。建議每個執行線程維護自己的上下文。

In the course of HTTP request execution HttpClient adds the following attributes to the execution context:

在 HTTP 請求執行過程中,HttpClient 向執行上下文添加了以下屬性:

  • HttpConnection instance representing the actual connection to the target server.

HttpConnection 實例,表示到目標服務器的實際連接。

  • HttpHost instance representing the connection target.

HttpHost 實例用於代表連接目標。

  • HttpRoute instance representing the complete connection route

HttpRoute 實例表示完整的連接路由。

  • HttpRequest instance representing the actual HTTP request. The final HttpRequest object in the execution context always represents the state of the message exactly as it was sent to the target server. Per default HTTP/1.0 and HTTP/1.1 use relative request URIs. However if the request is sent via a proxy in a non-tunneling mode then the URI will be absolute.

HttpRequest 實例用於代表實際 HTTP 請求。執行上下文中最後一個 HttpRequest 對象始終表示消息發送到目標服務器時的狀態。HTTP/1.0 和 HTTP/1.1 都默認使用相對請求 URI。然而,如果請求以非隧道模式通過代理髮送,則 URI 將是絕對的。

  • HttpResponse instance representing the actual HTTP response.

HttpResponse 實例用於表示實際的 HTTP 響應。

  • java.lang.Boolean object representing the flag indicating whether the actual request has been fully transmitted to the connection target.

java.lang.Boolean 對象用於標誌實際請求是否已完全傳輸到連接目標。

  • RequestConfig object representing the actual request configuation.

RequestConfig 對象,表示實際的請求配置。

  • java.util.List<URI> object representing a collection of all redirect locations received in the process of request execution.

java.util.List<URI> 對象,表示在請求執行過程中接收到的所有重定向位置的集合。

One can use HttpClientContext adaptor class to simplify interractions with the context state.

可以使用 HttpClientContext 適配器類來簡化與上下文狀態的交互。

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

Multiple request sequences that represent a logically related session should be executed with the same HttpContext instance to ensure automatic propagation of conversation context and state information between requests.

表示邏輯相關會話的多個請求序列應該使用相同的 HttpContext 實例執行,以確保會話上下文和狀態信息在請求之間自動傳播。

In the following example the request configuration set by the initial request will be kept in the execution context and get propagated to the consecutive requests sharing the same context.

在下面的示例中,初始請求設置的請求配置將保存在執行上下文中,並傳播到共享相同上下文中的連續請求中。

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 protocol interceptors

The HTTP protocol interceptor is a routine that implements a specific aspect of the HTTP protocol. Usually protocol interceptors are expected to act upon one specific header or a group of related headers of the incoming message, or populate the outgoing message with one specific header or a group of related headers. Protocol interceptors can also manipulate content entities enclosed with messages - transparent content compression / decompression being a good example. Usually this is accomplished by using the 'Decorator' pattern where a wrapper entity class is used to decorate the original entity. Several protocol interceptors can be combined to form one logical unit.

HTTP 協議攔截器是實現 HTTP 協議特定方面的一個程序。通常,協議攔截器應作用於傳入消息的一個特定頭或一組相關頭,或者用一個特定頭或一組相關頭填充傳出消息。協議攔截器還可以操作包含在消息中的內容實體(透明內容壓縮 / 解壓就是一個很好的例子)。通常這是使用「裝飾者」模式來實現的,其中使用包裝器實體類來裝飾原始實體。幾個協議攔截器可以組合成一個邏輯單元。

Protocol interceptors can collaborate by sharing information - such as a processing state - through the HTTP execution context. Protocol interceptors can use HTTP context to store a processing state for one request or several consecutive requests.

協議攔截器可以通過 HTTP 執行上下文共享信息(例如處理狀態)進行協作。協議攔截器可以使用 HTTP 上下文存儲一個請求或多個連續請求的處理狀態。

Usually the order in which interceptors are executed should not matter as long as they do not depend on a particular state of the execution context. If protocol interceptors have interdependencies and therefore must be executed in a particular order, they should be added to the protocol processor in the same sequence as their expected execution order.

通常,只要攔截器不依賴於執行上下文的特定狀態,攔截器執行的順序就不重要。如果協議攔截器具有相互依賴關係,就必須按照特定的順序執行,就應該按照預期的執行順序將它們添加到協議處理器中。

Protocol interceptors must be implemented as thread-safe. Similarly to servlets, protocol interceptors should not use instance variables unless access to those variables is synchronized.

協議攔截器必須實現爲線程安全的。與 servlet 類似,協議攔截器不應該使用實例變量,除非同步訪問這些變量。

This is an example of how local context can be used to persist a processing state between consecutive requests:

這是一個說明如何使用本地上下文在連續請求之間持久化處理狀態的例子:

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 Exception handling

HTTP protocol processors can throw two types of exceptions: java.io.IOException in case of an I/O failure such as socket timeout or an socket reset and HttpException that signals an HTTP failure such as a violation of the HTTP protocol. Usually I/O errors are considered non-fatal and recoverable, whereas HTTP protocol errors are considered fatal and cannot be automatically recovered from. Please note that HttpClient implementations re-throw HttpExceptions as ClientProtocolException, which is a subclass of java.io.IOException. This enables the users of HttpClient to handle both I/O errors and protocol violations from a single catch clause.

HTTP 協議處理器可以拋出兩種類型的異常:在 I/O 失敗(如套接字超時或套接字重置)的情況下拋出 java.io.IOException,以及發出 HTTP 失敗(如違反 HTTP 協議)信號的 HttpException。通常,I/O 錯誤被認爲是非致命的和可恢復的,而 HTTP 協議錯誤被認爲是致命的,不能自動恢復。請注意,HttpClient 實現將 HttpExceptions 重新拋出爲 ClientProtocolException,這是 java.io.IOException 的子類。這使得 HttpClient 的用戶能夠通過一個 catch 子句同時處理 I/O 錯誤拋出的異常和違反協議拋出的異常。

1.5.1 HTTP transport safety

It is important to understand that the HTTP protocol is not well suited to all types of applications. HTTP is a simple request/response oriented protocol which was initially designed to support static or dynamically generated content retrieval. It has never been intended to support transactional operations. For instance, the HTTP server will consider its part of the contract fulfilled if it succeeds in receiving and processing the request, generating a response and sending a status code back to the client. The server will make no attempt to roll back the transaction if the client fails to receive the response in its entirety due to a read timeout, a request cancellation or a system crash. If the client decides to retry the same request, the server will inevitably end up executing the same transaction more than once. In some cases this may lead to application data corruption or inconsistent application state.

重要的是要理解 HTTP 協議並不適合所有類型的應用程序。HTTP 是一個簡單的面向請求 / 響應的協議,最初設計它是爲了支持靜態或動態生成的內容檢索。它從來沒有打算支持事務操作。例如,如果 HTTP 服務器成功地接收和處理了請求、生成響應並將狀態代碼發送回客戶端,那麼它將認爲已經完成了約定的一部分。如果客戶端由於讀取超時、請求取消或系統崩潰而無法接收到完整的響應,服務器將不嘗試回滾事務。如果客戶端決定重試相同的請求,服務器將不可避免地多次執行相同的事務。在某些情況下,這可能導致應用程序數據損壞或應用程序狀態不一致。

Even though HTTP has never been designed to support transactional processing, it can still be used as a transport protocol for mission critical applications provided certain conditions are met. To ensure HTTP transport layer safety the system must ensure the idempotency of HTTP methods on the application layer.

儘管 HTTP 從來沒有被設計成支持事務處理,但如果滿足某些條件,它仍然可以作爲任務關鍵型應用程序的傳輸協議。爲了確保 HTTP 傳輸層的安全性,系統必須確保 HTTP 方法在應用層上的冪等性。

1.5.2 Idempotent methods

HTTP/1.1 specification defines an idempotent method as

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]

方法還可以具有「冪等性」的屬性,除了出現錯誤或過期的問題,N>0 次相同請求的作用與單次請求相同。

In other words the application ought to ensure that it is prepared to deal with the implications of multiple execution of the same method. This can be achieved, for instance, by providing a unique transaction id and by other means of avoiding execution of the same logical operation.

換句話說,應用程序應該確保應對處理同一方法的多次執行的影響。例如,可以通過提供唯一的事務 id 和避免執行相同邏輯操作的其他方法來實現這一點。

Please note that this problem is not specific to HttpClient. Browser based applications are subject to exactly the same issues related to HTTP methods non-idempotency.

請注意,這個問題不是 HttpClient 特有的。基於瀏覽器的應用程序與 HTTP 方法的非冪等性完全相同。

By default HttpClient assumes only non-entity enclosing methods such as GET and HEAD to be idempotent and entity enclosing methods such as POST and PUT to be not for compatibility reasons.

默認情況下,HttpClient 只假設非實體封裝的方法(如 GET 和 HEAD)是冪等的,而實體封裝的方法(如 POST 和 PUT)由於兼容性原因不是冪等的。

1.5.3 Automatic exception recovery

By default HttpClient attempts to automatically recover from I/O exceptions. The default auto-recovery mechanism is limited to just a few exceptions that are known to be safe.

默認情況下,HttpClient 嘗試從 I/O 異常中自動恢復。默認的自動恢復機制僅限於少數已知安全的異常。

  • HttpClient will make no attempt to recover from any logical or HTTP protocol errors (those derived from HttpException class).

HttpClient 將不嘗試從任何邏輯或 HTTP 協議錯誤(源自 HttpException 類的錯誤)中恢復。

  • HttpClient will automatically retry those methods that are assumed to be idempotent.

HttpClient 將自動重試那些假定爲冪等的方法。

  • HttpClient will automatically retry those methods that fail with a transport exception while the HTTP request is still being transmitted to the target server (i.e. the request has not been fully transmitted to the server).

當 HTTP 請求仍然傳輸到目標服務器(即請求還沒有完全傳輸到服務器)時,HttpClient 將自動重試那些由於傳輸異常而失敗的方法。

1.5.4 Request retry handler

In order to enable a custom exception recovery mechanism one should provide an implementation of the HttpRequestRetryHandler interface.

爲了啓用自定義異常恢復機制,應該提供 HttpRequestRetryHandler 接口的實現。

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

Please note that one can use StandardHttpRequestRetryHandler instead of the one used by default in order to treat those request methods defined as idempotent by RFC-2616 as safe to retry automatically: GET, HEAD, PUT, DELETE, OPTIONS, and TRACE.

請注意,爲了將 RFC-2616 定義爲冪等的請求方法視爲自動重試的安全方法,可以使用 StandardHttpRequestRetryHandler,而不是默認使用的方法:GET、HEAD、PUT、DELETE、OPTIONS 和 TRACE。

1.6 Aborting requests

In some situations HTTP request execution fails to complete within the expected time frame due to high load on the target server or too many concurrent requests issued on the client side. In such cases it may be necessary to terminate the request prematurely and unblock the execution thread blocked in a I/O operation. HTTP requests being executed by HttpClient can be aborted at any stage of execution by invoking HttpUriRequest#abort() method. This method is thread-safe and can be called from any thread. When an HTTP request is aborted its execution thread - even if currently blocked in an I/O operation - is guaranteed to unblock by throwing a InterruptedIOException

在某些情況下,由於目標服務器上的高負載或客戶端發出的併發請求太多,HTTP 請求執行無法在預期的時間內完成。在這種情況下,可能需要提前終止請求並解除 I/O 操作中阻塞的執行線程。通過調用 HttpUriRequest#abort() 方法,可以在執行的任何階段中止由 HttpClient 執行的 HTTP 請求。該方法是線程安全的,可以從任何線程調用。當 HTTP 請求中止時,它的執行線程(即使當前在 I/O 操作中被阻塞)也可以通過拋出一個 InterruptedIOException 來解除阻塞。

1.7 Redirect handling

HttpClient handles all types of redirects automatically, except those explicitly prohibited by the HTTP specification as requiring user intervention. See Other (status code 303) redirects on POST and PUT requests are converted to GET requests as required by the HTTP specification. One can use a custom redirect strategy to relaxe restrictions on automatic redirection of POST methods imposed by the HTTP specification.

HttpClient 自動處理所有類型的重定向,但 HTTP 規範明確禁止用戶進行干預的除外。根據 HTTP 規範的要求,POST 重定向(狀態碼 303)和 PUT 請求將被轉換爲 GET 請求。可以自定義重定向策略來放寬 HTTP 規範對 POST 方法自動重定向的限制。

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

HttpClient often has to rewrite the request message in the process of its execution. Per default HTTP/1.0 and HTTP/1.1 generally use relative request URIs. Likewise, original request may get redirected from location to another multiple times. The final interpreted absolute HTTP location can be built using the original request and the context. The utility method URIUtils#resolve can be used to build the interpreted absolute URI used to generate the final request. This method includes the last fragment identifier from the redirect requests or the original request.

HttpClient 在執行請求消息的過程中必須重寫請求消息。默認情況下,HTTP/1.0 和 HTTP/1.1 通常使用相對請求 URI。同樣,原始請求可能會多次從某個位置重定向到另一個位置。可以使用原始請求和上下文構建最終的絕對 HTTP 位置。URIUtils 的 URIUtils#resolve 方法可用於構建生成最終請求的絕對 URI。此方法包括重定向請求或原始請求的最後一個 fragment 標識符。

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

Back to contents of HttpClient Tutorial(返回 HttpClient 教程目錄)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章