HttpComponents-Core 学习
官方文档:http://hc.apache.org/httpcomponents-core-4.4.x/tutorial/html/index.html
点击进入 我的为知笔记链接(带格式)
前言
HttpCore是一组实现了大部分HTTP协议的组件的机会,虽然没有全部实现,但已足够用来开发完全功能的客户端和服务端HTTP服务!
HttpCore Scope 范围
- 一致性的API,用于构建client/proxy/server side HTTP 服务。
- 一致性的API,用于构建同步和异步的HTTP服务。
- 低层级组件的集合,基于阻塞式(经典)和非阻塞式(NIO)模型。
HttpCore Goals 目标
- 实现HTTP传输的大部分功能。
- 在性能和 API的 清晰性 & 表达性 之间平衡。
- 小的内存占用。
- 自包含的库(没有任何外部依赖,除了JRE)。
HttpCore不是什么?
- 不是HttpClient的替代品。
- 不是Servlet APIs的替代品。
第一章 1.基础
1.1 HTTP messages
1.1.1 结构
一个HTTP message 由一个消息头和一个可选的消息体组成。一个HTTP request 的消息头 由一个请求行 和一组header字段构成。一个HTTP response 的消息头由一个状态行 和一组header字段构成。 所有的HTTP messages 必须包含协议版本。一些HTTP messages 可能包含一个内容体(content body)。
HttpCore 贴切地遵守该定义,来定义了 HTTP message对象模型,并提供了对HTTP message元素的序列化(格式化)和反序列化(解析)的扩展支持。
1.1.2 基本操作
1.1.2.1 HTTP request message
HTTP request 是从 客户端 发送到 服务端 的message。该message的第一行包含了method、uri、以及协议版本。
HttpRequest request = new BasicHttpRequest("GET", "/", HttpVersion.HTTP_1_1);
1.1.2.2 HTTP response message
HTTP response 是从 服务端 返回到 客户端 的message,是在服务端接收并解释了一个request message之后。 第一行由协议版本、状态码、状态文字构成。
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
1.1.2.3 HTTP message 的公共属性和方法
一个HTTP message 可能包含多个headers 以描述该message的属性,例如内容长度、内容类型等到。
HttpCore 提供了方法来获取、添加、移除、以及遍历这些headers:
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");
Header h2 = response.getLastHeader("Set-Cookie");
Header[] hs = response.getHeaders("Set-Cookie");
还有一种高效的方式来获取某个key对应的所有headers:
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());
}
还提供了便捷的方法来将 HTTP message 解析成单独的header元素:
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]);
}
}
1.1.3 HTTP entity
HTTP messages 可以携带一个 content entity,这是可选的。
那些使用entities的Requests,通常叫做 entity-enclosing requests。HTTP spec 定义了两种entity-enclosing methods:POST 和 PUT。
Response 通常enclose a content entity。 当然也有例外,如对HEAD method的响应,如204 No Content、304 Not Modified、205 Reset Content responses。
依据内容的目的不同,HttpCore 划分出三种类型的entity:
- streamed(流的):该content 是接收自一个stream。尤其,该类别包含了接收自一个连接的entity。一般不可重复。
- self-contained (自包容的):该content 是在内存中,或者通过无关于一个连接或者其他entity的方法获取到。通常可重复。
- wrapping (包装的):获取自其他entity。
1.1.3.1 可重复的entity
一个entity是可重复的,意味着它的content可以被多次读取。仅适用于self-contained entity (如ByteArrayEntity 或 StringEntity)。
1.1.3.2 使用HTTP entity
由于 entity 可以是binary 也可以是 character content,所以它支持character encodings。--天然支持编码。
从entity中读取content,可以通过 HttpEntity#getContent() 方法来获取InputStream,也可以 为HttpEntity#writeTo(OutputStream)方法提供一个输出流,候着会将所有内容一次性地返回到给定的流中。 请注意:一些非流式的entity(如self-contained),可能无法将其content有效地表现为InputStream。 对于这些entity,可以只实现HttpEntity#writeTo(OutputStream) 方法,而对HttpEntity#getContent()采取抛出UnsupportedOperationException操作。
EntityUtils类,暴露了一些静态方法,以简化 从entity中提取content或信息的过程。与直接读取InputStream相比,你可以使用该类的方法来获取完整的content body --以字符串或者byte array的形式。
当从来信 中接收到entity时,可以使用 HttpEntity#getContentType() 和 HttpEntity#getContentLength() 来读取公有的元数据,如Content-Type 和 Content-Length headers(如果可用)。因为Content-Type header 可能为诸如text/plain 或text/html之类的mime-types 包含一个字符编码,用可以使用 HttpEntity#getContentEncoding() 来读取该信息。 如果 headers不可用,长度返回-1,content type为NULL。 如果Content-Type header可用, 会返回一个Header对象。
当为去信 创建一个entity时, 必须提供元数据:
StringEntity myEntity = new StringEntity("important message", Consts.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.1.3.3 确保释放系统资源
为了确保正确地释放系统资源,用户必须close 与entity关联的content stream。
HttpResponse response;
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
try {
// do something useful
} finally {
instream.close();
}
}
当处理 streamed entity时,用户可用使用 EntityUtils#consume(HttpEntity) 来确保entity content被完全地consumed,并且底层的stream被关闭。
1.1.4 创建entity
有几种方式来创建entity。 HttpCore 提供了下列实现:
1.1.4.1 BasicHttpEnitty
顾名思义,它代表了一种底层的stream。
该entity 有一个empty constructor。 构造之后,no content,content length为 -1。
用户需要设置content stream,以及可选地设置length。
BasicHttpEntity myEntity = new BasicHttpEntity();
myEntity.setContent(someInputStream);
myEntity.setContentLength(340); // sets the length to 340; optionally
1.1.4.2 ByteArrayEntity
这是一个self-contained、可重复的entity,它从一个给定的byte array获取content。
ByteArrayEntity myEntity = new ByteArrayEntity(new byte[] {1,2,3}, ContentType.APPLICATION_OCTET_STREAM);
1.1.4.3 StringEntity
这是一个self-contained、可重复的entity,从java.lang.String对象中获取content。
StringBuilder sb = new StringBuilder();
Map<String, String> env = System.getenv();
for (Map.Entry<String, String> envEntry : env.entrySet()) {
sb.append(envEntry.getKey())
.append(": ").append(envEntry.getValue())
.append("\r\n");
}
// construct without a character encoding (defaults to ISO-8859-1). TODO: 不推荐
HttpEntity myEntity1 = new StringEntity(sb.toString());
// alternatively construct with an encoding (mime type defaults to "text/plain"). TODO: 推荐
HttpEntity myEntity2 = new StringEntity(sb.toString(), Consts.UTF_8);
// alternatively construct with an encoding and a mime type
HttpEntity myEntity3 = new StringEntity(sb.toString(), ContentType.create("text/plain", Consts.UTF_8));
1.1.4.4 InputStreamEntity
这是一个streamed、不可重复的entity,从一个输入流获取content。通过提供输入流和content length 来构造。提供的 content length 是用来限制读取的数据量,但是,如果给定负值,那会读取全部数据。
InputStream instream = getSomeInputStream();
InputStreamEntity myEntity = new InputStreamEntity(instream, 16);
1.1.4.5 FileEntity
这是一个self-contained、可重复的entity,从一个文静中获取content。
HttpEntity entity = new FileEntity(staticFile, ContentType.create("application/java-archive"));
1.1.4.6 HttpEntityWrapper
这是创建wrapped entity的基类。该entity会持有被封装的entity的引用,并代理所有对其的调用。 wrapping entity的实现可用继承自该类,只需要重写那些不应该被代理到被封装的entity的方法即可。
1.1.4.7 BufferedHttpEntity
这是HttpEntityWrapper的子类。 通过提供另一个entity来构造。它会读取提供的entity的content,并将其缓存进内存。
这使得其可以从一个不可重复的entity得到一个可重复的entity。 如果提供的entity本身可重复,它会简单地pass the calls。
myNonRepeatableEntity.setContent(someInputStream);
BufferedHttpEntity myBufferedEntity = new BufferedHttpEntity(myNonRepeatableEntity);
1.2 HTTP 协议 处理器
HTTP协议拦截器是一个程序,该程序实现了HTTP协议的特定方面(aspect)。通常,协议拦截器作用于一个特定的header或者一组相关联的headers -- 无论是来信或者去信。协议拦截器 也可以操作message中的content entity;透明的内容压缩/解压缩是一个很好的例子。通常这是通过装饰器模式来实现:一个wrapper entity class来装饰原有的entity。 数个协议拦截器可以结合起来形成一个逻辑单元。
HTTP 协议处理器 是一组协议拦截器的集合,其实现了 'Chain of Responsibility' pattern,每一个独立的协议拦截器负责HTTP协议的一个特定方面。
通常,拦截器的执行顺序不会影响彼此,因为它们不依赖于执行上下文的特定状态。如果依赖了,那就必须以特定的顺序来执行!
协议拦截器必须是线程安全的。类似于servlets,协议拦截器不应该使用实例变量,除非是同步的。
1.2.1 标准的协议拦截器
HttpCore 提供了数个最基本的协议拦截器,用于客户端和服务端HTTP处理。
1.2.1.1 RequestContent
这是用于outgoing request的最重要的拦截器。负责限制content length,通过添加Content-Length或者Transfer-Content header实现。客户端必须。
1.2.1.2 ResponseContent
这是用于outgoing response的最重要的拦截器。负责限制content length,通过添加Content-Length或者Transfer-Content header实现。服务端必须。
1.2.1.3 RequestConnControl
负责为outgoing request添加 Connection header,这对于管理HTTP/1.0连接来说很重要。客户端推荐。
1.2.1.4 ResponseConnControl
负责为outgoing response添加 Connection header,这对于管理HTTP/1.0连接来说很重要。服务端推荐。
1.2.1.5 RequestDate
负责为outgoing request 添加Date header。客户端可选。
1.2.1.6 ResponseDate
负责为outgoing response添加Date header。服务端推荐。
1.2.1.7 RequestExpectContinue
用于通过添加Expect header 来启用'expect-continue'握手。推荐用于客户端协议处理器。客户端推荐。
1.2.1.8 RequestTargetHost
负责添加Host header。客户端必须。
1.2.1.9 RequestUserAgent
负责添加User-Agent header。客户端推荐。
1.2.1.10 ResponseServer
负责添加Server header。 服务端推荐。
1.2.2 使用协议处理器
通常,HTTP协议处理器用于预处理来信,以及后处理出信。
如下:
HttpProcessor httpproc = HttpProcessorBuilder.create()
// Required protocol interceptors
.add(new RequestContent())
.add(new RequestTargetHost())
// Recommended protocol interceptors
.add(new RequestConnControl())
.add(new RequestUserAgent("MyAgent-HTTP/1.1"))
// Optional protocol interceptors
.add(new RequestExpectContinue(true))
.build();
HttpCoreContext context = HttpCoreContext.create();
HttpRequest request = new BasicHttpRequest("GET", "/");
httpproc.process(request, context);
或者:
HttpResponse response = <...>
httpproc.process(response, context);
请注意:BasicHttpProcessor 不会同步访问其内部结构,因此,可能线程不安全。
1.3 HTTP execution context 执行上下文
起初的时候,HTTP被设计成一个无状态的、面向响应-请求的协议。然而,实际的应用经常需要在数个逻辑相关的请求-响应交换中保持状态信息。为了使得应用能够维持一个处理状态,HttpCore 允许HTTP message 在一个特定的execution context中执行,这个执行上下文也被叫做 HTTP context。
多个逻辑相关的message可以参与进同一个逻辑session -- 只要连续的请求中重复使用同一个context。HTTP context 功能类似于 Map<String, Object>。只是一组逻辑相关的KV的集合。
请注意,HttpContext可以包含任意对象,因此,在多线程之间共享可能不安全。请确认HttpContext实例在同一时刻只被一个线程访问。
1.3.1 Context sharing 上下文共享
协议拦截器 可以通过共享信息来合作,如:经由一个HTTP execution context来共享同一个处理状态。
HTTP context 是一个结构,可被用于将一个attribute name映射到attribute value。内部,HTTP context 的实现通常是由HashMap支持的。HTTP context 最基本的目标是有效利用多个逻辑相关的组件之间共享的信息。HTTP context 可以用于存储一个处理状态。多个逻辑相关的消息可以参与进同一个逻辑会话 -- 只要这连续的消息之间重用了同一个context。
HttpProcessor httpproc = HttpProcessorBuilder.create()
.add(new HttpRequestInterceptor() {
public void process(
HttpRequest request,
HttpContext context) throws HttpException, IOException {
String id = (String) context.getAttribute("session-id");
if (id != null) {
request.addHeader("Session-ID", id);
}
}
})
.build();
HttpCoreContext context = HttpCoreContext.create();
HttpRequest request = new BasicHttpRequest("GET", "/");
httpproc.process(request, context);
第二章 2. 阻塞式I/O模型
Java中的阻塞式I/O,代表了一个非常高效的、便捷的I/O模型,非常适合高性能应用(并发连接数 中等)。现代JVMs能够有效的进行context 切换,并且 阻塞式I/O 模型应该提供最佳性能 -- 只要并发连接数低于1000,且连接主要是传输数据时。然而,对于那些 在多数时间内都空闲的连接的应用来说,非阻塞式I/O模型可能是一个更好的选择。
2.1 阻塞式HTTP 连接
HTTP 连接负责 HTTP message的序列化和反序列化。 用户很少需要直接使用HTTP 连接对象。有更高级的协议组件用于执行和处理HTTP请求。
然而,在某些情况下,仍然需要直接操作HTTP连接,例如,获取某些属性:连接状态、socket超时、local/remote地址等。
需要谨记在心:HTTP连接是线程不安全的! 我们强烈建议 限制只有一个线程来与HTTP connection交互。HttpConnection接口中能够由其他线程安全调用的唯一方法是:HttpConnection#shutdown()。
2.1.1 使用阻塞式HTTP 连接
HttpCore没有 为打开连接 提供完全的支持,因为创建一个新连接的过程 -- 特别是在客户端 -- 可能非常复杂,当涉及到一个或多个认证 和/或 通道代理时。相反,阻塞式HTTP连接可用于任何network socket。
Socket socket = <...>
DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1024);
conn.bind(socket);
System.out.println(conn.isOpen());
HttpConnectionMetrics metrics = conn.getMetrics();
System.out.println(metrics.getRequestCount());
System.out.println(metrics.getResponseCount());
System.out.println(metrics.getReceivedBytesCount());
System.out.println(metrics.getSentBytesCount());
未完待续
ps:未知笔记的链接里已经把除了NIO部分的全放上去了。