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

轉(zhuǎn)譯自:http://hc.apache.org/httpcomponents-client-ga/tutorial/html/fundamentals.html
本文在已發(fā)布在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 最基本的功能是執(zhí)行 HTTP 方法魂贬。HTTP 方法的執(zhí)行涉及一個或多個 HTTP 請求或 HTTP 響應的交互,這通常在 HttpClient 內(nèi)部處理。用戶需要提供一個要執(zhí)行的請求對象颊亮,HttpClient 將請求傳輸?shù)侥繕朔掌鞑⒎祷叵鄳捻憫獙ο螅绻麍?zhí)行不成功育苟,則拋出異常阀圾。

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:

下面這個例子摊册,演示了一個請求的最簡執(zhí)行過程:

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 協(xié)議版本茅特。

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 規(guī)范中定義的所有 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 是統(tǒng)一資源標識符胚鸯,它標識要請求的資源。HTTP Request-URI 由協(xié)議方案拆融、主機名蠢琳、端口(可選)啊终、資源路徑镜豹、查詢條件(可選)和 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 的創(chuàng)建和修改蓝牲。

URI uri = new URIBuilder()
        .setScheme("http")
        .setHost("www.google.com")
        .setPath("/search")
        .setParameter("q", "httpclient")
        .setParameter("btnG", "Google Search")
        .setParameter("aq", "f")
        .setParameter("oq", "")
        .build();
HttpGet httpget = new HttpGet(uri);
System.out.println(httpget.getURI());

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 響應是服務器在接收并解析請求消息之后發(fā)送回客戶端的消息趟脂。該消息的第一行由協(xié)議版本、數(shù)字狀態(tài)代碼及其相關的文本短語組成例衍。

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 消息可以包含許多消息頭昔期,它們描述了消息屬性(如內(nèi)容長度、內(nèi)容類型等)佛玄。HttpClient 提供了檢索硼一、添加、刪除和枚舉消息頭的方法梦抢。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost");
response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"localhost\"");
Header h1 = response.getFirstHeader("Set-Cookie");
System.out.println(h1);
Header h2 = response.getLastHeader("Set-Cookie");
System.out.println(h2);
Header[] hs = response.getHeaders("Set-Cookie");
System.out.println(hs.length);

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 消息可以攜帶與請求或響應相關聯(lián)的內(nèi)容實體哼蛆。實體可以在某些請求和響應中找到,因為它們是可選的霞赫。使用實體的請求稱為 entity enclosing request腮介。HTTP 規(guī)范定義了兩個 entity enclosing request 的方法:POST 和 PUT。通常認為響應包含一個內(nèi)容實體端衰。這個規(guī)則也有例外叠洗,比如 HEAD 方法的響應和幾種常見的響應:204 No Content、304 Not Modified旅东、205 Reset Content灭抑。

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

HttpClient 根據(jù)內(nèi)容的來源將實體分為三種:

  • 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:內(nèi)容從流中接收,或即時生成的玉锌。尤其應注意的是名挥,這個類別也包括從 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:這些內(nèi)容在內(nèi)存中禀倔,或者 obtained by means that are independent from a connection or other entity榄融。self-contained 實體通常是可重復的。這種類型的實體主要用于封裝 HTTP 請求的實體救湖。

  • wrapping: The content is obtained from another entity.

wrapping:內(nèi)容從另一個實體獲得愧杯。

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 響應中輸出內(nèi)容時,這些區(qū)別對于連接管理非常重要鞋既。對于由應用程序創(chuàng)建且僅使用 HttpClient 發(fā)送的請求實體力九,streamed 和 self-contained 的區(qū)別并不重要。在這種情況下邑闺,建議將不可重復的實體視為 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)

一個實體可重復陡舅,這意味著它的內(nèi)容可以被多次讀取抵乓。這僅適用于 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).

由于實體既可以表示二進制內(nèi)容,也可以表示字符內(nèi)容靶衍,所以它支持字符編碼(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.

實體是在執(zhí)行 enclosed 內(nèi)容的請求,或請求成功時創(chuàng)建的颅眶,響應體用于將結果發(fā)送回客戶端蜈出。

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.

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

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() 可用于讀取常見的元數(shù)據(jù),如 Content-Type 和 Content-Length 頭信息(如果它們可用)沈自。由于 Content-Type 頭可以包含文本 mime-type(如 text/plain 或 text/html)的字符編碼酌儒,因此使用 HttpEntity#getContentEncoding() 方法來讀取此信息。如果頭不可用枯途,則返回長度 -1忌怎,內(nèi)容類型為 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.

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

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

為了確保系統(tǒng)資源正確釋放鸥印,必須關閉與實體關聯(lián)的內(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();
        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.

關閉內(nèi)容流和關閉響應之間的區(qū)別在于,前者使用實體內(nèi)容后库说,能保持底層連接狂鞋,而后者將立即關閉并丟棄連接。

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) 方法也需要確保在實體被完全寫入之后釋放系統(tǒng)資源骚揍。如果該方法通過調(diào)用 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) 方法來確保實體內(nèi)容已被完全消費,并且底層流已被關閉亡呵。

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.

但是抽活,當僅需要檢索整個響應內(nèi)容的一小部分,而且繼續(xù)消費剩余內(nèi)容或使連接可重用的性能代價太高政己,這時可以直接關閉響應來終止內(nèi)容流酌壕。

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
    HttpEntity entity = response.getEntity();
    if (entity != null) {
        InputStream instream = entity.getContent();
        int byteOne = instream.read();
        int byteTwo = instream.read();
        // Do not need the rest
    }
} finally {
    response.close();
}

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.

推薦消費實體內(nèi)容的方法是使用 HttpEntity#getContent() 或 HttpEntity#writeTo(OutputStream) 方法。HttpClient 還附帶了 EntityUtils 類果港,它公開了幾個靜態(tài)方法沦泌,以便更容易的從實體中讀取內(nèi)容或信息⌒谅樱可以直接使用這個類的方法在一個字符串或字節(jié)數(shù)組中檢索整個內(nèi)容主體谢谦,而不必讀取 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.

在某些情況下千劈,可能需要多次讀取實體內(nèi)容。在這種情況下牌捷,實體內(nèi)容必須以某種方式緩沖在內(nèi)存或磁盤墙牌。最簡單的方法是使用 BufferedHttpEntity 類包裝原始實體。這將導致原始實體的內(nèi)容被讀入內(nèi)存緩沖區(qū)暗甥。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 連接高效地輸出內(nèi)容喜滨。這些類的實例可以與諸如 POST 和 PUT 等 entity enclosing requests 的實體相關聯(lián),以便將實體內(nèi)容封裝到傳出 HTTP 請求中撤防。HttpClient 為最常見的數(shù)據(jù)容器提供了幾個類(如字符串虽风、字節(jié)數(shù)組、輸入流和文件):StringEntity、ByteArrayEntity辜膝、InputStreamEntity 和 FileEntity陌凳。

File file = new File("somefile.txt");
FileEntity entity = new FileEntity(file, ContentType.create("text/plain", "UTF-8"));

HttpPost httppost = new HttpPost("http://localhost/action.do");
httppost.setEntity(entity);

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 是不可重復的,因為它只能從底層數(shù)據(jù)流讀取一次内舟。通常情況下合敦,建議實現(xiàn)自定義的 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 應用程序或提交輸入數(shù)據(jù)崔梗。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 編碼對參數(shù)進行編碼垒在,并生成以下內(nèi)容:

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 根據(jù)要傳輸?shù)?HTTP 消息的屬性選擇最合適的傳輸編碼。但是场躯,可以通過將 HttpEntity#setChunked() 設置為 true 來通知 HttpClient 首選 Chunked 編碼谈为。請注意 HttpClient 將只使用此標志作為提示。當使用不支持 Chunked 編碼的 HTTP 協(xié)議版本(如 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 將確保連接釋放回連接管理器儒搭,無論請求執(zhí)行是否成功或?qū)е庐惓吠架!?/p>

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 請求執(zhí)行的最基本約定。它對請求執(zhí)行過程沒有任何限制或指明具體細節(jié)搂鲫,而將連接管理傍药、狀態(tài)管理、身份驗證和重定向處理的細節(jié)留給各個實現(xiàn)默穴。這讓使用附加功能(如響應內(nèi)容緩存)裝飾接口變得更容易怔檩。

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 實現(xiàn)充當許多專用處理程序或策略接口實現(xiàn)的基礎蓄诽,這些處理程序或策略接口實現(xiàn)負責處理 HTTP 協(xié)議的特定實現(xiàn)薛训,比如重定向或身份驗證處理,或者決定連接持久性和保持活動持續(xù)時間仑氛。這使用戶能夠選擇定制的乙埃、特定于應用程序的實現(xiàn)來替換默認實現(xiàn)闸英。

ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() {

    @Override
    public long getKeepAliveDuration(
            HttpResponse response,
            HttpContext context) {
        long keepAlive = super.getKeepAliveDuration(response, context);
        if (keepAlive == -1) {
            // Keep connections alive 5 seconds if a keep-alive value
            // has not be explicitly set by the server
            keepAlive = 5000;
        }
        return keepAlive;
    }

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

1.2.1 HttpClient 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 實現(xiàn)應該是線程安全的。建議在多個請求執(zhí)行中重用該類的同一個實例介袜。

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甫何,并且超出作用域時,必須通過調(diào)用 CloseableHttpClient#close() 方法關閉與它關聯(lián)的連接管理器遇伞。

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 被設計為一種無狀態(tài)的、面向響應請求的協(xié)議鸠珠。然而巍耗,應用程序常需要能夠通過幾個邏輯上相關的請求響應交換來持久化狀態(tài)信息。為了使應用程序能夠保持處理狀態(tài)渐排,HttpClient 允許在特定的執(zhí)行環(huán)境(稱為 HTTP 上下文)中執(zhí)行 HTTP 請求炬太。如果在連續(xù)的請求之間重用相同的執(zhí)行上下文,則多個邏輯相關的請求可以參與邏輯會話驯耻。HTTP 上下文的功能類似于 java.util.Map<String, Object>亲族。它只是任意命名值的集合。應用程序可以在請求執(zhí)行之前填充上下文屬性可缚,或者在執(zhí)行完成后檢查上下文霎迫。

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 可以包含任意對象,因此在多個線程之間共享可能是不安全的城看。建議每個執(zhí)行線程維護自己的上下文女气。

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

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

  • 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 請求。執(zhí)行上下文中最后一個 HttpRequest 對象始終表示消息發(fā)送到目標服務器時的狀態(tài)朝扼。HTTP/1.0 和 HTTP/1.1 都默認使用相對請求 URI赃阀。然而,如果請求以非隧道模式通過代理發(fā)送擎颖,則 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 對象用于標志實際請求是否已完全傳輸?shù)竭B接目標搂捧。

  • 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> 對象允跑,表示在請求執(zhí)行過程中接收到的所有重定向位置的集合王凑。

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

可以使用 HttpClientContext 適配器類來簡化與上下文狀態(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();

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 實例執(zhí)行,以確保會話上下文和狀態(tài)信息在請求之間自動傳播索烹。

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.

在下面的示例中工碾,初始請求設置的請求配置將保存在執(zhí)行上下文中,并傳播到共享相同上下文中的連續(xù)請求中百姓。

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 協(xié)議攔截器是實現(xiàn) HTTP 協(xié)議特定方面的一個程序渊额。通常,協(xié)議攔截器應作用于傳入消息的一個特定頭或一組相關頭垒拢,或者用一個特定頭或一組相關頭填充傳出消息旬迹。協(xié)議攔截器還可以操作包含在消息中的內(nèi)容實體(透明內(nèi)容壓縮 / 解壓就是一個很好的例子)。通常這是使用「裝飾者」模式來實現(xiàn)的子库,其中使用包裝器實體類來裝飾原始實體舱权。幾個協(xié)議攔截器可以組合成一個邏輯單元。

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.

協(xié)議攔截器可以通過 HTTP 執(zhí)行上下文共享信息(例如處理狀態(tài))進行協(xié)作仑嗅。協(xié)議攔截器可以使用 HTTP 上下文存儲一個請求或多個連續(xù)請求的處理狀態(tài)宴倍。

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.

通常脖律,只要攔截器不依賴于執(zhí)行上下文的特定狀態(tài)驱犹,攔截器執(zhí)行的順序就不重要。如果協(xié)議攔截器具有相互依賴關系屎媳,就必須按照特定的順序執(zhí)行脖捻,就應該按照預期的執(zhí)行順序?qū)⑺鼈兲砑拥絽f(xié)議處理器中阔逼。

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.

協(xié)議攔截器必須實現(xiàn)為線程安全的。與 servlet 類似地沮,協(xié)議攔截器不應該使用實例變量嗜浮,除非同步訪問這些變量。

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

這是一個說明如何使用本地上下文在連續(xù)請求之間持久化處理狀態(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 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 協(xié)議處理器可以拋出兩種類型的異常:在 I/O 失斈σ伞(如套接字超時或套接字重置)的情況下拋出 java.io.IOException危融,以及發(fā)出 HTTP 失敗(如違反 HTTP 協(xié)議)信號的 HttpException雷袋。通常吉殃,I/O 錯誤被認為是非致命的和可恢復的,而 HTTP 協(xié)議錯誤被認為是致命的楷怒,不能自動恢復蛋勺。請注意,HttpClient 實現(xiàn)將 HttpExceptions 重新拋出為 ClientProtocolException鸠删,這是 java.io.IOException 的子類抱完。這使得 HttpClient 的用戶能夠通過一個 catch 子句同時處理 I/O 錯誤拋出的異常和違反協(xié)議拋出的異常。

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 協(xié)議并不適合所有類型的應用程序冶共。HTTP 是一個簡單的面向請求 / 響應的協(xié)議乾蛤,最初設計它是為了支持靜態(tài)或動態(tài)生成的內(nèi)容檢索每界。它從來沒有打算支持事務操作。例如家卖,如果 HTTP 服務器成功地接收和處理了請求眨层、生成響應并將狀態(tài)代碼發(fā)送回客戶端,那么它將認為已經(jīng)完成了約定的一部分上荡。如果客戶端由于讀取超時趴樱、請求取消或系統(tǒng)崩潰而無法接收到完整的響應,服務器將不嘗試回滾事務酪捡。如果客戶端決定重試相同的請求叁征,服務器將不可避免地多次執(zhí)行相同的事務。在某些情況下逛薇,這可能導致應用程序數(shù)據(jù)損壞或應用程序狀態(tài)不一致捺疼。

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 從來沒有被設計成支持事務處理,但如果滿足某些條件永罚,它仍然可以作為任務關鍵型應用程序的傳輸協(xié)議啤呼。為了確保 HTTP 傳輸層的安全性,系統(tǒng)必須確保 HTTP 方法在應用層上的冪等性呢袱。

1.5.2 Idempotent methods

HTTP/1.1 specification defines an idempotent method as

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

[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]

方法還可以具有「冪等性」的屬性官扣,除了出現(xiàn)錯誤或過期的問題,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.

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

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 異常中自動恢復。默認的自動恢復機制僅限于少數(shù)已知安全的異常老速。

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

HttpClient 將不嘗試從任何邏輯或 HTTP 協(xié)議錯誤(源自 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 請求仍然傳輸?shù)侥繕朔掌鳎凑埱筮€沒有完全傳輸?shù)椒掌鳎r橘券,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 接口的實現(xiàn)锋华。

HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {

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

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

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

在某些情況下芜辕,由于目標服務器上的高負載或客戶端發(fā)出的并發(fā)請求太多尚骄,HTTP 請求執(zhí)行無法在預期的時間內(nèi)完成。在這種情況下侵续,可能需要提前終止請求并解除 I/O 操作中阻塞的執(zhí)行線程倔丈。通過調(diào)用 HttpUriRequest#abort() 方法,可以在執(zhí)行的任何階段中止由 HttpClient 執(zhí)行的 HTTP 請求询兴。該方法是線程安全的乃沙,可以從任何線程調(diào)用。當 HTTP 請求中止時诗舰,它的執(zhí)行線程(即使當前在 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 規(guī)范明確禁止用戶進行干預的除外眶根。根據(jù) HTTP 規(guī)范的要求蜀铲,POST 重定向(狀態(tài)碼 303)和 PUT 請求將被轉(zhuǎn)換為 GET 請求∈舭伲可以自定義重定向策略來放寬 HTTP 規(guī)范對 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 在執(zhí)行請求消息的過程中必須重寫請求消息。默認情況下族扰,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 教程目錄)

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市录豺,隨后出現(xiàn)的幾起案子朦肘,更是在濱河造成了極大的恐慌饭弓,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,835評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件媒抠,死亡現(xiàn)場離奇詭異弟断,居然都是意外死亡,警方通過查閱死者的電腦和手機领舰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,900評論 2 383
  • 文/潘曉璐 我一進店門夫嗓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人冲秽,你說我怎么就攤上這事舍咖。” “怎么了锉桑?”我有些...
    開封第一講書人閱讀 156,481評論 0 345
  • 文/不壞的土叔 我叫張陵排霉,是天一觀的道長。 經(jīng)常有香客問我民轴,道長攻柠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,303評論 1 282
  • 正文 為了忘掉前任后裸,我火速辦了婚禮瑰钮,結果婚禮上,老公的妹妹穿的比我還像新娘微驶。我一直安慰自己浪谴,他們只是感情好,可當我...
    茶點故事閱讀 65,375評論 5 384
  • 文/花漫 我一把揭開白布因苹。 她就那樣靜靜地躺著苟耻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扶檐。 梳的紋絲不亂的頭發(fā)上凶杖,一...
    開封第一講書人閱讀 49,729評論 1 289
  • 那天,我揣著相機與錄音款筑,去河邊找鬼智蝠。 笑死,一個胖子當著我的面吹牛奈梳,可吹牛的內(nèi)容都是我干的寻咒。 我是一名探鬼主播,決...
    沈念sama閱讀 38,877評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼颈嚼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了饭寺?” 一聲冷哼從身側響起阻课,我...
    開封第一講書人閱讀 37,633評論 0 266
  • 序言:老撾萬榮一對情侶失蹤叫挟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后限煞,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抹恳,經(jīng)...
    沈念sama閱讀 44,088評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,443評論 2 326
  • 正文 我和宋清朗相戀三年署驻,在試婚紗的時候發(fā)現(xiàn)自己被綠了奋献。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,563評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡旺上,死狀恐怖瓶蚂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宣吱,我是刑警寧澤窃这,帶...
    沈念sama閱讀 34,251評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站征候,受9級特大地震影響杭攻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疤坝,卻給世界環(huán)境...
    茶點故事閱讀 39,827評論 3 312
  • 文/蒙蒙 一兆解、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧跑揉,春花似錦锅睛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,712評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至扮饶,卻和暖如春具练,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背甜无。 一陣腳步聲響...
    開封第一講書人閱讀 31,943評論 1 264
  • 我被黑心中介騙來泰國打工扛点, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人岂丘。 一個月前我還...
    沈念sama閱讀 46,240評論 2 360
  • 正文 我出身青樓陵究,卻偏偏與公主長得像,于是被迫代替她去往敵國和親奥帘。 傳聞我的和親對象是個殘疾皇子铜邮,可洞房花燭夜當晚...
    茶點故事閱讀 43,435評論 2 348

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