• 背景知识
  • 前言
    • HTTPCore范围
    • HTTPCore目标
    • HTTPCore不是什么
  • 一、基础
    • 1.1. HTTP messages

      • 1.1.1.构造
      • 1.1.2. 基本操作
        • 1.1.2.1. HTTP请求消息
        • 1.1.2.2. HTTP响应消息
        • 1.1.2.3. HTTP消息通用属性和方法
      • 1.1.3 HTTP实体
        • 1.1.3.1. 可重复的实体
        • 1.1.3.2. 使用HTTP实体
        • 1.1.3.3 确保释放系统资源
      • 1.1.4. 创建实体
        • 1.1.4.1. BasicHttpEntity
        • 1.1.4.2. ByteArrayEntity
        • 1.1.4.3. StringEntity
        • 1.1.4.4. InputStreamEntity
        • 1.1.4.5 FileEntity
        • 1.1.4.6. HttpEntityWrapper
        • 1.1.4.7. BufferedHttpEntity
    • 1.2. HTTP协议处理器
      • 1.2.1. 标准的协议拦截器
      • 1.2.2. 使用协议处理器
    • 1.3 HTTP执行上下文
  • 二、阻塞I/O模式
    • 2.1 阻塞HTTP连接

      • 2.1.1. 使用阻塞HTTP连接
      • 2.1.2 使用阻塞I/O传输内容
      • 2.1.3. 支持的内容传输机制
      • 2.1.4. 结束HTTP连接
    • 2.2. HTTP异常处理
      • 2.2.1. 协议异常
    • 2.3. 阻塞HTTP协议handlers
      • 2.3.1. HTTP service

        • 2.3.1.1. HTTP request handlers
        • 2.3.1.2. Request handler resolver
        • 2.3.1.3. 使用HTTP service处理请求
      • 2.3.2. HTTP request executor
      • 2.3.3. 连接持久化/重用
      • 2.4. 连接池
    • 2.5. TLS/SSL支持
    • 2.6. 嵌入式的HTTP服务
  • 三、基于NIO的异步I/O
    • 3.1. 和其它I/O框架的不同
    • 3.2. I/O reactor
      • 3.2.1. I/O dispatchers
      • 3.2.2. I/O反应器关闭
      • 3.2.3. I/O会话
      • 3.2.4. I/O会话状态管理
      • 3.2.5. I/O会话事件掩码
      • 3.2.6. I/O会话缓冲
      • 3.2.7. I/O会话关闭
      • 3.2.8. 监听I/O反应器
      • 3.2.9. 连接I/O反应器
    • 3.3. I/O反应器配置
      • 3.3.1. Queuing of I/O interest set operations
    • 3.4. I/O反应器异常处理
      • 3.4.1. I/O反应器审计日志
    • 3.5. 非阻塞的HTTP连接
      • 3.5.1. 非阻塞HTTP连接的执行上下文
      • 3.5.2. 使用非阻塞的HTTP连接
      • 3.5.3. HTTP I/O控制
      • 3.5.4. 非阻塞内容传输
      • 3.5.5. 非阻塞内容传输机制的支持
      • 3.5.6. 直接I/O通道
    • 3.6. HTTP I/O 事件调度
    • 3.7. 非阻塞HTTP内容生产者
      • 3.7.1. 创建非阻塞实体

        • 3.7.1.1. NByteArrayEntity
        • 3.7.1.2. NStringEntity
        • 3.7.1.3. NFileEntity
    • 3.8. 非阻塞HTTP协议handlers
  • 参考文献

背景知识

httpCore是对http协议的封装实现,底层也是用socket进行交互的

http底层也是socket只不过是支持了http协议的socket,也就是说使用socket长连接返回http协议格式的数据

http实现单向主动通信,但是socket可以实现双向主动通信

  • socket 与 http

    • HTTP是基于应用层,socket是基于传输层(tcp/udp)
    • HTTP 是一种作用于 应用层的数据协议,用于如何封装数据
    • HTTP连接使用的是”请求-响应”方式,不仅在请求时建立连接,而且客户端向服务器端请求后,服务器才返回数据
    • Socket 是对 TCP/IP 协议的封装,Socket 只是个接口不是协议,通过 Socket 我们才能使用 TCP/IP 协议
    • 创建 Socket 连接的时候,可以指定传输层协议,可以是 TCP 或者 UDP,当用 TCP 连接,该Socket就是个TCP连接,反之
    • Socket为长连接:通常情况下Socket 连接就是 TCP 连接,因此 Socket 连接一旦建立,通讯双方开始互发数据内容,直到双方断开连接。在实际应用中,由于网络节点过多,在传输过程中,会被节点断开连接,因此要通过轮询高速网络,该节点处于活跃状态。
    • web 使用 HTTP 作传输层协议,以封装 HTTP 文本信息,然后使用 TCP/IP 做传输层协议,将数据发送到网络上
  • httpcore 与 httpclient

    • HttpClient是一个基于HttpCore的客户端Http传输类库,基于传统的(阻塞)IO

前言

HTTPCore是一套实现了HTTP协议最基本的方面的组件,尽管这样还是足够开发占用最小空间、功能全面的客户端和服务器端HTTP服务。
HTTPCore有以下的范围和目标:

HTTPCore范围

  • 一套创建客户端/代理/服务端HTTP服务的一致的API
  • 一套创建同步和异步HTTP服务的一致的API
  • 一套基于阻塞(传统的)和非阻塞(NIO)I/O模型的低级别的组件

HTTPCore目标

  • 实现最基本的HTTP传送部分
  • 优秀的性能和明晰富有表现力的API之间的平衡
  • 少量(预期)内存占用
  • 自包含库(除了JRE外没有外部依赖)

HTTPCore不是什么

  • HttpClient的替代

    • HttpClient是一个基于HttpCore的客户端Http传输类库,基于传统的(阻塞)IO
  • Servlet APIs的替代

一、基础

1.1. HTTP messages

1.1.1.构造

一个HTTP消息由一个头部和一个可选的主体组成。一个HTTP请求的消息头由一个请求行和一个头部字段的集合组成。一个HTTP响应的消息头由一个状态行和一个头部字段的集合组成。所有的HTTP消息必须包含协议版本。一些HTTP消息可选地包括一个内容主体部分。

1.1.2. 基本操作

1.1.2.1. HTTP请求消息

HTTP请求是一个从客户端发往服务端的消息。消息的第一行包括应用资源的方法,资源的标识符,正在使用的协议的版本。


HttpRequest request = new BasicHttpRequest("GET", "/",HttpVersion.HTTP_1_1);System.out.println(request.getRequestLine().getMethod());
System.out.println(request.getRequestLine().getUri());
System.out.println(request.getProtocolVersion());
System.out.println(request.getRequestLine().toString());

stdout >

GET
/
HTTP/1.1
GET / HTTP/1.1

1.1.2.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.2.3. HTTP消息通用属性和方法

一个HTTP消息能够包含一些描述消息属性的头部,例如内容长度、内容类型等等。HttpCore提供方法检索、增加、删除和枚举这些头部。

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接口是一种获取所有给定类型的头部的有效方式。

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消息转换为单个的头部对象

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 = apath=/domain=localhost
c2 = bpath=/
c3 = cdomain=localhost

HTTP头部在需要的时候根据token被拆分为单个的头部对象。HTTP头部通过HTTP连接接收,在内部作为字符数组存储,只有当你访问他们的属性时才懒解析。

1.1.3 HTTP实体

HTTP消息能够通过请求和响应携带内容实体。实体是可选的,在一些请求和响应中能够找到。使用实体的请求被称为实体包含的请求。HTTP规范定义了两种实体包含的方法:POST和PUT。通常预期响应包含一个内容实体。此规则也有例外的情况,如HEAD方法的响应204 No Content,304 Not Modified,205 Reset Content response.

HTTPCore根据内容来源区分三种实体:

  • 流式的:从一个流中接收的,或者随时产生的。特别的,这个类别包括从一个连接中接收的实体。流式的实体通常不能重复。
  • 自包含的:在内存中的内容或者通过独立于连接或其它实体的方式获得。自包含的实体通常是可重复的。
  • 包装的:从其它实体中获得的内容。

1.1.3.1. 可重复的实体

一个实体可重复,意味着它的内容能被读取多次。只有自包含的实体可重复(像ByteArrayEntiey或者StringEntity)

1.1.3.2. 使用HTTP实体

既然一个实体能够代表二进制和文本内容,它必然支持字符编码(例如支持拉丁文等字符内容)。

当执行一个包含内容的请求或者当请求成功并且使用响应体返回结果给客户端的时候实体被创建。

要从实体中读取内容,可以通过HttpEntity的getContent()方法返回java.io.InputStream 输入流来检索,或者向HttpEntity的writeTo(OutputStream)方法提供输出流,一旦所有的内容被写到指定的流时就会返回。请注意一些非流式的实体(自包含的)可能不能高效的把他们的内容表示为java.io.InputStream。这样的实体仅仅实现HttpEntity的writeTo(OutputStream)方法并从HttpEntity的getContent()方法抛出UnsupportedOperationException异常是合法的。
EntieyUtils类公开了一些静态方法简化从实体中提取内容或信息。通过这个类以字符串或者字节数组的方式检索全部的内容体,而不是通过直接的读输入流。

当实体接收到传入消息时,HttpEntity的getContentType()和HttpEntity的getContentLength()方法能够用于读取例如Content-Type和Content-Length头部等的元数据(如果可用的话)。由于Content-Type头部能够包含文本MIME类型如text/plain或text/html的字符编码,HttpEntity的getContentEncoding()方法用于读取这些信息。如果头部不可用,返回-1,内容类型返回NULL。如果Content-Type头部可用,返回一个头部对象。
当为外发消息创建实体时,这个元数据必须由实体创建者提供。

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

stdout >

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

1.1.3.3 确保释放系统资源

为了确保合适地释放系统资源,必须关闭实体关联的内容流

HttpResponse response;
HttpEntity entity = response.getEntity();
if (entity != null) {InputStream instream = entity.getContent();try {// do something useful} finally {instream.close();}
}

当使用流式实体时,可以使用EntityUtils的consume(HttpEntity)方法确保实体内容已经被全部消费并且底层的流已经关闭。

1.1.4. 创建实体

有一些创建实体的方式。HttpCore提供了以下实现:

  • BasicHttpEntity
  • ByteArrayEntity
  • StringEntity
  • InputStreamEntity
  • FileEntity
  • EntityTemplate
  • HttpEntityWrapper
  • BufferedHttpEntity

1.1.4.1. BasicHttpEntity

  • 顾名思义,基本实体代表一个底层的流。通常,这个类用于从HTTP消息接收实体。
  • 这个实体有一个空的构造方法。构造后,它表示没有内容,有一个负值的内容长度。
  • 需要设置内容流,可选长度,通过分别使用BasicHttpEntity的setContent(InputStream)方法和BasicHttpEntity的setContentLength(long)方法。
BasicHttpEntity myEntity = new BasicHttpEntity();
myEntity.setContent(someInputStream);
myEntity.setContentLength(340); // sets the length to 340

1.1.4.2. ByteArrayEntity

ByteArrayEntity是一个自包含的,可重复的实体,从一个给定数组获取实体。提供字节数组给构造方法。

ByteArrayEntity myEntity = new ByteArrayEntity(new byte[] {1,2,3},ContentType.APPLICATION_OCTET_STREAM);

1.1.4.3. StringEntity

StringEntity是一个自包含的,可重复的实体,从一个java.lang.String对象获取内容。

它有三个构造方法,一个给定java.lang.Object参数的简单构造方法;第二个还需要字符串类型的数据的字符编码;第三个允许指定MIME类型。

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)
HttpEntity myEntity1 = new StringEntity(sb.toString());// alternatively construct with an encoding (mime type defaults to "text/plain")
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

InputStreamEntity是一个流式的,不可重复的实体,从一个输入流获取内容。通过提供输入流和内容长度来构造。

使用内容长度去限制从java.io.InputStream读取数据量。如果长度和输入流的内容的长度相匹配,则发送所有的数据。

或者,一个负值的长度将从输入流中读取所有的数据,这和提供精确的内容长度一样,因此使用长度去限制读取的数据量。

InputStream instream = getSomeInputStream();
InputStreamEntity myEntity = new InputStreamEntity(instream, 16);

1.1.4.5 FileEntity

FileEntity是一个自包含的,可重复的实体,从一个文件获取内容。

使用它主要传输不同类型的大文件,你需要提供文件的内容类型,例如,发送一个zip文件需要application/zip的内容类型,XML文件需要application/xml类型。

HttpEntity entity = new FileEntity(staticFile,ContentType.create("application/java-archive"));

1.1.4.6. HttpEntityWrapper

这是创建包装实体的基本类。包装实体持有被包装实体的引用并委托所有的调用给它。源于这个类的包装实体的实现仅需要重写那些不应该被委托给被包装实体的方法。

1.1.4.7. BufferedHttpEntity

BufferedHttpEntity是HttpEntityWrapper的子类。
通过另一个实体构造它。它从提供的实体中读取内容然后缓存到内存中。
这把它从一个不可重复的实体变成为一个可重复的实体。如果提供的实体已经可重复,它仅仅通过内在的实体传递调用。

myNonRepeatableEntity.setContent(someInputStream);
BufferedHttpEntity myBufferedEntity = new BufferedHttpEntity(
myNonRepeatableEntity);

1.2. HTTP协议处理器

HTTP协议拦截器是一个实现特定HTTP协议方面的例程。通常希望协议拦截器在入站消息的一个特定的头部或一组相关的头部起作用或者用一个特定头部或一组相关头部填充出站消息。协议拦截器还能操作包括在消息中的内容实体;透明的内容压缩/解压缩是一个很好的例子。通常这是通过使用包装器实体装饰原有的实体的装饰模式来实现的。多个协议拦截器能够组合在一个逻辑单元里。

  • HTTP协议处理器是实现了责任链模式的一个协议拦截器集合,每个单独的协议拦截器都希望在它负责的特定方面起作用。
  • 通常拦截器的执行顺序不受影响,只要它们不依赖一个执行上线文的特定状态。如果协议拦截器有内部依赖必须按照一定顺序执行,它们应该按照期望执行的顺序添加到协议处理器。
  • 协议拦截器必须以线程安全的方式实现。和servlets类似,协议拦截器不应该使用实例变量,除非访问那些实例变量是同步的

1.2.1. 标准的协议拦截器

HttpCore为客户端和服务端HTTP处理提供了一些基本的协议拦截器。

  • 1.2.1.1. RequestContent

RequestContent是最重要的出站请求拦截器。它负责通过根据闭合实体和协议版本添加Content-Length头部或Transfer-Content头部来定界内容的长度。这个拦截器是客户端协议处理器正确运转所必须的。

  • 1.2.1.2. ResponseContent

ResponseContent是最重要的出站响应拦截器。它负责通过根据闭合实体和协议版本添加Content-Length头部或Transfer-Content头部来定界内容的长度。这个拦截器是服务器协议处理器正确运转所必须的。

  • 1.2.1.3. RequestConnControl

RequestConnControl负责添加Connection头部到出站请求,这是管理HTTP/1.0连接持久的本质。这个协议拦截器在客户端协议处理器是建议的。

  • 1.2.1.4. ResponseConnControl

ResponseConnControl负责添加Connection头部到出站响应,这是管理HTTP/1.0连接持久的本质。这个协议拦截器在服务端协议处理器是建议的。

  • 1.2.1.5. RequestDate

RequestDate负责添加Date头部到出站请求。这个拦截器在客户端协议处理器是可选的。

  • 1.2.1.6. ResponseDate

ResponseDate负责添加Date头部到出站响应。这个拦截器在服务端协议处理器是建议的。

  • 1.2.1.7. RequestExpectContinue

RequestExpectContinue负责通过添加Expect头部启用继续连接握手。这个拦截器在客户端协议处理器是建议的。

  • 1.2.1.8. RequestTargetHost

  • RequestTargetHost负责添加Host头部。这个拦截器在客户端协议处理器是必须的。

  • 1.2.1.9. RequestUserAgent

RequestUserAgent负责添加User-Agent头部。这个拦截器在客户端协议处理器是建议的。

  • 1.2.1.10. ResponseServer

ResponseServer负责添加Server头部。这个拦截器在服务端协议拦截器是建议的。

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 = <...>
httpproc.process(response, context);

请注意BasicHttpProcess类没有同步访问它的内部结构,因此可能不是线程安全的

1.3 HTTP执行上下文

最初,HTTP被设计为无状态的、面向响应请求的协议。

然而,真实世界的应用程序通常需要能够通过一些逻辑相关联的请求响应交换信息来保存状态信息。

为了应用程序能够保持一个处理状态,HttpCore允许HTTP消息在一个被称为HTTP context的特定的执行上下文执行。

如果在连续的请求中同一上下文被重复使用,那么多个逻辑相关的消息能够加入一个逻辑的会话。HTTP上线文和java.util.Map

二、阻塞I/O模式

阻塞(或者传统)I/O在Java中代表一个高效方便的I/O模式,很适合并发连接数相对适中的高性能应用程序。现在的JVM具有高效的上下文切换能力,只要并发连接数低于1000并且连接大部分忙于传输数据,阻塞I/O模式在原始数据吞吐量方面提供最佳的性能。然而,对于大多数时间连接保持空闲的应用来说,上下文切换的开销可能变得很大,并且一个非阻塞的I/O模式可能是更好的选择。

2.1 阻塞HTTP连接

HTTP连接负责HTTP消息的序列化和反序列化。几乎不直接使用HTTP连接对象。有高级别的协议组件为了执行和处理HTTP请求。

然而,在一些情况下直接使用HTTP连接交互可能必须的,例如,访问像连接状态、socket超时或者本地和远程地址的属性。

牢记HTTP连接不是线程安全的是很重要的。我们强烈建议限制所HTTP连接对象的所有交互在一个线程。HttpConnection接口和它的子接口从另一个线程调用唯一安全的方法是HttpConnection的shutdown()方法。

2.1.1. 使用阻塞HTTP连接

HttpCore不提供打开连接的全面支持,因为建立一个新连接的过程(尤其在客户端)可能非常复杂,因为它涉及一个或多个身份验证或/和隧道代理。
相反,阻塞HTTP连接能够绑定到任意的网络套接字上。

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

HTTP连接接口(客户端和服务端),发送和接收消息分两个阶段。

先发送消息头。根据消息头的属性,一个消息体可能跟随它。请注意始终关闭底层的内容流以便发送处理消息完成的信号是非常重要的。直接从底层连接的输入流中流出的实体必须确保消息体的内容被全部消费以便可能重用。
在客户端过度简化的请求执行过程可能如下:

Socket socket = <...>DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1024);
conn.bind(socket);
HttpRequest request = new BasicHttpRequest("GET", "/");
conn.sendRequestHeader(request);
HttpResponse response = conn.receiveResponseHeader();
conn.receiveResponseEntity(response);
HttpEntity entity = response.getEntity();
if (entity != null) {// Do something useful with the entity and, when done, ensure all// content has been consumed, so that the underlying connection// can be re-usedEntityUtils.consume(entity);
}

在服务端过度简化的请求处理过程可能如下:

Socket socket = <...>DefaultBHttpServerConnection conn = new DefaultBHttpServerConnection(8 * 1024);
conn.bind(socket);
HttpRequest request = conn.receiveRequestHeader();
if (request instanceof HttpEntityEnclosingRequest) {conn.receiveRequestEntity((HttpEntityEnclosingRequest) request);HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();if (entity != null) {// Do something useful with the entity and, when done, ensure all// content has been consumed, so that the underlying connection// could be re-usedEntityUtils.consume(entity);}
}
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,200, "OK") ;
response.setEntity(new StringEntity("Got it") );
conn.sendResponseHeader(response);
conn.sendResponseEntity(response);

请注意几乎不应该使用这些低级别的方法传输消息,应该使用合适的高级别的HTTP服务实现代替。

2.1.2 使用阻塞I/O传输内容

HTTP连接使用HttpEntity接口管理内容传输的过程。

HTTP连接生成封装入站消息的内容流的实体对象。

请注意HttpServerConnection的receiveRequestEntity()方法和HttpClientConnection的receiveResponseEneity()方法没有检索或者缓存任何输入数据。它们只是基于输入消息的属性注入一些内容编码解码器。通过使用HttpEntity的getContent()方法从包含的实体的内容输入流中读取检索内容。输入数据将被自动解码并对数据消费者是透明的。同样,HTTP连接依赖HttpEntity的writeTo(OutputStream)方法生成出站消息的内容。如果一个出站消息包含一个实体,内容将会基于消息的属性被自动编码。

2.1.3. 支持的内容传输机制

HTTP连接的默认实现支持HTTP/1/规范定义的三种内容传输机制:

  • Content-Length定界:内容实体的结束取决于Content-Length头部的值。最大的实体长度:Long的MAX_VALUE。
  • 代码标识:通过关闭底层的连接(流结束的条件)来限定内容实体的结束。由于显而易见的原因,代码标识只能用于服务端。最大实体长度:无限。
  • 块编码:内容以小块发送。最大实体长度:无限。

基于包含在消息中的实体的属性,合适的内容流将会被自动创建。

2.1.4. 结束HTTP连接

HTTP连接通过调用HttpConnection的close()方法优雅的结束,或者通过调用HttpConnection的shutdown()方法强制结束。
前者在结束连接前尝试刷新所有缓存的数据并且可能无限期的阻塞。HttpConnection的close()方法不是线程安全的。
后者结束连接不刷新内部的缓存,并且尽快把控制权返回给调用者,不会阻塞太久。HttpConnection的shutdown()方法是线程安全的

2.2. HTTP异常处理

所有的HttpCore组件可能抛出两种异常:IOException 发生I/O错误的情况下(例如套接字超时或者套接字重置),HttpException 表示HTTP失败(例如违反HTTP协议)。通常I/O错误被认为是非致命的、可恢复的,而HTTP协议错误别认为是致命的、不能自动恢复的。

2.2.1. 协议异常

ProtocolException 通知一个致命的HTTP协议错误,通常会导致HTTP消息处理的立刻终止

2.3. 阻塞HTTP协议handlers

2.3.1. HTTP service

HttpService是一个实现了RFC2616描述的服务端消息处理的HTTP协议的基本需求,基于阻塞I/O模式的服务端HTTP协议handler。
HttpService依靠HttpProcessor实例为所有出站消息生成强制的协议头部,并将常见的交叉消息转换应用于所有的入站和出站消息,而HTTP请求handlers希望关注应用程序指定的内容生成和处理

HttpProcessor httpproc = HttpProcessorBuilder.create().add(new ResponseDate()).add(new ResponseServer("MyServer-HTTP/1.1")).add(new ResponseContent()).add(new ResponseConnControl()).build();
HttpService httpService = new HttpService(httpproc, null);

2.3.1.1. HTTP request handlers

HttpRequestHandler接口代表一组特定HTTP请求的处理例程。

HttpService被设计于关注协议特定的方面,而单个的请求handlers期望关注于应用程序特定的HTTP处理。

请求handler的主要目的是在给定请求的返回给客户端的响应中使用内容实体生成响应对象。

HttpRequestHandler myRequestHandler = new HttpRequestHandler() {public void handle(HttpRequest request,HttpResponse response,HttpContext context) throws HttpException, IOException {response.setStatusCode(HttpStatus.SC_OK);response.setEntity(new StringEntity("some important message",ContentType.TEXT_PLAIN));}};

2.3.1.2. Request handler resolver

HTTP请求handler通常被把一个请求URI匹配到一个请求handler的HttpRequestHandlerResolver管理。HttpCore包括一个很简单的基于一个琐碎的模式匹配算法的请求handler解析器实现:HttpRequestHandlerRegistry仅支持三种格式:,和*。

HttpProcessor httpproc = <...>HttpRequestHandler myRequestHandler1 = <...>
HttpRequestHandler myRequestHandler2 = <...>
HttpRequestHandler myRequestHandler3 = <...>UriHttpRequestHandlerMapper handlerMapper = new UriHttpRequestHandlerMapper();
handlerMapper.register("/service/*", myRequestHandler1);
handlerMapper.register("*.do", myRequestHandler2);
handlerMapper.register("*", myRequestHandler3);
HttpService httpService = new HttpService(httpproc, handlerMapper);

建议用户提供更复杂的HttpRequestHandlerResolver的实现,例如,基于正则表达式

2.3.1.3. 使用HTTP service处理请求

当全部配置和初始化后,HttpService能够用于执行和处理活动的HTTP连接请求。HttpService的handleRequest()方法读取一个入站请求,生成一个响应并把它返回给客户端。这个方法能够在一个持续的连接中循环处理多个请求。HttpService的handleRequest()方法在多个线程间执行是安全的。只要HttpService使用的所有协议拦截器和请求handler是线程安全的,那么允许同时处理一些连接上的请求。

HttpService httpService = <...>
HttpServerConnection conn = <...>
HttpContext context = <...>boolean active = true;
try {while (active && conn.isOpen()) {httpService.handleRequest(conn, context);}
} finally {conn.shutdown();
}

2.3.2. HTTP request executor

HttpRequestExecutor是一个实现了RFC2616所描述的客户端HTTP协议消息处理的基本要求,基于阻塞I/O模式的客户端HTTP协议handler。HttpRequestExecutor依赖HttpProcessor实例为所有的出站消息生成强制的协议头部,并且将常见的交叉消息转换应用到所有的入站和出站消息。一旦请求被执行并且响应被接收,应用程序特定的处理能够在HttpRequestExecutor之外实现。

HttpClientConnection conn = <...>HttpProcessor httpproc = HttpProcessorBuilder.create().add(new RequestContent()).add(new RequestTargetHost()).add(new RequestConnControl()).add(new RequestUserAgent("MyClient/1.1")).add(new RequestExpectContinue(true)).build();
HttpRequestExecutor httpexecutor = new HttpRequestExecutor();HttpRequest request = new BasicHttpRequest("GET", "/");
HttpCoreContext context = HttpCoreContext.create();
httpexecutor.preProcess(request, httpproc, context);
HttpResponse response = httpexecutor.execute(request, conn, context);
httpexecutor.postProcess(response, httpproc, context);HttpEntity entity = response.getEntity();
EntityUtils.consume(entity);

在多个线程中HttpRequestExecutor方法的执行是安全的。只要被HttpRequestExecutor使用的协议拦截器是线程安全的,那么允许同时在一些连接上执行请求。

2.3.3. 连接持久化/重用

ConnectionReuseStrategy接口旨在决定是否在当前消息传输完成后底层的连接能够被重用去处理更多的消息。
默认的连接重用策略尽可能地保持连接活动。

首先,它检查传输消息使用的HTTP的版本。HTTP/1.1默认连接是持久的,而HTTP/1.0不是。
其次,检查Connection头部的值。通过在Connection头部发送Keep-Alive或者Close的值的对等体表明是否打算重用对方的连接。
第三,如果可能的话,这个策略会基于包含的实体的属性决定将要重用的连接是否是安全的。

2.4. 连接池

高效的客户端HTTP传输通常需要有效的重用持久的连接。HttpCore通过提供持久HTTP连接管理池的支持来促进连接重用。连接池的实现是线程安全的,能够被多个消费者并发使用。

默认连接池只允许20个并发连接,并且每个唯一路由两个并发连接。两个连接的限制是由于HTTP规范的需要。然而,这通常太严格了。在特定的应用程序上下文中,可以在运行时改变连接池的配置允许更多的并发连接。

HttpHost target = new HttpHost("localhost");
BasicConnPool connpool = new BasicConnPool();
connpool.setMaxTotal(200);
connpool.setDefaultMaxPerRoute(10);
connpool.setMaxPerRoute(target, 20);
Future<BasicPoolEntry> future = connpool.lease(target, null);
BasicPoolEntry poolEntry = future.get();
HttpClientConnection conn = poolEntry.getConnection();

请注意连接池不知道是否一个租用的连接是否还在使用中。连接池的使用者有责任在连接不需要的时候把它释放回池中,即使这个连接是不可重用的。

BasicConnPool connpool = <...>
Future<BasicPoolEntry> future = connpool.lease(target, null);
BasicPoolEntry poolEntry = future.get();
try {HttpClientConnection conn = poolEntry.getConnection();
} finally {connpool.release(poolEntry, true);
}

在运行中连接池的状态能够被询问。

HttpHost target = new HttpHost("localhost");
BasicConnPool connpool = <...>
PoolStats totalStats = connpool.getTotalStats();
System.out.println("total available: " + totalStats.getAvailable());
System.out.println("total leased: " + totalStats.getLeased());
System.out.println("total pending: " + totalStats.getPending());
PoolStats targetStats = connpool.getStats(target);
System.out.println("target available: " + targetStats.getAvailable());
System.out.println("target leased: " + targetStats.getLeased());
System.out.println("target pending: " + targetStats.getPending());

请注意连接池不主动驱逐过期的连接,即使过期的连接不能租给请求者。连接池可能随着时间积累过时的连接,尤其是在一段不活动时期后。通常建议在长时间不活动后强制驱逐过期和限制的连接出连接池。

BasicConnPool connpool = <...>
connpool.closeExpired();
connpool.closeIdle(1, TimeUnit.MINUTES);

一般认为,消费者有责任跟踪从池中租用的连接,并确保在不需要或不再使用时立即释放。尽管如此,BasicConnPool提供了受保护的方法来枚举可用的空闲连接和当前从池中释放的连接。这使消费者能够查询连接状态并选择性的终止符合特定条件的连接。

static class MyBasicConnPool extends BasicConnPool {@Overrideprotected void enumAvailable(final PoolEntryCallback<HttpHost, HttpClientConnection> callback) {super.enumAvailable(callback);}@Overrideprotected void enumLeased(final PoolEntryCallback<HttpHost, HttpClientConnection> callback) {super.enumLeased(callback);}}MyBasicConnPool connpool = new MyBasicConnPool();
connpool.enumAvailable(new PoolEntryCallback<HttpHost, HttpClientConnection>() {@Overridepublic void process(final PoolEntry<HttpHost, HttpClientConnection> entry) {Date creationTime = new Date(entry.getCreated());if (creationTime.before(someTime)) {entry.close();}}});

2.5. TLS/SSL支持

阻塞连接绑定到任意的套接字上。这使得SSL支持相当简单。任何SSLSocket实例能够被绑定到一个阻塞的连接上,以便所有的消息通过TLS/SL加密保证传输。

SSLContext sslcontext = SSLContexts.createSystemDefault();
SocketFactory sf = sslcontext.getSocketFactory();
SSLSocket socket = (SSLSocket) sf.createSocket("somehost", 443);
// Enforce TLS and disable SSL
socket.setEnabledProtocols(new String[] {"TLSv1","TLSv1.1","TLSv1.2" });
// Enforce strong ciphers
socket.setEnabledCipherSuites(new String[] {"TLS_RSA_WITH_AES_256_CBC_SHA","TLS_DHE_RSA_WITH_AES_256_CBC_SHA","TLS_DHE_DSS_WITH_AES_256_CBC_SHA" });
DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1204);
conn.bind(socket);

2.6. 嵌入式的HTTP服务

自4.4版本起,HTTPCore提供上述基于阻塞I/O的嵌入式的HTTP服务。

HttpRequestHandler requestHandler = <...>
HttpProcessor httpProcessor = <...>
SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(15000).setTcpNoDelay(true).build();
final HttpServer server = ServerBootstrap.bootstrap().setListenerPort(8080).setHttpProcessor(httpProcessor).setSocketConfig(socketConfig).setExceptionLogger(new StdErrorExceptionLogger()).registerHandler("*", requestHandler).create();
server.start();
server.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);Runtime.getRuntime().addShutdownHook(new Thread() {@Overridepublic void run() {server.shutdown(5, TimeUnit.SECONDS);}
});

三、基于NIO的异步I/O

异步I/O可能更适合那些在扩展性、资源有效管理,同时处理数千个连接的能力比原始数据吞吐量更重要的场景。异步I/O可以说更加复杂,通常在处理大量消息载荷时要特别小心。

3.1. 和其它I/O框架的不同

和其它框架解决的问题类似,但是有一定不同的特点:

  • 极简主义,针对数据密集型协议如HTTP进行优化。
  • 有效的内存管理:数据消费者只需要像处理输入数据一样多内存,不需要更多内存。
  • 如果可能的话,直接访问NIO通道。

3.2. I/O reactor

HttpCore NIO基于Doug Lea描述的Reactor模式。I/O反应器的目的是对I/O事件作出反应,并把事件通知分发给单个的I/O会话。I/O反应器模式的主要思想是从传统阻塞I/O模型强加的一个线程一个连接的模式中脱离出来。IOReactor接口代表实现反应器模式的的一个抽象对象。在内部,IOReactor的实现封装了NIO java.nio.channels.Selector的功能。

I/O反应器通常使用少量的调度线程(通常至少一个)分发I/O事件通知给大量(通常多大数千个)的I/O连接会话。通常建议每个cpu核心一个调度线程。

IOReactorConfig config = IOReactorConfig.DEFAULT;
IOReactor ioreactor = new DefaultConnectingIOReactor(config);

3.2.1. I/O dispatchers

IOReactor实现使用IOEventDispatch接口来通知特定会话暂挂事件的客户端。IOEventDispatch的所有方法在I/O反应器的调度线程上执行。因此,处理发生在事件方法上不会阻塞调度线程太久是很重要的,因为I/O反应器不能对其它的事件作出反应。

IOReactor ioreactor = new DefaultConnectingIOReactor();IOEventDispatch eventDispatch = <...>
ioreactor.execute(eventDispatch);

一般的I/O事件就像IOEventDispatch接口定义的:

  • 已连接: 当一个新的会话被创建时触发。
  • 准备输入: 当这个会话挂起在输入时触发。
  • 准备输出: 当这个会话准备输出时触发。
  • 超时: 当这个会话超时时触发。
  • 连接断开: 当这个会话结束时触发。

3.2.2. I/O反应器关闭

I/O反应器的关闭是一个复杂的过程,可能花一点时间完成。I/O反应器将在一段时间内尝试优雅地关闭所有活动的I/O会话和调度线程。如果所有的I/O会话都不能正确的关闭,I/O反应器将强制关闭剩余的会话。

IOReactor ioreactor = <...>
long gracePeriod = 3000L; // milliseconds
ioreactor.shutdown(gracePeriod);

从任何线程中调用IOReactor的shutdown(long)方法都是安全的。

3.2.3. I/O会话

IOSession接口代表两端一系列逻辑相关的数据交换。IOSession封装了NIO java.nio.channels.SelectionKey和java.nio.channels.SocketChannel。IOSession关联的通道可以用于从中读取数据和往会话中写数据。

IOSession iosession = <...>
ReadableByteChannel ch = (ReadableByteChannel) iosession.channel();
ByteBuffer dst = ByteBuffer.allocate(2048);
ch.read(dst);

3.2.4. I/O会话状态管理

I/O会话没有绑定一个执行线程,因此能够使用线程上线文保存会话状态。一个会话的所有细节必须存储在会话自己内部存储。

IOSession iosession = <...>
Object someState = <...>
iosession.setAttribute("state", someState);
...
IOSession iosession = <...>
Object currentState = iosession.getAttribute("state");

请注意如果多个会话使用共享对象,访问这些对象必须是线程安全的。

3.2.5. I/O会话事件掩码

可以通过设置事件掩码表示对特定I/O会话的特定类型的I/O事件感兴趣。

IOSession iosession = <...>
iosession.setEventMask(SelectionKey.OP_READ | SelectionKey.OP_WRITE);

还可以单独的打上OP_READ和OP_WRITE标志。

IOSession iosession = <...>
iosession.setEvent(SelectionKey.OP_READ);
iosession.clearEvent(SelectionKey.OP_READ);

如果对于的感兴趣的标志没有设置,事件通知将不会发生。

3.2.6. I/O会话缓冲

通常,I/O会话需要维护内部I/O缓冲为了在返回给消费者或者写到底层的通道之前转换输入/输出数据。HttpCore NIO内存管理基于基本原则,即消费者读取到的数据只是尽可能多的输入数据,无需分配更多的内存。这意味着,通常在一个内部或外部会话缓冲中还有一些输入数据未读。I/O反应器能够查询这些会话缓冲的状态,确保当更多数据存储到某个会话缓冲区时消费者能够正确的得到通知,从而允许消费者在能够处理时读取剩余的数据。I/O会话能够使用SessionBufferStatus接口感知外部会话缓冲状态的变化。

IOSession iosession = <...>
SessionBufferStatus myBufferStatus = <...>
iosession.setBufferStatus(myBufferStatus);
iosession.hasBufferedInput();
iosession.hasBufferedOutput();

3.2.7. I/O会话关闭

优雅的关闭一个I/O会话通过调用IOSession的close()方法允许会话有序的关闭,或者通过调用IOSession的shutdown()方法强制关闭底层的通道。两种方法的区别对于涉及某种会话终止握手(如SSL/TLS连接)类型的I/O会话是非常重要的。

3.2.8. 监听I/O反应器

ListeningIOReactor代表一个能够在一个或多个端口监听入站连接的I/O反应器。

ListeningIOReactor ioreactor = <...>
ListenerEndpoint ep1 = ioreactor.listen(new InetSocketAddress(8081) );
ListenerEndpoint ep2 = ioreactor.listen(new InetSocketAddress(8082));
ListenerEndpoint ep3 = ioreactor.listen(new InetSocketAddress(8083));
// Wait until all endpoints are up
ep1.waitFor();
ep2.waitFor();
ep3.waitFor();

一旦一个端点完全初始化,它开始接收入站连接并且传播I/O活动通知给IOEventDispatch实例。
能够在运行时获取注册的端点,查询端点的状态,如果希望可以关闭它。

ListeningIOReactor ioreactor = <...>Set<ListenerEndpoint> eps = ioreactor.getEndpoints();
for (ListenerEndpoint ep: eps) {// Still active?System.out.println(ep.getAddress());if (ep.isClosed()) {// If not, has it terminated due to an exception?if (ep.getException() != null) {ep.getException().printStackTrace();}} else {ep.close();}
}

3.2.9. 连接I/O反应器

ConnectingIOReactor代表能够和远程主机建立连接的I/O反应器。

ConnectingIOReactor ioreactor = <...>SessionRequest sessionRequest = ioreactor.connect(new InetSocketAddress("www.google.com", 80),null, null, null);

向远程主机打开一个连接往往是一个耗时的过程,可能需要过一段时间才能完成。通过SessionRequest接口来监视和控制会话初始化的过程。

// Make sure the request times out if connection
// has not been established after 1 sec
sessionRequest.setConnectTimeout(1000);
// Wait for the request to complete
sessionRequest.waitFor();
// Has request terminated due to an exception?
if (sessionRequest.getException() != null) {sessionRequest.getException().printStackTrace();
}
// Get hold of the new I/O session
IOSession iosession = sessionRequest.getSession();

SessionRequest实现期望是线程安全的。在另一个线程执行时调用IOSession的cancel()方法会话请求将立刻终止。

if (!sessionRequest.isCompleted()) {sessionRequest.cancel();
}

可以将多个可选参数传递给ConnectingIOReactor的connect()方法以便更好的控制会话的初始化过程。一个非空的本地套接字地址参数能够用于绑定一个指定的本地地址的套接字。

ConnectingIOReactor ioreactor = <...>SessionRequest sessionRequest = ioreactor.connect(new InetSocketAddress("www.google.com", 80),new InetSocketAddress("192.168.0.10", 1234),null, null);

可以提供一个附加对象,在一个新的会话上下文初始化的时候被添加上。这个对象用于传递一个初始化的处理状态给协议处理器。

SessionRequest sessionRequest = ioreactor.connect(new InetSocketAddress("www.google.com", 80),null, new HttpHost("www.google.ru"), null);IOSession iosession = sessionRequest.getSession();
HttpHost virtualHost = (HttpHost) iosession.getAttribute(IOSession.ATTACHMENT_KEY);

通常希望能够异步的对请求会话的完成作出反应,而不等待阻塞当前线程的执行。可以选择性的实现SessionRequestCallback接口去获取请求会话相关的事件通知,例如请求完成,取消,失败或者超时。

ConnectingIOReactor ioreactor = <...>SessionRequest sessionRequest = ioreactor.connect(new InetSocketAddress("www.google.com", 80), null, null,new SessionRequestCallback() {public void cancelled(SessionRequest request) {}public void completed(SessionRequest request) {System.out.println("new connection to " +request.getRemoteAddress());}public void failed(SessionRequest request) {if (request.getException() != null) {request.getException().printStackTrace();}}public void timeout(SessionRequest request) {}});

3.3. I/O反应器配置

I/O反应器默认使用系统依赖的配置,在大多数情况下足够明智。

IOReactorConfig config = IOReactorConfig.DEFAULT;
IOReactor ioreactor = new DefaultListeningIOReactor(config);

然而,在一些情况下自定义配置可能是必需的,例如,为了修改默认的套接字属性和超时时间。几乎不需要改变其它参数。

IOReactorConfig config = IOReactorConfig.custom().setTcpNoDelay(true).setSoTimeout(5000).setSoReuseAddress(true).setConnectTimeout(5000).build();
IOReactor ioreactor = new DefaultListeningIOReactor(config);

3.3.1. Queuing of I/O interest set operations

一些老的JRE实现(主要来自IBM)包括Java API文档引用的java.nio.channels.SelectionKey类的幼稚的实现。在这些JRE中java.nio.channels.SelecionKey的问题是读或写I/O兴趣设置可能无限的阻塞下去,如果I/O选择器在执行一个选择操作的过程中。HttpCore NIO能够配置I/O兴趣设置操作放入队列中,只有在I/O选择器没有在执行选择操作的时候被调度线程执行。

IOReactorConfig config = IOReactorConfig.custom().setInterestOpQueued(true).build();

3.4. I/O反应器异常处理

特定的协议异常和那些使用会话通道交互中抛出的I/O异常期望被特定的协议handler处理。这些异常可能导致单个的会话终止,但不应该影响I/O反应器和所有其它活着的会话。然而也有例外情况,当I/O反应器自己遇到一个例如在底层NIO类的I/O异常活着一个未处理的运行时异常时。这些类型的异常通常都是致命的,会引起I/O反应器自动关闭。

可以重写这些行为或者阻止I/O反应器自动关闭当内部的类发生运行时异常或者I/O异常的时候。可以提供一个IOReactorExceptionHandler接口的自定义实现来完成。

DefaultConnectingIOReactor ioreactor = <...>ioreactor.setExceptionHandler(new IOReactorExceptionHandler() {public boolean handle(IOException ex) {if (ex instanceof BindException) {// bind failures considered OK to ignorereturn true;}return false;}public boolean handle(RuntimeException ex) {if (ex instanceof UnsupportedOperationException) {// Unsupported operations considered OK to ignorereturn true;}return false;}});

不加选择的丢弃异常需要十分小心。通常让I/O反应器自己干净的关闭和重启比留下一个不一致的或者不稳定的状态更好。

3.4.1. I/O反应器审计日志

如果一个I/O反应器不能从一个I/O或者运行时异常中自动恢复,它将进入关闭模式。首先,它将关闭所有活着的监听取消所有挂起的新会话请求。然后,它将尝试优雅的关闭所有活着的I/O会话,给他们一些时间刷新挂起的输出数据干净的结束。最后,在优雅期过后它将强制关闭那些仍然或者的I/O会话。这是一个很复杂的过程,在关闭的过程中许多事情可能同时失败,许多不同的异常可能被抛出。在关闭的过程中,I/O反应器将记录所有抛出的异常在一个审计日志中,包括第一次真正引起关闭的原始异常。能够通过检查审计日志来决定重启I/O反应器是否是安全的。

DefaultConnectingIOReactor ioreactor = <...>// Give it 5 sec grace period
ioreactor.shutdown(5000);
List<ExceptionEvent> events = ioreactor.getAuditLog();
for (ExceptionEvent event: events) {System.err.println("Time: " + event.getTimestamp());event.getCause().printStackTrace();
}

3.5. 非阻塞的HTTP连接

有效的非阻塞的HTTP连接是IOSession特定HTTP功能的包装。非阻塞的HTTP连接是有状态的、不是线程安全的。非阻塞HTTP连接的输入/输出操作应该限制事件由I/O事件调度线程触发。

3.5.1. 非阻塞HTTP连接的执行上下文

非阻塞的HTTP连接没有绑定到一个特定的执行线程,因此需要获取它们自己的执行上下文。每个非阻塞的HTTP连接有一个关联的HTTPContext实例,能够用于管理处理状态。HttpContext实例是线程安全的,能够被多个线程操作。

DefaultNHttpClientConnection conn = <...>
Object myStateObject = <...>HttpContext context = conn.getContext();
context.setAttribute("state", myStateObject);

3.5.2. 使用非阻塞的HTTP连接

任何时候都能获得当前通过非阻塞的HTTP连接传输的请求和享用对象。如果当前没有正在传输的入站或出站消息,这些对象中的一个或多个可能是null。

NHttpConnection conn = <...>HttpRequest request = conn.getHttpRequest();
if (request != null) {System.out.println("Transferring request: " +request.getRequestLine());
}
HttpResponse response = conn.getHttpResponse();
if (response != null) {System.out.println("Transferring response: " +response.getStatusLine());
}

但是请注意,当前的请求和当前的响应可能不是同样的消息交换!非阻塞HTTP连接完全在全双工模式下工作。可以完全独立的处理入站和出站消息。这使得非阻塞HTTP连接完全流水线化,这也意味着匹配逻辑相关的请求和响应消息时协议handler的工作。
在客户端提交一个请求的过度简化处理可能像这样:

NHttpClientConnection conn = <...>
// Obtain execution context
HttpContext context = conn.getContext();
// Obtain processing state
Object state = context.getAttribute("state");
// Generate a request based on the state information
HttpRequest request = new BasicHttpRequest("GET", "/");conn.submitRequest(request);
System.out.println(conn.isRequestSubmitted());

在服务端提交一个响应的过度简化过程可能像这样:

NHttpServerConnection conn = <...>
// Obtain execution context
HttpContext context = conn.getContext();
// Obtain processing state
Object state = context.getAttribute("state");// Generate a response based on the state information
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,HttpStatus.SC_OK, "OK");
BasicHttpEntity entity = new BasicHttpEntity();
entity.setContentType("text/plain");
entity.setChunked(true);
response.setEntity(entity);conn.submitResponse(response);
System.out.println(conn.isResponseSubmitted());

请注意几乎不需要使用这些低级别的方法而使用合适的高级别的HTTP服务实现代替去传输消息。

3.5.3. HTTP I/O控制

所有非阻塞的HTTP连接类实现了代表在I/O事件通知中控制兴趣的连接的子功能的IOControl接口。IOControl实例期望完全线程安全。因此,在任意线程之间IOControl能用于请求/挂起 I/O事件通知。

当使用非阻塞连接进行交互时必须特别防范。HttpRequest和HttpResponse不是线程安全的。通常建议在非阻塞连接上的输入/输出操作被I/O事件调度线程执行。

下面的模式是建议的:

  • 使用IOControl接口传递控制从一个连接的I/O事件到另一个线程/会话.
  • 如果输入/输出操作需要在特定的连接执行,存储所有的需要的信息(状态)在连接上下文,通过调用IOControl的requestInput()或者IOControl的requestOutput()方法请求合适的I/O操作。
  • 使用存储在连接上下文的信息,在调度线程执行事件方法需要的操作。

请注意所有发生在事件方法的操作不应该花费太长时间,因为这时调度线程仍然阻塞在一个会话,不能处理其它会话的事件。当前会话底层的通道的I/O操作没有问题,因为它们保证是非阻塞的。

3.5.4. 非阻塞内容传输

和阻塞连接比起来非阻塞连接内容传输的处理完全不同,由于非阻塞连接需要适应NIO模型异步的本质。两种类型的连接的主要区别是不能使用平常的、内在的阻塞的java.io.InputStream和java.io.OutputStream类去代表入站和出站内容的流。HttpCore NIO提供ContentEncoder和ContentDecoder接口去处理异步内容传输的过程。非阻塞的HTTP连接将基于消息包含的实体的属性去实例化合适的内容编码解码器实现。
非阻塞HTTP连接将发起输入事件直到内容实体全部传输完。

ContentDecoder decoder = <...>
//Read data in
ByteBuffer dst = ByteBuffer.allocate(2048);
decoder.read(dst);
// Decode will be marked as complete when
// the content entity is fully transferred
if (decoder.isCompleted()) {// Done
}

非阻塞HTTP连接将发起输出事件直到内容实体被标记为全部传输完。

ContentEncoder encoder = <...>
// Prepare output data
ByteBuffer src = ByteBuffer.allocate(2048);
// Write data out
encoder.write(src);
// Mark content entity as fully transferred when done
encoder.complete();

请注意,仍然需要提供一个HttpEntity实例当提交一个包含实体的消息给非阻塞HTTP连接。该实体的属性将用于初始化一个ContentEncoder实例用于传输实体内容。非阻塞HTTP连接,然而,忽略内部的阻塞包含的实体的HttpEntity的getContent()和HttpEntity的writeTo()方法。

NHttpServerConnection conn  = <...>HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,HttpStatus.SC_OK, "OK");
BasicHttpEntity entity = new BasicHttpEntity();
entity.setContentType("text/plain");
entity.setChunked(true);
entity.setContent(null);
response.setEntity(entity);conn.submitResponse(response);

同样的,入站的包含实体的消息将有一个HttpEntity实例和它们关联,但是尝试调用HttpEntity的getContent()方法或者HttpEntity的writeTo()方法将引起java.lang.IllegalStateException。HttpEntity实例用于决定入站实体如内容长度的属性。

NHttpClientConnection conn = <...>HttpResponse response = conn.getHttpResponse();
HttpEntity entity = response.getEntity();
if (entity != null) {System.out.println(entity.getContentType());System.out.println(entity.getContentLength());System.out.println(entity.isChunked());
}

3.5.5. 非阻塞内容传输机制的支持

非阻塞HTTP连接接口的默认实现支持HTTP/1.1规范定义的三种内容传输机制:

Content-Length定界:内容实体的结束取决于Content-Length头部的值。最大的实体长度:Long的MAX_VALUE。
代码标识:通过关闭底层的连接(流结束的条件)来限定内容实体的结束。由于显而易见的原因,代码标识只能用于服务端。最大实体长度:无限。
块编码:内容以小块发送。最大实体长度:无限。
根据包含在消息中的内容实体的属性,合适的内容编码解码器将自动创建。

3.5.6. 直接I/O通道

内容编码器经过优化直接从底层的I/O会话通道读取数据或者直接写入数据,尽量避免立刻在会话缓冲中缓冲。此外,编码解码器不执行任何内容传输(例如Content-Length限制和编码解码器标识)可以利用java.nio.FileChannel方法显著的提高入站和出站时文件传输操作的性能。
如果真实的内容解码器实现了FIleContentDecoder,则可以使用它的方法读取入站内容直接到一个文件绕过中间的java.nio.ByteBuffer。

ContentDecoder decoder = <...>
//Prepare file channel
FileChannel dst;
//Make use of direct file I/O if possible
if (decoder instanceof FileContentDecoder) {long Bytesread = ((FileContentDecoder) decoder).transfer(dst, 0, 2048);// Decode will be marked as complete when// the content entity is fully transmittedif (decoder.isCompleted()) {// Done}
}

如果真实的内容编码器实现了FileContentEncoder,则能够使用它的方法直接写出站内容从一个文件绕过中间的java.nio.ByteBuffer。

ContentEncoder encoder = <...>
// Prepare file channel
FileChannel src;
// Make use of direct file I/O if possible
if (encoder instanceof FileContentEncoder) {// Write data outlong bytesWritten = ((FileContentEncoder) encoder).transfer(src, 0, 2048);// Mark content entity as fully transferred when doneencoder.complete();
}

3.6. HTTP I/O 事件调度

HTTP I/O事件调度服务于转换被I/O反应器触发的一般的I/O事件成HTTP协议规范的事件。它们依赖NHttpCientEventHandler和NHttpServerEventHandler接口传播HTTP协议事件给HTTP协议handler。
服务端HTTP I/O事件如NHttpServerEventHandler接口定义:

  • 已连接: 当一个新的入站连接被建立时触发。
  • 请求已接收: 当一个新的HTTP请求被接收时触发。连接作为参数传递给该方法保证返回一个合法的HTTP请求对象。如果接收到的请求包含一个请求实体,该方法将会紧跟着一系列的inputReady事件去传输请求内容。
  • 准备输入: 当底层的通道准备通过相应的内容解码器读取请求实体的新的部分时被触发。如果内容消费者不能够处理入站内容,输入事件通知使用IOControl接口(NHttpServerConnection的父接口)临时挂起。请注意,NHttpServerConnection和ConntentDecoder对象不是线程安全的,应该只在该方法调用的上下文中被使用。IOControl对象能够共享,当handler有能力处理更多内容的时候IOcontrol对象能够在其它线程中复活输入事件通知。
  • 准备响应: 当连接准备接收新的HTTP响应是触发。协议handler不必提交一个响应如果没有准备好。
  • 准备输出: 当底层的通道准备通过相应的内容编码器写响应实体的下一部分时被触发。如果内容生产者不能够生成出站内容,使用IOControl接口(NHttpServerConnection的父接口)输出事件通知能被临时挂起。请注意NHttpServerConnection和ContentEncoder对象不是线程安全的,应该只在该方法调用上线文中使用。IOControl对象能够被共享,当更多的内容可用时,在其它线程中使用IOcontrol对象去复活输出事件通知。
  • 异常: 当从底层的通道中读数据或者往里面写数据的时候发生I/O错误,或者当接收一个HTTP请求时违反HTTP协议时触发。
  • 超时: 当连接超过最大不活动时间没有检测到输入时触发。

客户端HTTP I/O时间如NHttpClientEventHandler接口定义的:

  • 已连接: 当一个新的出站连接被建立时触发。作为参数传递给该事件的附加对象是一个附加到会话请求的任意对象。
  • 准备请求: 当连接准备接收新的HTTP请求时触发。协议handler不必提交一个请求如果没有准备好。
  • 准备输出: 当底层的通道准备通过相应的内容编码器写请求实体的下一部分时触发。如果内容生产者不能生成出站内容,输出事件使用IOControl接口(NHttpClientConnection的父接口)被临时挂起。请注意NHttpClientConnection和ContentEncoder对象不是线程安全的,应该只在该方法被调用的上下文内使用。IOControl能被共享,当更多内容可用时,在其它线程使用IOControl复活输入事件通知。
  • 已接收到响应: 当一个HTTP响应被接收到时触发。连接作为参数被传递该方法保证返回一个合法的HTTP响应对象。如果接收到的响应包含一个响应实体,该方法会紧接着一系列的inputReadys事件去转换响应内容。
  • 准备输入: 当一个底层的通道准备通过相应的内容解码器读取响应实体的新的部分时被触发。如果内容消费者不能处理入站内容,输入事件通知通过使用IOControl接口(NHttpClientConnection)被临时挂起。请注意NHttpClientConnection和ContentDecoder对象不是线程安全的,应该只在该方法调用的上下文中使用。IOControl对象能够被共享,当handler有能力处理更多内容时,其它线程使用IOControl去复活输入事件通知。
  • 异常: 当从底层的通道读取数据或者往里面写数据时发生一个I/O错误或者当接收一个HTTP响应时发生HTTP协议错误时触发。
  • 超时: 当连接超过最大不活动时间,没有检测到输入时触发。
  • 关闭: 当连接已经被关闭时触发。

3.7. 非阻塞HTTP内容生产者

正如之前讨论的,非阻塞连接的内容转换过程和阻塞连接比起来完全不同。显而易见的原因,传统I/O抽象基于内在的阻塞java.io.InputStream和java.io.OutputStream不适合异步数据传输。为了避免无效和可能通过java.nio.channels.Channles的newChannel阻塞I/O操作重定向,HTTP实体被希望实现NIO规范的扩展接口HttpAsyncContentProducer。
HttpAsyncContentProducer接口定义了一些额外的方法为了将内容有效的传输到非阻塞HTTP连接上:

produceContent: 调用去写一个内容块到ContentEncoder。如果实体临时不能产生更多内容,IOControl接口能被使用去挂起输出事件。当所有内容完成后,生产者必须调用ContentEncoder的complete()。没有这样做可能引起实体不正确的限定。请注意ContentEncoder对象不是线程安全的,应该只在该方法调用的上线文内使用。IOControl对象能够被共享,当更多内容可用时在其它线程使用IOControl去复活输出事件通知。
isRepeatable: 决定生产者是否能够多次生产内容。可重复的内容生产者期望重建它们的内容即使被关闭后。
close: 关闭生产者并释放当前被它分配的所有资源。

3.7.1. 创建非阻塞实体

一些包含在HttpCore NIO中的HTTP实体实现支持HttpAsyncContentProducer接口:

NByteArrayEntity
NStringEntity
NFileEntity

3.7.1.1. NByteArrayEntity

这是一个简单的自包含可重复实体,从一个给定的字节数组中接收内容。这个字节数组提供给构造函数。

NByteArrayEntity entity = new NByteArrayEntity(new byte[] {1, 2, 3});

3.7.1.2. NStringEntity

这是简单的、自包含的、可重复的实体,从一个java.lang.String对象中检索数据。它有两个构造函数,一个给定字符串的简单构造函数,另一个还需要数据的java.lang.String形式的字符编码。

NStringEntity myEntity = new NStringEntity("important message",Consts.UTF_8);

3.7.1.3. NFileEntity

这个实体从一个文件中读取内容。这个类多数用于传输不同类型的大文件,因此需要提供文件的内容类型确保内容能够被接收者正确的识别和处理。

File staticFile = new File("/path/to/myapp.jar");
NFileEntity entity = new NFileEntity(staticFile,ContentType.create("application/java-archive", null));

NHttpEntity将尽可能的使用直接通道I/O,提供能够直接从一个文件传输数据到底层连接的套接字中的内容编码器。

3.8. 非阻塞HTTP协议handlers

参考文献

  • Http和Socket 优劣比较 使用场景选择_转
  • HttpCore向导
  • HttpClient 4.3详细教程

(4.2.47.1)HttpCore手机服务器相关推荐

  1. 云手机服务器系统,云手机主板 私有云手机系统 私有云手机平台 云手机服务器...

    SPECIFICATION FOR MT6771 Customer : Product model: Z750 Module Customer model: Version Designed by C ...

  2. 亚信科技虚拟手机服务器地址,亚信云手机服务器地址是什么

    亚信云手机服务器地址是什么 内容精选 换一换 华为云帮助中心,为用户提供产品简介.价格说明.购买指南.用户指南.API参考.最佳实践.常见问题.视频帮助等技术文档,帮助您快速上手使用华为云服务. 华为 ...

  3. 如何修改手机服务器的ip地址,怎么修改手机服务器ip地址

    怎么修改手机服务器ip地址 内容精选 换一换 云堡垒机的认证方式是系统全局可选择设置,即系统所有用户都可选择认证方式,包括本地认证.多因子认证(手机令牌.手机短信.USBKey.动态令牌).远程认证( ...

  4. Android之解决PC浏览器上传表单文件到手机服务器read数据错误导致有时候下载到手机的文件打开文字错乱问题

    1 .问题 手机客户端app通过java socket写了一个服务器,然后PC端通过浏览器访问服务端显示上传页面(这些资源都在手机端),然后其中有一个功能是PC端上传文件到手机服务器,然后把这个文件下 ...

  5. 无限流量手机怎样改服务器,无限流量手机服务器

    无限流量手机服务器 内容精选 换一换 云手机作为一种新型服务,依靠华为云的服务器底座.创新技术及周边服务的天然优势,让移动应用不但可以在物理手机运行,还可以在云端智能运行.相比常见的手机模拟方案,云手 ...

  6. 手机服务器共享信息,手机云共享服务器

    手机云共享服务器 内容精选 换一换 云手机(Cloud Phone,简称CPH),是基于华为云裸金属服务器虚拟出的带有原生安卓操作系统,同时具有虚拟手机功能的云服务器.简单来说,云手机=云服务器+An ...

  7. 手机服务器异常修复,手机服务器异常

    手机服务器异常 内容精选 换一换 正常返回值说明200 OKGET和PUT操作正常返回.201 CreatedPOST操作正常返回.异常返回值说明400 Bad Request请求参数错误403 Fo ...

  8. 灵活使用手机之-手机服务器和客户端

    灵活使用手机之-手机服务器和客户端 2020-12-15 近期玩手机少了,更多的精力用来学习未来必将大放光明的linux系统,一个redhat系列,一个ubuntu系列,把自己搞的晕头晕脑,虽然了解的 ...

  9. 用AndServer做安卓手机服务器

    用AndServer搭建手机服务器以及跨域问题解决 AndServer官方文档地址 AndServer简介 什么情况需要用? AndServer官方文档地址 https://yanzhenjie.co ...

最新文章

  1. php无法引用,PHP错误:“无法通过引用传递参数2”
  2. 【计算理论】计算复杂性 ( 时间复杂度时间单位 : 步数 | 算法分析 | 算法复杂性分析 )
  3. Redis进阶-如何从海量的 key 中找出特定的key列表 Scan详解
  4. Android四大组件ContentProvider
  5. 二叉树的锯齿形层序遍历Python解法
  6. [BZOJ2125]最短路(圆方树DP)
  7. php左侧菜单栏递归代码,js实现左侧菜单栏递归循环遍历
  8. 宁滨院士生前最后一场学术报告
  9. 文件操作命令(TYPE)
  10. vue2.0 实现导航守卫(路由守卫)
  11. Spring JavaConfig
  12. H3C 网管交换机快速配置指南(转)
  13. JavaScript实现map
  14. 软件开发生命周期 --瀑布模型
  15. supergo定位软件_super go定位修改下载
  16. Mac os X 常用技巧
  17. 但行好事 莫问前程(五月)
  18. STM32仿真器下载配置
  19. 小米4降级android版本,小米MIUI不再允许降级,小心手机变砖
  20. IntelliJ inspection gives “Cannot resolve symbol“ but still compiles code

热门文章

  1. 方舟服务器在线人数查询软件,Steam游戏该如何保持在线人数?方舟生存进化告诉你答案!...
  2. (附源码)计算机毕业设计仓库物资信息可视化管理系统
  3. 【数字图像分析】中科大2022-2023秋 往年考题汇总
  4. TI单芯片毫米波雷达代码走读(十)—— 距离维(1D)FFT
  5. vim 块选择 多行操作
  6. 2020下半年新机最新消息_2020年12月新机发布时间表_2020年12月手机发布会有哪些...
  7. Ubuntu 20.04 打开WPS时间过长解决办法
  8. virbr0网卡恢复
  9. HLS笔记——HLS的接口描述
  10. Vue cli3配置生产环境,开发环境,和测试环境