Preface
尽管java.net包提供了通过HTTP访问资源的基本功能,但它并未提供许多应用程序所需的灵活性或功能性。
HttpClient旨在通过提供一个高效,最新且功能丰富的软件包来实现这一空白,该软件包实现了最新HTTP标准和建议的客户端。
HttpClient专为扩展而设计,同时为基本HTTP协议提供强大支持。
HttpClient scope(HttpClient范围)
- 基于HttpCore的客户端HTTP传输库
- 基于经典的阻塞式IO
- 操作透明,使用用户无感
What HttpClient is
- HttpClient不是一个浏览器,他是客户端的Http传输库。
- HttpClient的目的是传输和接收Http信息。
- HttpClient不会尝试处理内容、执行嵌入HTML中的js、在没有明确设置的情况下猜测内容类型、重新格式化请求、重写位置URI
Fundamentals(基本原理)
Request execution(请求执行)
HttpClient最重要的功能是执行HTTP方法。
执行HTTP方法涉及一个或多个HTTP请求/ HTTP响应交换,通常由HttpClient内部处理。期望用户提供要执行的请求对象,并且HttpClient期望将请求发送到目标服务器返回相应的响应对象,或者如果执行不成功则抛出异常。
1 | CloseableHttpClient httpclient = HttpClients.createDefault(); |
HTTP request(请求行)
所有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由协议方案,主机名,可选端口,资源路径,可选查询和可选片段组成。
1 | HttpGet httpget = new HttpGet( |
HttpClient提供了URIBuilder实体类,以简化请求URI的创建和修改。
1 | URI uri = new URIBuilder() |
HTTP response(响应行)
HTTP响应是服务器在接收并解释请求消息后发送回客户端的消息。
该消息的第一行包括协议版本,后跟数字状态代码及其相关的文本短语。
1 | HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, |
Working with message headers(请求头)
HTTP消息可以包含许多描述消息属性的标题,例如内容长度,内容类型等。
HttpClient提供了检索,添加,删除和枚举标头的方法。
1 | HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, |
获取给定类型的所有标头的最有效方法是使用HeaderIterator接口。
1 | HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, |
它还提供了将HTTP消息解析为单个头元素的便捷方法。
1 | HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, |
HTTP entity(请求体)
HTTP消息可以携带与请求或响应相关联的内容实体。
实体可以在某些请求和某些响应中找到,对于某些方法实体是非必传的内容,使用实体的请求称为封闭请求的实体。
HTTP规范定义了两个封闭请求方法的实体:POST和PUT,通常期望响应包含内容实体。此规则有例外,例如对HEAD方法的响应、204 No Content、304 Not Modified、205 Reset Content响应。
HttpClient 根据内容的来源区分三种实体:
- streamed:内容从流中接收,或在运行中生成。特别是此类别包括从HTTP响应接收的实体。流实体通常不可重复。
- self-contained:内容在内存中或通过独立于连接或其他实体的方式获得。自包含实体通常是可重复的。这种类型的实体主要用于封闭HTTP请求的实体。
- wrapping:内容从另一个实体获得
当从HTTP响应中流出内容时,这种区别对于连接管理很重要。对于由应用程序创建并仅使用HttpClient发送的请求实体,流式和自包含之间的差异并不重要。在这种情况下,建议将不可重复的实体视为流式传输,将那些可重复的实体视为自包含的。Repeatable entities(可重复实体)
实体可以是可重复的,这意味着其内容可以被多次读取。这仅适用于自包含实体(如ByteArrayEntity或StringEntity)Using HTTP entities(使用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对象。sout1
2
3
4
5
6
7StringEntity 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);1
2
3
4Content-Type: text/plain; charset=utf-8
17
important message
17Ensuring release of low level resources(确保释法低级资源)
为了确保正确释放系统资源,必须关闭与实体关联的内容流或响应本身关闭内容流和关闭响应之间的区别在于前者将尝试通过使用实体内容来保持底层连接处于活动状态,而后者立即关闭并丢弃连接。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16CloseableHttpClient 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();
}
请注意,一旦实体完全写出,还需要HttpEntity#writeTo(OutputStream)方法来确保正确释放系统资源。
如果此方法通过调用HttpEntity#getContent()获取java.io.InputStream的实例,则还应该在finally子句中关闭该流。
使用流实体时,可以使用EntityUtils#consume(HttpEntity)方法确保实体内容已完全消耗且基础流已关闭。
然而,可能存在这样的情况:当只需要复用整个响应内容的一小部分并且消耗剩余内容并使连接可重用时的性能损失太高,在这种情况下,可以通过关闭响应来终止内容流。
。1
2
3
4
5
6
7
8
9
10
11
12
13
14CloseableHttpClient 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();
}连接不会被重用,但它所拥有的所有级别资源都将被正确释放。
Consuming entity content(消费内容实体)
消费实体内容的推荐方法是使用其HttpEntity#getContent()或HttpEntity#writeTo(OutputStream)方法。
HttpClient还附带了EntityUtils类,它公开了几种静态方法,以便更容易地从实体中读取内容或信息。
可以使用此类中的方法检索字符串/字节数组中的整个内容主体,而不是直接读取java.io.InputStream。
但是,强烈建议不要使用EntityUtils,除非响应实体来自可信HTTP服务器并且已知长度有限。在某些情况下,可能需要能够多次读取实体内容。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
long len = entity.getContentLength();
if (len != -1 && len < 2048) {
System.out.println(EntityUtils.toString(entity));
} else {
// Stream content out
}
}
} finally {
response.close();
}
在这种情况下,实体内容必须以某种方式缓冲,无论是在内存中还是在磁盘上。
实现这一目标的最简单方法是使用BufferedHttpEntity类包装原始实体。
这将导致原始实体的内容被读入内存缓冲区。
在所有其他方式中,实体包装器将具有原始包装器。1
2
3
4
5CloseableHttpResponse response = <...>
HttpEntity entity = response.getEntity();
if (entity != null) {
entity = new BufferedHttpEntity(entity);
}Producing entity content(生产内容实体)
HttpClient提供了几个类,可用于通过HTTP连接有效地流出内容。
这些类的实例可以与封闭请求(如POST和PUT)的实体相关联,以便将实体内容封装到传出的HTTP请求中。
HttpClient为大多数常见数据容器提供了几个类,如字符串,字节数组,输入流和文件:StringEntity,ByteArrayEntity,InputStreamEntity和FileEntity。1
2
3
4
5
6File 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);请注意,InputStreamEntity不可重复,因为它只能从基础数据流中读取一次。
通常,建议实现自定义的HttpEntity类,而不是使用通用的InputStreamEntity。
FileEntity可以是一个很好的起点。HTML forms(表单)
许多应用程序需要模拟提交HTML表单的过程,以便登录Web应用程序或提交输入数据。
HttpClient提供实体类UrlEncodedFormEntity以方便该过程。UrlEncodedFormEntity实例将使用所谓的URL编码对参数进行编码并生成以下内容:1
2
3
4
5
6List<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);1
param1=value1¶m2=value2
Content chunking(内容分块)
建议让HttpClient根据要传输的HTTP消息的属性选择最合适的传输编码。
但是,通过将HttpEntity#setChunked()设置为true,可以通知HttpClient优先使用块编码。
请注意,HttpClient将仅使用此标志作为提示。
使用不支持块编码的HTTP协议版本(例如HTTP / 1.0)时,将忽略此值。
Response handlers(响应处理程序)
处理响应的最简单和最方便的方法是使用ResponseHandler接口,该接口包含handleResponse(HttpResponse响应)方法。
该方法完全使用户不必担心连接管理。
使用ResponseHandler时,无论请求执行成功还是导致异常,HttpClient都会自动确保将连接释放回连接管理器。
1 | CloseableHttpClient httpclient = HttpClients.createDefault(); |
HttpClient interface
HttpClient接口代表HTTP请求执行的最重要的契约。它对请求执行过程没有任何限制或特定细节,并且将连接管理,状态管理,身份验证和重定向处理的细节留给单个实现。
这应该可以更容易地使用响应内容缓存等附加功能来修饰界面。
通常,HttpClient实现充当许多专用处理程序或策略接口实现的外观,负责处理HTTP协议的特定方面,例如重定向或认证处理或决定连接持久性和保持活动持续时间。
这使用户能够有选择地用自定义替换应用程序特定的这些方面的默认实现。
1 | ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() { |
HttpClient thread safety(HttpClient线程安全)
HttpClient实现应该是线程安全的。建议将此类的同一实例重用于多个请求执行。
HttpClient resource deallocation(资源释放)
当不再需要实例CloseableHttpClient并且即将超出范围时,必须通过调用CloseableHttpClient #close()方法关闭与其关联的连接管理器。
1 | CloseableHttpClient httpclient = HttpClients.createDefault(); |
HTTP execution context(HTTP执行上下文)
最初,HTTP被设计为无状态,面向响应请求的协议。
但是,真实世界的应用程序通常需要能够通过几个逻辑上相关的请求 - 响应交换来持久保存状态信息。
为了使应用程序能够维持处理状态,HttpClient允许在特定的执行上下文中执行HTTP请求,称为HTTP上下文。
如果在连续请求之间重用相同的上下文,则多个逻辑相关的请求可以参与逻辑会话。
HTTP上下文函数与java.util.Map <String,Object>类似。
它只是一组任意命名值。
应用程序可以在请求执行之前填充上下文属性,也可以在执行完成后检查上下文。
HttpContext可以包含任意对象,因此在多个线程之间共享可能不安全。
建议每个执行线程都维护自己的上下文。
在HTTP请求执行过程中,HttpClient将以下属性添加到执行上下文中:
- HttpConnection实例表示与目标服务器的实际连接。
- HttpHost实例表示目标的连接
- HttpRoute实例表示完整的连接路由
- 表示实际HTTP请求的HttpRequest实例。
执行上下文中的最终HttpRequest对象始终表示消息的状态与发送到目标服务器的状态完全相同。
默认HTTP / 1.0和HTTP / 1.1使用相对请求URI。
但是,如果请求是通过代理以非隧道模式发送的,那么URI将是绝对的。 - HttpResponse实例表示实际的HTTP响应。
- java.lang.Boolean对象,表示指示实际请求是否已完全传输到连接目标的标志。
- RequestConfig对象表示实际的请求配置。
- java.util.List
对象,表示在请求执行过程中收到的所有重定向位置的集合。
可以使用HttpClientContext适配器类来简化与上下文状态的交互。
1 | HttpContext context = <...> |
应使用相同的HttpContext实例执行表示逻辑相关会话的多个请求序列,以确保在请求之间自动传播会话上下文和状态信息。
在以下示例中,初始请求设置的请求配置将保留在执行上下文中,并传播到共享相同上下文的连续请求。
1 | CloseableHttpClient httpclient = HttpClients.createDefault(); |
HTTP protocol interceptors(HTTP协议拦截器)
HTTP协议拦截器是一个常规实现了Http协议的具体切面。通常,期望协议拦截器作用于传入传出消息的一个特定报头或一组相关报头。协议拦截器也可以操纵用消息附带的内容实体 - 透明内容压缩/解压缩就是一个很好的例子。
通常这是通过使用’Decorator’模式来完成的,其中包装器实体类用于装饰原始实体。
可以组合几个协议拦截器以形成一个逻辑单元。
协议拦截器可以通过HTTP执行上下文共享信息(例如处理状态)来协作。
协议拦截器可以使用HTTP上下文来存储一个请求或多个连续请求的处理状态。
通常,执行拦截器的顺序无关紧要,只要它们不依赖于执行上下文的特定状态即可。
如果协议拦截器具有相互依赖性,因此必须按特定顺序执行,则应按照与预期执行顺序相同的顺序将它们添加到协议处理器中。
协议拦截器必须实现为线程安全的。与servlet类似,协议拦截器不应使用实例变量,除非同步访问这些变量。
这是一个如何使用本地上下文在连续请求之间保持处理状态的示例:
1 | CloseableHttpClient httpclient = HttpClients.custom() |
Exception handling(异常处理)
HTTP协议处理器可以抛出两种类型的异常:在I / O故障(如套接字超时或套接字重置)和HttpException的情况下抛出java.io.IOException,表示HTTP故障(例如违反HTTP协议)。
通常,I / O错误被认为是非致命和可恢复的,而HTTP协议错误被认为是致命的,无法自动恢复。
请注意,HttpClient实现将HttpExceptions重新抛出为ClientProtocolException,它是java.io.IOException的子类。
这使HttpClient的用户能够从单个catch子句处理I / O错误和协议违规
HTTP transport safety(Http传输安全)
重要的是要理解HTTP协议并不是适合所有类型的应用程序。HTTP是一种简单的面向请求/响应的协议,最初设计用于支持静态或动态生成的内容检索。
它从未打算支持事务操作。
例如,如果HTTP服务器成功接收并处理请求,生成响应并将状态代码发送回客户端,则HTTP服务器将考虑这是合约履行的一部分。
如果客户端由于读取超时,请求取消或系统崩溃而未能完全接收响应,则服务器将不会尝试回滚事务。
如果客户端决定重试相同的请求,则服务器将不可避免地多次执行同一事务。
在某些情况下,这可能会导致应用程序数据损坏或应用程序状态不一致。
尽管HTTP从未被设计为支持事务处理,但只要满足某些条件,它仍可用作关键任务应用程序的传输协议。
为确保HTTP传输层安全,系统必须确保应用层上HTTP方法的幂等性。
Idempotent methods(幂等方法)
HTTP / 1.1规范将幂等方法定义为
[方法也可以具有“幂等”的属性(除了错误或过期问题)N> 0个相同请求的副作用与单个请求相同]
换句话说,应用程序应该确保它准备好处理同一方法的多次执行的情况。
例如,这可以通过提供唯一的事务id和通过避免执行相同逻辑操作的其他手段来实现。
请注意,此问题并非特定于HttpClient。
基于浏览器的应用程序与HTTP方法非幂等性相关的问题完全相同。
默认情况下,HttpClient假定只有非实体封闭方法(如GET和HEAD)是幂等的,而实体封闭方法(如POST和PUT)因为兼容性原因问题所以不是幂等性。
Automatic exception recovery(自动异常恢复)
默认情况下,HttpClient会尝试从I / O异常中自动恢复。
默认的自动恢复机制仅限于一些已知安全的例外情况
- HttpClient不会尝试从任何逻辑或HTTP协议错误(从HttpException类派生的错误)中恢复。
- HttpClient将自动重试那些被认为是幂等的方法。
- 当HTTP请求仍在传输到目标服务器时(即请求尚未完全传输到服务器),HttpClient将自动重试那些因传输异常而失败的方法。
Request retry handler(请求重试处理程序)
为了启用自定义异常恢复机制,应该提供HttpRequestRetryHandler接口的实现。
1 | HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() { |
请注意,可以使用StandardHttpRequestRetryHandler而不是默认使用的那个,以便将RFC-2616定义为幂等的请求方法视为安全自动重试:GET,HEAD,PUT,DELETE,OPTIONS和TRACE。
Aborting requests(关于请求)
在某些情况下,由于目标服务器上的高负载或客户端发出的并发请求太多,HTTP请求执行无法在预期的时间范围内完成。
在这种情况下,可能需要提前终止请求并解除阻塞I / O操作中阻止的执行线程。
通过调用HttpUriRequest#abort()方法,可以在执行的任何阶段中止由HttpClient执行的HTTP请求。
此方法是线程安全的,可以从任何线程调用。
当HTTP请求被中止时,它的执行线程 - 即使当前在I / O操作中被阻塞 - 也可以通过抛出InterruptedIOException来解除阻塞
Redirect handling(重定向处理)
HttpClient自动处理所有类型的重定向,除了HTTP规范明确禁止的需要用户干预的重定向。
请参阅其他(状态代码303)重定向POST,并将PUT请求转换为HTTP规范要求的GET请求。
可以使用自定义重定向策略来放宽对HTTP规范强加的POST方法的自动重定向的限制。
1 | LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy(); |
HttpClient通常必须在执行过程中重写请求消息。
默认情况下,HTTP / 1.0和HTTP / 1.1通常使用相对请求URI。
同样,原始请求可能会多次从一个位置重定向到另一个位置。
可以使用原始请求和上下文构建最终解释的绝对HTTP位置。
实用程序URIUtils #resolution可用于构建用于生成最终请求的已解释绝对URI。
此方法包括重定向请求或原始请求中的最后一个片段标识符。
1 | CloseableHttpClient httpclient = HttpClients.createDefault(); |