第一章基础知识
英文链接:http://hc.apache.org/httpcomponents-client-ga/tutorial/html/
1.1.请求执行
HttpClient最重要的功能是执行HTTP方法。执行HTTP方法涉及一个或多个HTTP请求/ HTTP响应交换,通常由HttpClient内部处理。用户期望提供一个请求对象来执行,并且希望HttpClient将请求发送到目标服务器返回相应的响应对象,如果执行失败则抛出异常。
很自然,HttpClient API的主要入口点是定义上述合同的HttpClient接口。
以下是最简单的请求执行过程的一个例子:
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
public class HttpClientDemo01 {
public static void main(String[] args) throws Exception {
//实例化httpclient 对象
CloseableHttpClient httpclient = HttpClients.createDefault();
//实例化 httpget 对象
HttpGet httpget = new HttpGet("https://www.baidu.com/");
//执行get的请求
CloseableHttpResponse response = httpclient.execute(httpget);
//打印输出返回的实体
System.out.println(response);
response.close();
}
}
运行结果为:
HttpResponseProxy{HTTP/1.1 200 OK [Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform, Connection: Keep-Alive, Content-Type: text/html, Date: Wed, 30 Aug 2017 08:11:27 GMT, Last-Modified: Mon, 23 Jan 2017 13:24:18 GMT, Pragma: no-cache, Server: bfe/1.0.8.18, Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/, Transfer-Encoding: chunked] org.apache.http.client.entity.DecompressingEntity@46238e3f}
1.1.HTTP请求
所有HTTP请求都有一个请求行,包括方法名称,请求URI和HTTP协议版本。
HttpClient的支持了在HTTP / 1.1规范中定义的所有HTTP方法的框的:GET,HEAD,POST,PUT,DELETE,TRACE和OPTIONS。没有为每个方法类型:一个特定的类HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace,和HttpOptions。
Request-URI是统一资源标识符,用于标识应用该请求的资源。HTTP请求URI由协议方案,主机名,可选端口,资源路径,可选查询和可选片段组成。
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());
stdout>
http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=
1.1.2.HTTP响应
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.处理消息头
HTTP消息可以包含描述消息属性的多个头部,如内容长度,内容类型等。HttpClient提供检索,添加,删除和枚举头文件的方法。
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.message.BasicHttpResponse;
public class httpunitDemo01 {
public static void main(String[] args) {
//实例化一个基本的请求
//从元素的状态行创建响应。
//响应不会有一个原因短语目录和使用系统默认区域设置。
//HttpVersion.HTTP_1_1 代表一个HTTP版本 HttpStatus.SC_OK 常量枚举的 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\"");
//获取其名称属性第一个标头
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
获取指定类型的所有标头的最有效的方法是使用HeaderIterator
接口。
import org.apache.http.*;
import org.apache.http.message.BasicHttpResponse;
public class httpunitDemo01 {
public static void main(String[] args) {
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"
它还提供了方便的方法来将HTTP消息解析为单独的头元素。
import org.apache.http.*;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicHttpResponse;
public class httpunitDemo01 {
public static void main(String[] args) {
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实体
HTTP消息可以携带与请求或响应相关联的内容实体。实体可以在一些请求和一些响应中找到,因为它们是可选的。使用实体的请求被称为实体封装请求。HTTP规范定义了两个实体封装请求方法:POST
和PUT
。响应通常期望包含内容实体。有例外的情况,如应对HEAD
方法204 No Content
,304 Not Modified
,205 Reset Content
响应。
HttpClient根据其内容来源区分三种实体:
-
流式传输: 内容是从流中接收到的,或者即时生成。特别地,该类别包括从HTTP响应接收到的实体。流式实体通常不可重复。
-
自包含: 内容在内存中或通过独立于连接或其他实体的方式获取。自包含的实体通常是可重复的。这种类型的实体将主要用于实体封装HTTP请求。
-
包装: 内容是从另一个实体获得的。
当从HTTP响应流出内容时,此区别对于连接管理很重要。对于由应用程序创建并且仅使用HttpClient发送的请求实体,流和独立的区别不重要。在这种情况下,建议将不可重复的实体视为流式传输,将可重复的实体视为独立的。
1.1.4.1.可重复的实体
实体可以是可重复的,这意味着其内容可以被多次读取。这是唯一可能与自包含的实体(如ByteArrayEntity
或StringEntity
)
1.1.4.2.使用HTTP实体
由于实体可以表示二进制和字符内容,因此它支持字符编码(以支持后者,即字符内容)。
当执行带有封闭内容的请求或请求成功时,创建实体,并使用响应主体将结果发送回客户端。
要从实体读取内容,可以通过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()
方法用于读取此信息。如果标题不可用,则返回长度为-1,内容类型为NULL。如果Content-Type
标题可用,Header
将返回一个对象。
当为外发消息创建实体时,该元数据必须由实体的创建者提供。
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
public class httpunitDemo01 {
public static void main(String[] args) throws IOException {
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.确保发布低级别资源
为了确保系统资源的正确释放,必须关闭与实体或响应本身相关联的内容流
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import java.io.IOException;
import java.io.InputStream;
public class httpunitDemo01 {
public static void main(String[] args) throws IOException {
//实例化httpclient 对象 创建默认配置
CloseableHttpClient httpclient = HttpClients.createDefault();
//实例化httpGet 对象,创建一个http的get 方法
HttpGet httpget = new HttpGet("https://www.baidu.com/");
//执行请求
CloseableHttpResponse response = httpclient.execute(httpget);
try {
//返回实体
HttpEntity entity = response.getEntity();
//判断entity 是否为空
if (entity != null) {
//返回一个实体的内容流
InputStream instream = entity.getContent();
try {
// do something useful
} finally {
//关闭内容流
//尝试通过占用实体内容来保持底层连接
instream.close();
}
}
} finally {
//关闭响应
//立即关闭并丢弃连接
response.close();
}
}
}
关闭内容流和关闭响应之间的区别是,前者将尝试通过占用实体内容来保持底层连接,而后者立即关闭并丢弃连接。
请注意,HttpEntit.writeTo(OutputStream)
一旦实体完全写出,还需要确保正确释放系统资源的方法。如果此方法获取一个java.io.InputStream
通过调用的实例HttpEntity.getContent()
,那么也希望在finally子句中关闭流。
当使用流实体时,可以使用该EntityUtils.consume(HttpEntity)
方法来确保实体内容已被完全消耗,底层流已经被关闭。
然而,可能会有情况,当只需要检索整个响应内容的一小部分,并且消耗剩余内容并使连接可重用的性能损失太高,在这种情况下,可以通过关闭终止内容流响应。
import org.apache.http.*;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import java.io.IOException;
import java.io.InputStream;
public class httpunitDemo01 {
public static void main(String[] args) throws IOException {
//实例化httpclient 对象 创建默认配置
CloseableHttpClient httpclient = HttpClients.createDefault();
//实例化httpGet 对象,创建一个http的get 方法
HttpGet httpget = new HttpGet("https://www.baidu.com/");
//执行请求 响应对象生成的响应处理程序。
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();
}
}
}
连接不会重复使用,但由其持有的所有级别资源将被正确地分配。
1.1.6.消费实体内容
消费实体内容的推荐方法是使用其HttpEntity.getContent()
或HttpEntity.writeTo(OutputStream)
方法。HttpClient还附带了EntityUtils
类,它暴露了几种静态方法,以便更容易地从实体读取内容或信息。java.io.InputStream
可以通过使用此类的方法,而不是直接读取,而不是直接读取字符串/字节数组中的整个内容正文。但是,EntityUtils
除非响应实体来自受信任的HTTP服务器,并且已知其长度有限,否则强烈建议不要使用此功能。
import org.apache.http.*;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
public class httpunitDemo01 {
public static void main(String[] args) throws IOException {
//实例化httpclient 对象 创建默认配置
CloseableHttpClient httpclient = HttpClients.createDefault();
//实例化httpGet 对象,创建一个http的get 方法
HttpGet httpget = new HttpGet("https://www.baidu.com/");
//执行请求 响应对象生成的响应处理程序。
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
类包装原始实体。这将导致将原始实体的内容读入内存缓冲区。在所有其他方式,实体包装器将具有原始包装器。
import org.apache.http.*;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import java.io.IOException;
public class httpunitDemo01 {
public static void main(String[] args) throws IOException {
//实例化httpclient 对象 创建默认配置
CloseableHttpClient httpclient = HttpClients.createDefault();
//实例化httpGet 对象,创建一个http的get 方法
HttpGet httpget = new HttpGet("https://www.baidu.com/");
//执行请求 响应对象生成的响应处理程序。
CloseableHttpResponse response = httpclient.execute(httpget);
try {
返回实体
HttpEntity entity = response.getEntity();
//判断返回实体是否为空
if (entity != null) {
//创建一个新的实体包装缓冲。
entity = new BufferedHttpEntity(entity);
}
} finally {
//关闭响应
//立即关闭并丢弃连接
response.close();
}
}
}
1.1.7.制作实体内容
HttpClient提供了几个类,可以通过HTTP连接高效地流出内容。这些类的实例可以与实体包围请求,如相关联POST
并PUT
以包围实体内容分成传出HTTP请求。HttpClient的提供了几个类为最常见的数据的容器,如字符串,字节数组,输入流,和文件:StringEntity
,ByteArrayEntity
,InputStreamEntity
,和FileEntity
。
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.FileEntity;
import java.io.File;
import java.io.IOException;
public class httpunitDemo01 {
public static void main(String[] args) throws IOException {
//创建一个新的文件
File file = new File("somefile.txt");
//一个自我包含的、可重复的实体,从一个文件中获得其内容。
FileEntity entity = new FileEntity(file, ContentType.create("text/plain", "UTF-8"));
//创建一个httpPost 方法
HttpPost httppost = new HttpPost("http://localhost/action.do");
//一个实体的基本实现封闭HTTP请求,
httppost.setEntity(entity);
}
}
请注意InputStreamEntity
不可重复,因为它只能从基础数据流读取一次。通常建议实现一个自HttpEntity
包含的自定义类,而不是使用泛型InputStreamEntity
。FileEntity
可以是一个很好的起点。
1.1.7.1.HTML表单
许多应用程序需要模拟提交HTML表单的过程,例如,为了登录到Web应用程序或提交输入数据。HttpClient提供实体类UrlEncodedFormEntity
以方便进程。
import org.apache.http.Consts;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class httpunitDemo01 {
public static void main(String[] args) throws IOException {
//NameValuePair 一个名称/值对参数用作HTTP消息的一个元素。
//新建一个数组
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
//BasicNameValuePair 默认的构造函数接受一个名称和一个值。可能是null值。
formparams.add(new BasicNameValuePair("param1", "value1"));
formparams.add(new BasicNameValuePair("param2", "value2"));
//构造一个新的{UrlEncodedFormEntity }指定编码的参数列表。
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
//新建一个post 请求指定的URi
HttpPost httppost = new HttpPost("http://localhost/handler.do");
//设置实体
httppost.setEntity(entity);
}
}
本UrlEncodedFormEntity
实例将使用所谓的URL编码参数进行编码并生成以下内容:
param1=value1¶m2=value2
1.1.7.2.内容分块
一般建议让HttpClient根据正在传输的HTTP消息的属性选择最合适的传输编码。然而,可以通知HttpClient,通过设置HttpEntity.setChunked()
为true,优先选择块编码。请注意,HttpClient只会使用此标志作为提示。当使用不支持块编码的HTTP协议版本(如HTTP / 1.0)时,此值将被忽略。
import org.apache.http.Consts;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import java.io.IOException;
public class httpunitDemo01 {
public static void main(String[] args) throws IOException {
//创建一个StringEntity与指定的内容和内容类型。
StringEntity entity = new StringEntity("important message", ContentType.create("plain/text", Consts.UTF_8));
//优先选择块编码
entity.setChunked(true);
//新建一个post 请求指定的URi
HttpPost httppost = new HttpPost("http://localhost/handler.do");
//设置实体
httppost.setEntity(entity);
}
}
1.1.8.响应处理程序
处理响应的最简单和最方便的方法是使用ResponseHandler
包含该handleResponse(HttpResponse response)
方法的界面。这种方法可以完全避免用户不必担心连接管理。使用ResponseHandler
HttpClient 时,无论请求执行是成功还是导致异常,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界面
HttpClient
接口代表HTTP请求执行最基本的合同。它对请求执行过程没有施加任何限制或特定细节,并将连接管理,状态管理,身份验证和重定向处理的具体细节留给各个实现。这应该使得更容易使用附加功能(如响应内容缓存)来装饰接口。
一般来说,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线程安全
HttpClient
实现预期是线程安全的。建议将此类的同一个实例重用于多个请求执行。
1.2.2。HttpClient资源释放
当一个实例CloseableHttpClient
不再需要并且即将超出范围时,与它关联的连接管理器必须通过调用该CloseableHttpClient#close()
方法来关闭。
CloseableHttpClient httpclient = HttpClients.createDefault();
try {
<...>
} finally {
httpclient.close();
}
1.3。HTTP执行上下文
最初HTTP被设计为无状态的,响应请求的协议。然而,现实世界的应用程序通常需要通过几个逻辑相关的请求 - 响应交换来保持状态信息。为了使应用程序能够保持处理状态HttpClient允许在特定执行上下文(称为HTTP上下文)中执行HTTP请求。如果在连续请求之间重复使用相同的上下文,则多个逻辑相关请求可以参与逻辑会话。HTTP上下文功能类似于a java.util.Map<String, Object>
。它只是一个任意命名值的集合。应用程序可以在请求执行之前填充上下文属性,或在执行完成后检查上下文。
HttpContext
可以包含任意对象,因此可能不安全地在多个线程之间共享。建议每个执行线程都维护自己的上下文。
在HTTP请求执行过程中,HttpClient将以下属性添加到执行上下文中:
-
HttpConnection
表示与目标服务器的实际连接的实例。 -
HttpHost
表示连接目标的实例。 -
HttpRoute
表示完整连接路由的实例 -
HttpRequest
表示实际的HTTP请求。执行上下文中的最终HttpRequest对象总是表示消息的状态与发送到目标服务器的状态完全相同。默认HTTP / 1.0和HTTP / 1.1使用相对请求URI。但是,如果请求是通过代理在非隧道模式下发送的,则URI将是绝对的。 -
HttpResponse
表示实际的HTTP响应。 -
java.lang.Boolean
表示表示实际请求是否被完全发送到连接目标的标志的对象。 -
RequestConfig
表示实际请求配置的对象。 -
java.util.List<URI>
表示在请求执行过程中接收到的所有重定向位置的集合的对象。
可以使用HttpClientContext
适配器类来简化与上下文状态的分离。
HttpContext context = <...>
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpHost target = clientContext.getTargetHost();
HttpRequest request = clientContext.getRequest();
HttpResponse response = clientContext.getResponse();
RequestConfig config = clientContext.getRequestConfig();
表示逻辑相关会话的多个请求序列应该使用相同的HttpContext
实例来执行,以确保在请求之间自动传播会话上下文和状态信息。
在以下示例中,由初始请求设置的请求配置将保留在执行上下文中,并被传播到共享相同上下文的连续请求。
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协议拦截器
HTTP协议拦截器是一个实现HTTP协议特定方面的例程。通常,协议拦截器预期作用于输入消息的一个特定头部或一组相关头部,或者使用一个特定头部或一组相关头部填充输出消息。协议拦截器还可以操纵包含消息的内容实体 - 透明内容压缩/解压缩就是一个很好的例子。通常这是通过使用“装饰器”模式来实现的,其中使用包装器实体类来装饰原始实体。几个协议拦截器可以组合形成一个逻辑单元。
协议拦截器可以通过HTTP执行上下文共享信息(如处理状态)进行协作。协议拦截器可以使用HTTP上下文来存储一个请求或多个连续请求的处理状态。
通常执行拦截器的顺序不要紧,因为它们不依赖于执行上下文的特定状态。如果协议拦截器具有相互依赖关系,因此必须以特定顺序执行,则应将它们按照与其预期执行顺序相同的顺序添加到协议处理器。
协议拦截器必须实现为线程安全。与servlet类似,协议拦截器不应使用实例变量,除非对这些变量的访问是同步的。
这是一个例子,说明如何使用本地上下文来持续连续请求之间的处理状态:
CloseableHttpClient httpclient = HttpClients.custom()
.addInterceptorLast(new HttpRequestInterceptor() {
public void process(
final HttpRequest request,
final HttpContext context) throws HttpException, IOException {
AtomicInteger count = (AtomicInteger) context.getAttribute("count");
request.addHeader("Count", Integer.toString(count.getAndIncrement()));
}
})
.build();
AtomicInteger count = new AtomicInteger(1);
HttpClientContext localContext = HttpClientContext.create();
localContext.setAttribute("count", count);
HttpGet httpget = new HttpGet("http://localhost/");
for (int i = 0; i < 10; i++) {
CloseableHttpResponse response = httpclient.execute(httpget, localContext);
try {
HttpEntity entity = response.getEntity();
} finally {
response.close();
}
}
1.5。异常处理
HTTP协议处理器可以抛出两种类型的异常:java.io.IOException
在I / O故障(例如套接字超时或套接字复位)的情况下,并HttpException
发出HTTP故障(例如违反HTTP协议)。通常I / O错误被认为是非致命和可恢复的,而HTTP协议错误被认为是致命的,不能自动恢复。请注意,HttpClient
实现重新抛出HttpException
s ClientProtocolException
,它是一个子类java.io.IOException
。这使用户能够HttpClient
从单个catch子句处理I / O错误和协议违规。
1.5.1。HTTP运输安全
重要的是要了解HTTP协议不是很适合所有类型的应用程序。HTTP是一种简单的面向请求/响应的协议,最初被设计为支持静态或动态生成的内容检索。它从来没有意图支持交易操作。例如,如果HTTP服务器成功接收和处理请求,生成响应并将状态代码发送回客户端,则HTTP服务器将考虑其部分合同。如果客户端由于读取超时,请求取消或系统崩溃而无法全部收到响应,服务器将不会尝试回滚事务。如果客户端决定重试相同的请求,服务器将不可避免地最终不止一次地执行相同的事务。
即使HTTP从未被设计为支持事务处理,但如果满足某些条件,它仍然可以用作任务关键应用程序的传输协议。为了确保HTTP传输层安全,系统必须确保应用层上的HTTP方法的等效性。
1.5.2。幂等方法
HTTP / 1.1规范定义了一种幂等方法
[ 方法也可以具有“幂等”的属性(除了错误或到期问题),N> 0个相同请求的副作用与单个请求相同)
换句话说,应用程序应该确保它准备好处理多个执行相同方法的含义。这可以通过例如提供唯一的事务ID和避免执行相同逻辑操作的其他手段来实现。
请注意,这个问题不是HttpClient特有的。基于浏览器的应用程序受到与HTTP方法非幂等性相关的完全相同的问题。
默认情况下,HttpClient仅假定非实体封闭方法,例如GET
并且HEAD
是幂等和实体封闭方法,POST
并且PUT
不是出于兼容性原因。
1.5.3。自动异常恢复
默认情况下,HttpClient尝试自动从I / O异常恢复。默认的自动恢复机制仅限于一些已知是安全的异常。
-
HttpClient将不会尝试从任何逻辑或HTTP协议错误(派生自
HttpException
类)中恢复。 -
HttpClient会自动重试那些假定为幂等的方法。
-
当HTTP请求仍被传送到目标服务器(即请求尚未完全传输到服务器)时,HttpClient将自动重试那些失败的传输异常的方法。
1.5.4。请求重试处理程序
为了启用自定义异常恢复机制,应该提供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();
请注意,可以使用StandardHttpRequestRetryHandler
替代默认使用的一个,以治疗由RFC-2616定义为幂的安全要求的那些方法来自动重试:GET
,HEAD
,PUT
,DELETE
, OPTIONS
,和TRACE
。
1.6。中止请求
在某些情况下,由于目标服务器的负载过高或客户端发出的并发请求太多,HTTP请求执行在预期的时间内无法完成。在这种情况下,可能需要提前终止请求,并在I / O操作中解除阻塞执行线程。HttpClient执行的HTTP请求可以通过调用HttpUriRequest#abort()
方法在执行的任何阶段中止。这种方法是线程安全的,可以从任何线程调用。当一个HTTP请求被中止时,它的执行线程 - 即使当前在I / O操作中被阻塞 - 也被保证通过抛出一个InterruptedIOException
1.7。重定向处理
HttpClient会自动处理所有类型的重定向,除了要求用户干预之外由HTTP规范明确禁止的重定向。See Other
(状态码303)重定向上POST
和PUT
请求转换为GET
所要求的HTTP规范的请求。可以使用自定义重定向策略来放宽对HTTP规范强加的POST方法的自动重定向的限制。
LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
CloseableHttpClient httpclient = HttpClients.custom()
.setRedirectStrategy(redirectStrategy)
.build();
HttpClient经常在其执行过程中重写请求消息。默认情况下,HTTP / 1.0和HTTP / 1.1通常使用相对请求URI。同样,原始请求可能会从位置重定向到另一次。最终解释的绝对HTTP位置可以使用原始请求和上下文构建。该实用程序方法URIUtils#resolve
可用于构建用于生成最终请求的解释的绝对URI。该方法包括来自重定向请求或原始请求的最后一个片段标识符。
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();
}