续(六)
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. I/O兴趣集操作排队
一些老的JRE实现(主要来自IBM)包含了Java AIP文档中所设计到的java.nio.channels.SelectionKey类的一个简单的实现。在这样的JREs中java.nio.channels.SelectionKey的问题是如果I/O选择器在一个选择操作的执行过程中的话,那么读或写这样的I/O兴趣集可能被无限期阻塞。HttpCore NIO能够被配置为特殊的模式去操作,其中I/O兴趣集操作只有当I/O选择器没有忙于一个选择操作时才被在分派线程上排队并执行。
*****************************************************
* *
* IOReactorConfig config = IOReactorConfig.custom() *
* .setInterestOpQueued(true) *
* .build(); *
* *
*****************************************************
3.4. I/O反应堆异常处理
协议特定的异常与那些在跟会话的通道交互中被抛出的并被特定的协议处理者捕获和处理的I/O异常是一样的。这些异常可能会导致一个单独的会话终结,但是不应当影响I/O反应堆和其它所有的活动会话。然而,有一些情况,当反应堆自身遇到一个内部的问题,例如一个位于底层NIO类的I/O异常或一个未处理的运行时异常。这些类型的异常通常是毁灭性的并且将会引起I/O反应堆自动关闭。
可以覆盖这个行为进而预防I/O反应堆在发生运行时异常或内部类I/O异常时自动关闭。能够通过提供一个IOReactorExceptionHandler接口的自定义实现来完成。
*******************************************************************
* *
* DefaultConnectionIOReactor ioreactor = <...> *
* ioreactor.setExceptionHandler(new IOReactorExceptionHandler() { *
* public boolean handle(IOException ex) { *
* if (ex instanceof BindException) { *
* // bind failures considered OK to ignore *
* return true; *
* } *
* return false; *
* } *
* public boolean handle(RuntimeException ex) { *
* if (ex instanceof UnsupportedOperationException) { *
* // Unsupported operations considered OK to ignore *
* return true; *
* } *
* return false; *
* } *
* }); *
* *
*******************************************************************
关于任意地丢弃异常需要非常地小心。让I/O反应堆清晰地关闭自身通常是更好的并且宁可重启也不愿它处在一个不一致或不稳定的状态。
3.4.1. I/O反应堆审计日志
如果一个I/O反应堆不能够从一个I/O异常或一个运行时异常中恢复回来它将会进入关闭模式。刚开始,它将会关闭所有的活动监听器并且取消即将发生的心会话请求。接着它将尝试温柔地关闭所有的活动I/O会话并给予它们一些时间去刷新即将输出的数据和清楚地终止。最后,它将会在宽限期之后强制关闭那些依旧残留的活动I/O会话。这是一个相当复杂的过程,与此同时在这里有很多会失败并且有许多不同的异常会在关闭过程中被抛出。I/O反应堆会记录所有的在关闭过程中的抛出异常,包含起初最原始的哪一个真正导致关闭的,到一个审计日志。可以检查审计日志并且决定重启I/O反应堆是否是安全的。
************************************************************
* *
* DefaultConnectiongIOReactor ioreactor = <...> *
* // Give it 5 sec grace period *
* ioreactor.shutdown(5000); *
* List events = ioreactor.getAuditLog(); *
* for (ExceptionEvent event: events) { *
* System.err.println("Time: " + event.getTimestamp()); *
* event.getCause().printStackTrace(); *
* } *
* *
************************************************************
3.5. 非阻塞的HTTPS连接
实际上非阻塞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连接有完全的流水线能力,但是与此同时匹配请求和响应消息的逻辑相关性就是协议处理者的一个工作了。
简化的客户端上的一个请求的提交过程可能看起来像这样:
***********************************************************
* *
* 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连接类都实现IOControl接口,which represents a subset of connection functionality for controlling interest in I/O even notifications.(后半句不知道怎么翻译合适)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实例。实体的属性将被用于初始化被用于传输实体内容的一个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连接接口的实现支持3种定义于HTTP/1.1规范之中的内容传输机制:
* Content-Length限制:内容实体的结尾通过Content-Length头的值被决定。最大实体长度:Long#MAX_VALUE
* 标识编码:内容实体的结尾通过底层连接(尾流条件)的关闭而界定。显而易见标识编码只能被用于服务器端。最大实体长度:无限制
* 块编码:内容被分为小块然后发送。最大实体长度:无限制
响应的内容编解码器将依据附加消息实体的属性被自动地创建。
3.5.6. 直接隧道I/O
内容编码对直接从底层I/O会话的通道读取或写入数据进行了优化,无论何时都要避免一个会话缓冲中的中间缓存。此外,那些没有执行任何内容转换的编解码器(例如,Content-Length限制和标识编码)能利用NIO java.nio.channels.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 transmitted *
* if (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 out *
* long bytesWritten = ((FileContentEncoder) encoder) *
* .transfer(src, 0, 2048); *
* // Mark content entity as fully transferred when done *
* encoder.complete(); *
* } *
* *
*************************************************************
3.6. HTTP I/O事件分派器
HTTP I/O事件分派器用来转换由一个I/O反应堆触发的普通的I/O事件到HTTP协议指定的事件。它们依赖NHttpClientEventHandler和NHttpServerEventHandler接口传播HTTP协议事件给HTTP协议处理者。
服务端HTTP I/O事件由NHttpServerEventHandler接口定义:
* connected: 当一个新的到埠连接已经被创建时触发。
* requestReceived: 当接收一个新的HTTP请求时触发。连接被当作参数传给这个方法以保证返回一个合法的HTTP请求对象。如果接收的请求附加一个请求实体,那么这个方法将会随着一系列inputReady事件去转换请求内容。
* inputReady: 当底层通道为通过相应的内容解码器读取请求实体的一个新部分做准备时触发。如果内容消费者不能处理到埠请求,那么使用IOControl接口(NHttpServerConnection的父接口)可以临时挂起输入事件通知。请注意NHttpServerConnection和ContentDecoder对象是非线程安全的并且只能被用在本方法调用的上下文环境中。当处理者有能力处理更多的内容时IOControl对象能够被共享和从其它线程中用以恢复输入事件通知。
* responseReady: 当连接准备接收新的HTTP响应时触发。协议处理者不是必须要提交一个响应如果它还没有准备好的话。
* outputReady: 当底层通道准备通过相应的内容编码器写入响应中接下来的部分时触发。如果内容生产者不能产生离埠内容,那么使用IOControl接口(NHttpServerConnection的父接口)可以临时挂起输出事件通知。请注意NHttpServerConnection和ContentEncoder对象是非线程安全的并且只能被用在本方法调用的上下文环境中。当更多的内容变得可用时IOControl对象能够被共享和从其它线程中用以恢复输出事件通知。
* exception: 当从底层通道读取或写入时发生一个I/O错误,或者当接收一个HTTP请求时发生一个HTTP协议违反时触发。
* timeout: 当在这个连接上超出最大休止期也没有侦测到输入时触发。
* closed: 当连接已经被关闭时触发。
客户端HTTP I/O事件由NHttpClientEventHandler接口定义:
* connected: 当一个新的离埠连接易经创建完时触发。被当做参数传递到这个事件的附件对象是一个要附加到会话请求的任意对象。
* requestReady: 当连接准备接收新HTTP请求时触发。协议处理者不是必须要提交一个请求如果它还没准备好的话。
* outputReady: 当底层通道准备通过相应的内容编码器写入请求实体中接下来的部分时触发。如果内容生产者不能产生离埠内容,那么使用IOControl接口(NHttpClientConnection接口的父接口)可以临时挂起输出事件通知。请注意NHttpClientConnection和ContentEncoder对象是非线程安全的并且只能被用在本方法调用的上下文环境中。当更多的内容变得可用时IOControl对象能够被共享和从其它线程中用以恢复输出事件通知。
* responseReceived: 当一个HTTP响应被接收时触发。连接被当做参数传递给这个方法以保证返回一个合法的HTTP响应对象。如果接收的响应附加一个响应实体,那么这个方法将会随着一系列inputReady事件去转换响应内容。
* inputReady: 当底层通道为通过相应的内容解码器读取响应实体的一个新部分做准备时触发。如果内容消费者不能处理到埠内容,那么使用IOControl接口(NHttpClientConnection的父接口)可以临时挂起输入事件通知。请注意NHttpServerConnection和ContentDecoder对象是非线程安全的并且只能被用在本方法调用的上下文环境中。当处理者有能力处理更多的内容时IOControl对象能够被共享和从其它线程中用以恢复输入事件通知。
* exception: 在读取或写入底层通道期间发生一个I/O错误或者当接收一个HTTP响应期间发生一个HTTP协议违反时触发。
* timeout: 当在这个连接上超出最大休止期也没有侦测到输入时触发。
* closed: 当连接已经被关闭时触发。
3.7. 非阻塞HTTP内容生产者
如前所述,非阻塞链接对内容转换处理的工作方式与阻塞连接相比完全不同。显而易见基于天生阻塞的java.io.InputStream和java.io.OutputStream类的经典I/O抽象是不能很好的适应异步数据转换的。为了避免通过java.nio.channels.Channles#newChannel重定向时无效的和潜在的阻塞I/O操作,非阻塞HTTP实体被期望去实现NIO指定的扩展接口HttpAsyncContentProducer。
HttpAsyncContentProducer接口为内容到非阻塞HTTP连接的有效流转定义了若干个附加的方法。
* produceCentent:调用之后写出分块内容到ContentEncoder。IOControl接口能被用于挂起输出事件如果实体临时不能产生更多内容。当所有内容都处理完,生产者必须调用ContentEncoder#complete()。失败的行为很可能引发实体被错误的界定。请注意ContentEncoder对象是非线程安全的并且只应当被用于本方法调用的上下文环境中。当更多的内容变的可用时IOControl对象能够被共享和从其他线程中用于恢复输出事件通知。
* isRepeatable:确定这个生产者是否有能力不止一次地生产内容。可重复的内容生产者被期望能够重新创建他们的内容甚至在已经关闭之后。
* close:关闭生产者并释放所有由其分配的当前资源。
3.7.1. 创建非阻塞实体
包含在HttpCore NIO中的若干个HTTP实体实现支持HttpAsyncContentProtentProducer接口:
* 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对象。它有2个构造器,一个简单的构造器有一个给出的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协议处理者
3.8.1. 异步HTTP服务
HttpAsyncService是一个完全异步的基于非阻塞(NIO)I/O模型的HTTP服务端协议处理者。HttpAsyncService把由NHttpServerEventHandler接口激发的单独的事件转化为逻辑相关的HTTP消息交互。
当接收到一个到埠请求时HttpAsyncService使用HttpAsyncExceptionVerifier核查消息对服务端期望的服从性,如果提供了的话,接下来HttpAsyncRequestHandlerResolver被用于解析请求URI到一个特定的打算要处理这个请求和给定URI的HttpAsyncRequestHandler。协议处理者使用被选定的HttpAsyncRequestHandler实例去处理到埠请求和去产生一个离埠响应。
HttpAsyncService依赖HttpProcessor去为所有的离埠消息产生强制性的协议头还有应用通用的,交叉消息转换到所有的到埠和离埠消息,然而单个的HTTP请求处理者被期望去实现应用指定的内容生产和处理。
*******************************************************************************
* *
* HttpProcessor httpproc = HttpProcessorBuilder.create() *
* .add(new ResponseDate()) *
* .add(new ResponseServer("MyServer-HTTP/1.1")) *
* .add(new ResponseContent()) *
* .add(new ResponseConnControl()) *
* .build(); *
* HttpAsyncService protocolHandler = new HttpAsyncService(httpproc, null); *
* IOEventDispatch ioEventDispatch = new DefaultHttpServerIODispatch( *
* protocolHandler, *
* new DefaultNHttpServerConnectionFactory(ConnectionConfig.DEFAULT)); *
* ListeningIOReactor ioreactor = new DefaultListeningIOReactor(); *
* ioreactor.execute(ioEventDispatch); *
* *
*******************************************************************************
未完待续。。。
阅读(2716) | 评论(0) | 转发(0) |