blob: 0700e9b956782896095b24415140b65826021e56 [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE preface PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<!--
====================================================================
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
====================================================================
-->
<chapter id="nio">
<title>NIO extensions</title>
<section>
<title>Benefits and shortcomings of the non-blocking I/O model</title>
<para>
Contrary to the popular belief, the performance of NIO in terms of raw data throughput is
significantly lower than that of blocking I/O. NIO does not necessarily fit all use cases
and should be used only where appropriate:
</para>
<itemizedlist>
<listitem>
<para>
handling of thousands of connections, a significant number of which can be idle.
</para>
</listitem>
<listitem>
<para>
handling high latency connections.
</para>
</listitem>
<listitem>
<para>
request / response handling needs to be decoupled.
</para>
</listitem>
</itemizedlist>
</section>
<section>
<title>Differences from other NIO frameworks</title>
<para>
Solves similar problems as other frameworks, but has certain distinct features:
</para>
<itemizedlist>
<listitem>
<para>
minimalistic, optimized for data volume intensive protocols such as HTTP.
</para>
</listitem>
<listitem>
<para>
efficient memory management: data consumer can read only as much input data as it
can process without having to allocate more memory.
</para>
</listitem>
<listitem>
<para>
direct access to the NIO channels where possible.
</para>
</listitem>
</itemizedlist>
</section>
<section>
<title>I/O reactor</title>
<para>
HttpCore NIO is based on the Reactor pattern as described by Doug Lea. The purpose of I/O
reactors is to react to I/O events and to dispatch event notifications to individual I/O
sessions. The main idea of I/O reactor pattern is to break away from the one thread per
connection model imposed by the classic blocking I/O model. The <interfacename>IOReactor
</interfacename> interface represents an abstract object which implements the Reactor pattern.
Internally, <interfacename>IOReactor</interfacename> implementations encapsulate
functionality of the NIO <classname>java.nio.channels.Selector</classname>.
</para>
<para>
I/O reactors usually employ a small number of dispatch threads (often as few as one) to
dispatch I/O event notifications to a much greater number (often as many as several
thousands) of I/O sessions or connections. It is generally recommended to have one dispatch
thread per CPU core.
</para>
<programlisting><![CDATA[
HttpParams params = new BasicHttpParams();
int workerCount = 2;
IOReactor ioreactor = new DefaultConnectingIOReactor(workerCount,
params);
]]></programlisting>
<section>
<title>I/O dispatchers</title>
<para>
<interfacename>IOReactor</interfacename> implementations make use of the
<interfacename>IOEventDispatch</interfacename> interface to notify clients of events
pending for a particular session. All methods of the <interfacename>IOEventDispatch
</interfacename> are executed on a dispatch thread of the I/O reactor. Therefore, it is
important that processing that takes place in the event methods will not block the
dispatch thread for too long, as the I/O reactor will be unable to react to other
events.
</para>
<programlisting><![CDATA[
HttpParams params = new BasicHttpParams();
IOReactor ioreactor = new DefaultConnectingIOReactor(2, params);
IOEventDispatch eventDispatch = new MyIOEventDispatch();
ioreactor.execute(eventDispatch);
]]></programlisting>
<para>
Generic I/O events as defined by the <interfacename>IOEventDispatch</interfacename>
interface:
</para>
<itemizedlist>
<listitem>
<formalpara>
<title><methodname>connected</methodname>:</title>
<para>
Triggered when a new session has been created.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>inputReady</methodname>:</title>
<para>
Triggered when the session has pending input.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>outputReady</methodname>:</title>
<para>
Triggered when the session is ready for output.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>timeout</methodname>:</title>
<para>
Triggered when the session has timed out.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>disconnected</methodname>:</title>
<para>
Triggered when the session has been terminated.
</para>
</formalpara>
</listitem>
</itemizedlist>
</section>
<section>
<title>I/O reactor shutdown</title>
<para>
The shutdown of I/O reactors is a complex process and may usually take a while to
complete. I/O reactors will attempt to gracefully terminate all active I/O sessions and
dispatch threads approximately within the specified grace period. If any of the I/O
sessions fails to terminate correctly, the I/O reactor will forcibly shut down
remaining sessions.
</para>
<programlisting><![CDATA[
long gracePeriod = 3000L; // milliseconds
ioreactor.shutdown(gracePeriod);
]]></programlisting>
<para>
The <methodname>IOReactor#shutdown(long)</methodname> method is safe to call from any
thread.
</para>
</section>
<section>
<title>I/O sessions</title>
<para>
The <interfacename>IOSession</interfacename> interface represents a sequence of
logically related data exchanges between two end points. <interfacename>IOSession
</interfacename> encapsulates functionality of NIO <classname>
java.nio.channels.SelectionKey</classname> and <classname>
java.nio.channels.SocketChannel</classname>. The channel associated with the
<interfacename>IOSession</interfacename> can be used to read data from and write data
to the session.
</para>
<programlisting><![CDATA[
IOSession iosession;
ReadableByteChannel ch = (ReadableByteChannel) iosession.channel();
ByteBuffer dst = ByteBuffer.allocate(2048);
ch.read(dst);
]]></programlisting>
</section>
<section>
<title>I/O session state management</title>
<para>
I/O sessions are not bound to an execution thread, therefore one cannot use the context
of the thread to store a session's state. All details about a particular session must
be stored within the session itself.
</para>
<programlisting><![CDATA[
IOSession iosession;
Object someState;
iosession.setAttribute("state", someState);
Object currentState = iosession.getAttribute("state");
]]></programlisting>
<para>
Please note that if several sessions make use of shared objects, access to those
objects must be made thread-safe.
</para>
</section>
<section>
<title>I/O session event mask</title>
<para>
One can declare an interest in a particular type of I/O events for a particular I/O
session by setting its event mask.
</para>
<programlisting><![CDATA[
IOSession iosession;
iosession.setEventMask(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
]]></programlisting>
<para>
One can also toggle <literal>OP_READ</literal> and <literal>OP_WRITE</literal> flags
individually.
</para>
<programlisting><![CDATA[
iosession.setEvent(SelectionKey.OP_READ);
iosession.clearEvent(SelectionKey.OP_READ);
]]></programlisting>
<para>
Event notifications will not take place if the corresponding interest flag is not set.
</para>
</section>
<section>
<title>I/O session buffers</title>
<para>
Quite often I/O sessions need to maintain internal I/O buffers in order to transform
input / output data prior to returning it to the consumer or writing it to the
underlying channel. Memory management in HttpCore NIO is based on the fundamental
principle that the data consumer can read only as much input data as it can process
without having to allocate more memory. That means, quite often some input data may
remain unread in one of the internal or external session buffers. The I/O reactor can
query the status of these session buffers, and make sure the consumer gets notified
correctly as more data gets stored in one of the session buffers, thus allowing the
consumer to read the remaining data once it is able to process it. I/O sessions can be
made aware of the status of external session buffers using the <interfacename>
SessionBufferStatus</interfacename> interface.
</para>
<programlisting><![CDATA[
IOSession iosession;
SessionBufferStatus myBufferStatus = new MySessionBufferStatus();
iosession.setBufferStatus(myBufferStatus);
iosession.hasBufferedInput();
iosession.hasBufferedOutput();
]]></programlisting>
</section>
<section>
<title>I/O session shutdown</title>
<para>
One can close an I/O session gracefully by calling <methodname>IOSession#close()
</methodname> allowing the session to be closed in an orderly manner or by calling
<methodname>IOSession#shutdown()</methodname> to forcibly close the underlying channel.
The distinction between two methods is of primary importance for those types of I/O
sessions that involve some sort of a session termination handshake such as SSL/TLS
connections.
</para>
</section>
<section>
<title>Listening I/O reactors</title>
<para>
<interfacename>ListeningIOReactor</interfacename> represents an I/O reactor capable of
listening for incoming connections on one or several ports.
</para>
<programlisting><![CDATA[
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();
]]></programlisting>
<para>
Once an endpoint is fully initialized it starts accepting incoming connections and
propagates I/O activity notifications to the <interfacename>IOEventDispatch
</interfacename> instance.
</para>
<para>
One can obtain a set of registered endpoints at runtime, query the status of an
endpoint at runtime, and close it if desired.
</para>
<programlisting><![CDATA[
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();
}
}
]]></programlisting>
</section>
<section>
<title>Connecting I/O reactors</title>
<para>
<interfacename>ConnectingIOReactor</interfacename> represents an I/O reactor capable of
establishing connections with remote hosts.
</para>
<programlisting><![CDATA[
ConnectingIOReactor ioreactor;
SessionRequest sessionRequest = ioreactor.connect(
new InetSocketAddress("www.google.com", 80),
null, null, null);
]]></programlisting>
<para>
Opening a connection to a remote host usually tends to be a time consuming process and
may take a while to complete. One can monitor and control the process of session
initialization by means of the <interfacename>SessionRequest</interfacename>interface.
</para>
<programlisting><![CDATA[
// 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();
]]></programlisting>
<para>
<interfacename>SessionRequest</interfacename> implementations are expected to be
thread-safe. Session request can be aborted at any time by calling <methodname>
IOSession#cancel()</methodname> from another thread of execution.
</para>
<programlisting><![CDATA[
if (!sessionRequest.isCompleted()) {
sessionRequest.cancel();
}
]]></programlisting>
<para>
One can pass several optional parameters to the <methodname>
ConnectingIOReactor#connect()</methodname> method to exert a greater control over the
process of session initialization.
</para>
<para>
A non-null local socket address parameter can be used to bind the socket to a specific
local address.
</para>
<programlisting><![CDATA[
ConnectingIOReactor ioreactor;
SessionRequest sessionRequest = ioreactor.connect(
new InetSocketAddress("www.google.com", 80),
new InetSocketAddress("192.168.0.10", 1234),
null, null);
]]></programlisting>
<para>
One can provide an attachment object, which will be added to the new session's context
upon initialization. This object can be used to pass an initial processing state to
the protocol handler.
</para>
<programlisting><![CDATA[
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);
]]></programlisting>
<para>
It is often desirable to be able to react to the completion of a session request
asynchronously without having to wait for it, blocking the current thread of execution.
One can optionally provide an implementation <interfacename>SessionRequestCallback
</interfacename> interface to get notified of events related to session requests, such
as request completion, cancellation, failure or timeout.
</para>
<programlisting><![CDATA[
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) {
}
});
]]></programlisting>
</section>
<section>
<title>Queuing of I/O interest set operations</title>
<para>
Several older JRE implementations (primarily from IBM) include what Java API
documentation refers to as a naive implementation of the <classname>
java.nio.channels.SelectionKey</classname> class. The problem with <classname>
java.nio.channels.SelectionKey</classname> in such JREs is that reading or writing
of the I/O interest set may block indefinitely if the I/O selector is in the process
of executing a select operation. HttpCore NIO can be configured to operate in a special
mode wherein I/O interest set operations are queued and executed by on the dispatch
thread only when the I/O selector is not engaged in a select operation.
</para>
<programlisting><![CDATA[
HttpParams params = new BasicHttpParams();
NIOReactorParams.setInterestOpsQueueing(params, true);
ListeningIOReactor ioreactor = new DefaultListeningIOReactor(2, params);
]]></programlisting>
</section>
</section>
<section>
<title>I/O reactor exception handling</title>
<para>
Protocol specific exceptions as well as those I/O exceptions thrown in the course of
interaction with the session's channel are to be expected and are to be dealt with by specific
protocol handlers. These exceptions may result in termination of an individual session but
should not affect the I/O reactor and all other active sessions. There are situations,
however, when the I/O reactor itself encounters an internal problem such as an I/O
exception in the underlying NIO classes or an unhandled runtime exception. Those types of
exceptions are usually fatal and will cause the I/O reactor to shut down automatically.
</para>
<para>
There is a possibility to override this behaviour and prevent I/O reactors from shutting
down automatically in case of a runtime exception or an I/O exception in internal classes.
This can be accomplished by providing a custom implementation of the <interfacename>
IOReactorExceptionHandler</interfacename> interface.
</para>
<programlisting><![CDATA[
DefaultConnectingIOReactor 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;
}
});
]]></programlisting>
<para>
One needs to be very careful about discarding exceptions indiscriminately. It is often much
better to let the I/O reactor shut down itself cleanly and restart it rather than leaving
it in an inconsistent or unstable state.
</para>
<section>
<title>I/O reactor audit log</title>
<para>
If an I/O reactor is unable to automatically recover from an I/O or a runtime exception
it will enter the shutdown mode. First off, it will close all active listeners and
cancel all pending new session requests. Then it will attempt to close all active I/O
sessions gracefully giving them some time to flush pending output data and terminate
cleanly. Lastly, it will forcibly shut down those I/O sessions that still remain active
after the grace period. This is a fairly complex process, where many things can fail at
the same time and many different exceptions can be thrown in the course of the shutdown
process. The I/O reactor will record all exceptions thrown during the shutdown process,
including the original one that actually caused the shutdown in the first place, in an
audit log. One can examine the audit log and decide whether it is safe to restart the
I/O reactor.
</para>
<programlisting><![CDATA[
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();
}
]]></programlisting>
</section>
</section>
<section>
<title>Non-blocking HTTP connections</title>
<para>
Effectively non-blocking HTTP connections are wrappers around <interfacename>IOSession
</interfacename> with HTTP specific functionality. Non-blocking HTTP connections are
stateful and not thread-safe. Input / output operations on non-blocking HTTP connections
should be restricted to the dispatch events triggered by the I/O event dispatch thread.
</para>
<section>
<title>Execution context of non-blocking HTTP connections</title>
<para>
Non-blocking HTTP connections are not bound to a particular thread of execution and
therefore they need to maintain their own execution context. Each non-blocking HTTP
connection has an <interfacename>HttpContext</interfacename> instance associated with
it, which can be used to maintain a processing state. The <interfacename>HttpContext
</interfacename> instance is thread-safe and can be manipulated from multiple threads.
</para>
<programlisting><![CDATA[
// Get non-blocking HTTP connection
DefaultNHttpClientConnection conn;
// State
Object myStateObject;
HttpContext context = conn.getContext();
context.setAttribute("state", myStateObject);
]]></programlisting>
</section>
<section>
<title>Working with non-blocking HTTP connections</title>
<para>
At any point of time one can obtain the request and response objects currently being
transferred over the non-blocking HTTP connection. Any of these objects, or both, can
be null if there is no incoming or outgoing message currently being transferred.
</para>
<programlisting><![CDATA[
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());
}
]]></programlisting>
<para>
However, please note that the current request and the current response may not
necessarily represent the same message exchange! Non-blocking HTTP connections can
operate in a full duplex mode. One can process incoming and outgoing messages
completely independently from one another. This makes non-blocking HTTP connections
fully pipelining capable, but at same time implies that this is the job of the protocol
handler to match logically related request and the response messages.
</para>
<para>
Over-simplified process of submitting a request on the client side may look like this:
</para>
<programlisting><![CDATA[
// Obtain HTTP connection
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());
]]></programlisting>
<para>
Over-simplified process of submitting a response on the server side may look like this:
</para>
<programlisting><![CDATA[
// Obtain HTTP connection
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());
]]></programlisting>
<para>
Please note that one should rarely need to transmit messages using these low level
methods and should use appropriate higher level HTTP service implementations instead.
</para>
</section>
<section>
<title>HTTP I/O control</title>
<para>
All non-blocking HTTP connections classes implement <interfacename>IOControl
</interfacename> interface, which represents a subset of connection functionality for
controlling interest in I/O even notifications. <interfacename>IOControl
</interfacename> instances are expected to be fully thread-safe. Therefore
<interfacename>IOControl</interfacename> can be used to request / suspend I/O event
notifications from any thread.
</para>
<para>
One must take special precautions when interacting with non-blocking connections.
<interfacename>HttpRequest</interfacename> and <interfacename>HttpResponse
</interfacename>are not thread-safe. It is generally advisable that all input / output
operations on a non-blocking connection are executed from the I/O event dispatch
thread.
</para>
<para>
The following pattern is recommended:
</para>
<itemizedlist>
<listitem>
<para>
Use <interfacename>IOControl</interfacename> interface to pass control over
connection's I/O events to another thread / session.
</para>
</listitem>
<listitem>
<para>
If input / output operations need be executed on that particular connection,
store all the required information (state) in the connection context and
request the appropriate I/O operation by calling <methodname>
IOControl#requestInput()</methodname> or <methodname>IOControl#requestOutput()
</methodname> method.
</para>
</listitem>
<listitem>
<para>
Execute the required operations from the event method on the dispatch thread
using information stored in connection context.
</para>
</listitem>
</itemizedlist>
<para>
Please note all operations that take place in the event methods should not block for
too long, because while the dispatch thread remains blocked in one session, it is
unable to process events for all other sessions. I/O operations with the underlying
channel of the session are not a problem as they are guaranteed to be non-blocking.
</para>
</section>
<section>
<title>Non-blocking content transfer</title>
<para>
The process of content transfer for non-blocking connections works completely
differently compared to that of blocking connections, as non-blocking connections need
to accommodate to the asynchronous nature of the NIO model. The main distinction
between two types of connections is inability to use the usual, but inherently blocking
<classname>java.io.InputStream</classname> and <classname>java.io.OutputStream
</classname> classes to represent streams of inbound and outbound content. HttpCore NIO
provides <interfacename>ContentEncoder</interfacename> and <interfacename>
ContentDecoder</interfacename> interfaces to handle the process of asynchronous content
transfer. Non-blocking HTTP connections will instantiate the appropriate implementation
of a content codec based on properties of the entity enclosed with the message.
</para>
<para>
Non-blocking HTTP connections will fire input events until the content entity is fully
transferred.
</para>
<programlisting><![CDATA[
//Obtain content decoder
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
}
]]></programlisting>
<para>
Non-blocking HTTP connections will fire output events until the content entity is
marked as fully transferred.
</para>
<programlisting><![CDATA[
// Obtain content encoder
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();
]]></programlisting>
<para>
Please note, one still has to provide an HttpEntity instance when submitting an entity
enclosing message to the non-blocking HTTP connection. Properties of that entity will
be used to initialize an <interfacename>ContentEncoder</interfacename> instance to be
used for transferring entity content. Non-blocking HTTP connections, however, ignore
inherently blocking <methodname>HttpEntity#getContent()</methodname> and <methodname>
HttpEntity#writeTo()</methodname> methods of the enclosed entities.
</para>
<programlisting><![CDATA[
// Obtain HTTP connection
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);
]]></programlisting>
<para>
Likewise, incoming entity enclosing message will have an <interfacename>HttpEntity
</interfacename> instance associated with them, but an attempt to call <methodname>
HttpEntity#getContent()</methodname> or <methodname>HttpEntity#writeTo()</methodname>
methods will cause an <classname>java.lang.IllegalStateException</classname>. The
<interfacename>HttpEntity</interfacename> instance can be used to determine properties
of the incoming entity such as content length.
</para>
<programlisting><![CDATA[
// Obtain HTTP connection
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());
}
]]></programlisting>
</section>
<section>
<title>Supported non-blocking content transfer mechanisms</title>
<para>
Default implementations of the non-blocking HTTP connection interfaces support three
content transfer mechanisms defined by the HTTP/1.1 specification:
</para>
<itemizedlist>
<listitem>
<formalpara>
<title><literal>Content-Length</literal> delimited:</title>
<para>
The end of the content entity is determined by the value of the
<literal>Content-Length</literal> header. Maximum entity length:
<methodname>Long#MAX_VALUE</methodname>.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title>Identity coding:</title>
<para>
The end of the content entity is demarcated by closing the underlying
connection (end of stream condition). For obvious reasons the identity encoding
can only be used on the server side. Max entity length: unlimited.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title>Chunk coding:</title>
<para>
The content is sent in small chunks. Max entity length: unlimited.
</para>
</formalpara>
</listitem>
</itemizedlist>
<para>
The appropriate content codec will be created automatically depending on properties of
the entity enclosed with the message.
</para>
</section>
<section>
<title>Direct channel I/O</title>
<para>
Content codes are optimized to read data directly from or write data directly to the
underlying I/O session's channel, whenever possible avoiding intermediate buffering in
a session buffer. Moreover, those codecs that do not perform any content transformation
(<literal>Content-Length</literal> delimited and identity codecs, for example) can
leverage NIO <classname>java.nio.FileChannel</classname> methods for significantly
improved performance of file transfer operations both inbound and outbound.
</para>
<para>
If the actual content decoder implements <interfacename>FileContentDecoder
</interfacename> one can make use of its methods to read incoming content directly to a
file bypassing an intermediate <classname>java.nio.ByteBuffer</classname>.
</para>
<programlisting><![CDATA[
//Obtain content decoder
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
}
}
]]></programlisting>
<para>
If the actual content encoder implements <interfacename>FileContentEncoder
</interfacename> one can make use of its methods to write outgoing content directly
from a file bypassing an intermediate <classname>java.nio.ByteBuffer</classname>.
</para>
<programlisting><![CDATA[
// Obtain content encoder
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();
}
]]></programlisting>
</section>
</section>
<section>
<title>HTTP I/O event dispatchers</title>
<para>
HTTP I/O event dispatchers serve to convert generic I/O events triggered by an I/O reactor
to HTTP protocol specific events. They rely on <interfacename>NHttpClientHandler
</interfacename> and <interfacename>NHttpServiceHandler</interfacename> interfaces to
propagate HTTP protocol events to a HTTP protocol handler.
</para>
<para>
Server side HTTP I/O events as defined by the <interfacename>NHttpServiceHandler
</interfacename> interface:
</para>
<itemizedlist>
<listitem>
<formalpara>
<title><methodname>connected</methodname>:</title>
<para>
Triggered when a new incoming connection has been created.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>requestReceived</methodname>:</title>
<para>
Triggered when a new HTTP request is received. The connection passed as a parameter to
this method is guaranteed to return a valid HTTP request object. If the request
received encloses a request entity this method will be followed a series of
<methodname>inputReady</methodname> events to transfer the request content.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>inputReady</methodname>:</title>
<para>
Triggered when the underlying channel is ready for reading a new portion of the request
entity through the corresponding content decoder. If the content consumer is unable to
process the incoming content, input event notifications can be temporarily suspended
using <interfacename>IOControl</interfacename> interface.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>responseReady</methodname>:</title>
<para>
Triggered when the connection is ready to accept new HTTP response. The protocol
handler does not have to submit a response if it is not ready.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>outputReady</methodname>:</title>
<para>
Triggered when the underlying channel is ready for writing a next portion of the
response entity through the corresponding content encoder. If the content producer is
unable to generate the outgoing content, output event notifications can be temporarily
suspended using <interfacename>IOControl</interfacename> interface.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>exception</methodname>:</title>
<para>
Triggered when an I/O error occurrs while reading from or writing to the underlying
channel or when an HTTP protocol violation occurs while receiving an HTTP request.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>timeout</methodname>:</title>
<para>
Triggered when no input is detected on this connection over the maximum period of
inactivity.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>closed</methodname>:</title>
<para>
Triggered when the connection has been closed.
</para>
</formalpara>
</listitem>
</itemizedlist>
<para>
Client side HTTP I/O events as defined by the <interfacename>NHttpClientHandler
</interfacename> interface:
</para>
<itemizedlist>
<listitem>
<formalpara>
<title><methodname>connected</methodname>:</title>
<para>
Triggered when a new outgoing connection has been created. The attachment object passed
as a parameter to this event is an arbitrary object that was attached to the session
request.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>requestReady</methodname>:</title>
<para>
Triggered when the connection is ready to accept new HTTP request. The protocol handler
does not have to submit a request if it is not ready.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>outputReady</methodname>:</title>
<para>
Triggered when the underlying channel is ready for writing a next portion of the
request entity through the corresponding content encoder. If the content producer is
unable to generate the outgoing content, output event notifications can be temporarily
suspended using <interfacename>IOControl</interfacename> interface.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>responseReceived</methodname>:</title>
<para>
Triggered when an HTTP response is received. The connection passed as a parameter to
this method is guaranteed to return a valid HTTP response object. If the response
received encloses a response entity this method will be followed a series of
<methodname>inputReady</methodname> events to transfer the response content.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>inputReady</methodname>:</title>
<para>
Triggered when the underlying channel is ready for reading a new portion of the
response entity through the corresponding content decoder. If the content consumer is
unable to process the incoming content, input event notifications can be temporarily
suspended using <interfacename>IOControl</interfacename> interface.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>exception</methodname>:</title>
<para>
Triggered when an I/O error occurs while reading from or writing to the underlying
channel or when an HTTP protocol violation occurs while receiving an HTTP response..
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>timeout</methodname>:</title>
<para>
Triggered when no input is detected on this connection over the maximum period of
inactivity.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>closed</methodname>:</title>
<para>
Triggered when the connection has been closed.
</para>
</formalpara>
</listitem>
</itemizedlist>
</section>
<section>
<title>Non-blocking HTTP entities</title>
<para>
As discussed previously the process of content transfer for non-blocking connections works
completely differently compared to that for blocking connections. For obvious reasons
classic I/O abstraction based on inherently blocking <classname>java.io.InputStream
</classname> and <classname>java.io.OutputStream</classname> classes is not applicable to
the asynchronous process of data transfer. Therefore, non-blocking HTTP entities provide
NIO specific extensions to the HttpEntity interface: <interfacename>ProducingNHttpEntity
</interfacename> and <interfacename>ConsumingNHttpEntity</interfacename> interfaces.
Implementation classes of these interfaces may throw <classname>
java.lang.UnsupportedOperationException</classname> from <methodname>
HttpEntity#getContent()</methodname> or <methodname>HttpEntity#writeTo()</methodname> if
a particular implementation is unable to represent its content stream as instance of
<classname>java.io.InputStream</classname> or cannot stream its content out to an
<classname>java.io.OutputStream</classname>.
</para>
<section>
<title>Content consuming non-blocking HTTP entity</title>
<para>
<interfacename>ConsumingNHttpEntity</interfacename> interface represents a non-blocking
entity that allows content to be consumed from a content decoder. <interfacename>
ConsumingNHttpEntity</interfacename> extends the base <interfacename>HttpEntity
</interfacename> interface with a number of NIO specific notification methods:
</para>
<itemizedlist>
<listitem>
<formalpara>
<title><methodname>consumeContent</methodname>:</title>
<para>
Notification that content is available to be read from the decoder.
<interfacename>IOControl</interfacename> instance passed as a parameter to the
method can be used to suspend input events if the entity is temporarily unable
to allocate more storage to accommodate all incoming content.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>finish</methodname>:</title>
<para>
Notification that any resources allocated for reading can be released.
</para>
</formalpara>
</listitem>
</itemizedlist>
<para>
The following implementations of <interfacename>ConsumingNHttpEntity</interfacename>
provided by HttpCore NIO:
</para>
<itemizedlist>
<listitem>
<para>
<link linkend="buffering-n-entity">
<classname>BufferingNHttpEntity</classname>
</link>
</para>
</listitem>
<listitem>
<para>
<link linkend="consuming-n-entity-template">
<classname>ConsumingNHttpEntityTemplate</classname>
</link>
</para>
</listitem>
</itemizedlist>
<section id="buffering-n-entity">
<title><classname>BufferingNHttpEntity</classname></title>
<para>
<classname>BufferingNHttpEntity</classname> is a subclass of <classname>
HttpEntityWrapper</classname> that consumes all incoming content into memory. Once
the content body has been fully received it can be retrieved as an <classname>
java.io.InputStream</classname> via <methodname>HttpEntity#getContent()
</methodname>, or written to an output stream via <methodname>HttpEntity#writeTo()
</methodname>.
</para>
</section>
<section id="consuming-n-entity-template">
<title><classname>ConsumingNHttpEntityTemplate</classname></title>
<para>
<classname>ConsumingNHttpEntityTemplate</classname> is a subclass of <classname>
HttpEntityWrapper</classname> that decorates the incoming HTTP entity and
delegates the handling of incoming content to a <interfacename>ContentListener
</interfacename> instance.
</para>
<programlisting><![CDATA[
static class FileWriteListener implements ContentListener {
private final FileChannel fileChannel;
private long idx = 0;
public FileWriteListener(File file) throws IOException {
this.fileChannel = new FileInputStream(file).getChannel();
}
public void contentAvailable(
ContentDecoder decoder, IOControl ioctrl) throws IOException {
long transferred;
if (decoder instanceof FileContentDecoder) {
transferred = ((FileContentDecoder) decoder).transfer(
fileChannel, idx, Long.MAX_VALUE);
} else {
transferred = fileChannel.transferFrom(
new ContentDecoderChannel(decoder),
idx, Long.MAX_VALUE);
}
if (transferred > 0) {
idx += transferred;
}
}
public void finished() {
try {
fileChannel.close();
} catch(IOException ignored) {}
}
}
HttpEntity incomingEntity;
File file = new File("buffer.bin");
ConsumingNHttpEntity entity = new ConsumingNHttpEntityTemplate(
incomingEntity,
new FileWriteListener(file));
]]></programlisting>
</section>
</section>
<section>
<title>Content producing non-blocking HTTP entity</title>
<para>
<interfacename>ProducingNHttpEntity</interfacename> interface represents a non-blocking
entity that allows content to be written to a content encoder.
<interfacename>ProducingNHttpEntity</interfacename> extends the base
<interfacename>HttpEntity</interfacename> interface with a number of NIO specific
notification methods:
</para>
<itemizedlist>
<listitem>
<formalpara>
<title><methodname>produceContent</methodname>:</title>
<para>
Notification that content can be written to the encoder. <interfacename>
IOControl</interfacename> instance passed as a parameter to the method can be
used to temporarily suspend output events if the entity is unable to produce
more content. Please note one must call <methodname>ContentEncoder#complete()
</methodname> to inform the underlying connection that all content has been
written. Failure to do so could result in the entity never being correctly
delimited.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>finish</methodname>:</title>
<para>
Notification that any resources allocated for writing can be released.
</para>
</formalpara>
</listitem>
</itemizedlist>
<para>
The following implementations of <interfacename>ProducingNHttpEntity</interfacename>
provided by HttpCore NIO:
</para>
<itemizedlist>
<listitem>
<para>
<link linkend="bytearray-n-entity">
<classname>NByteArrayEntity</classname>
</link>
</para>
</listitem>
<listitem>
<para>
<link linkend="string-n-entity">
<classname>NStringEntity</classname>
</link>
</para>
</listitem>
<listitem>
<para>
<link linkend="file-n-entity">
<classname>NFileEntity</classname>
</link>
</para>
</listitem>
</itemizedlist>
<section id="bytearray-n-entity">
<title><classname>NByteArrayEntity</classname></title>
<para>
This is a simple self contained repeatable entity, which receives its content from
a given byte array. This byte array is supplied to the constructor.
</para>
<programlisting><![CDATA[
String myData = "Hello world on the other side!!";
NByteArrayEntity entity = new NByteArrayEntity(myData.getBytes());
]]></programlisting>
</section>
<section id="string-n-entity">
<title><classname>NStringEntity</classname></title>
<para>
It's is a simple, self contained, repeatable entity that retrieves its data from a
<classname>java.lang.String</classname> object. It has 2 constructors, one simply
constructs with a given string where the other also takes a character encoding for
the data in the <classname>java.lang.String</classname>.
</para>
<programlisting><![CDATA[
String myData = "Hello world on the other side!!";
// construct without a character encoding
NStringEntity myEntity1 = new NStringEntity(myData);
// alternatively construct with an encoding
NStringEntity myEntity2 = new NStringEntity(myData, "UTF-8");
]]></programlisting>
</section>
<section id="file-n-entity">
<title><classname>NFileEntity</classname></title>
<para>
This entity reads its content body from a file. This class is mostly used to stream
large files of different types, so one needs to supply the content type of the file
to make sure the content can be correctly recognized and processed by the
recipient.
</para>
<programlisting><![CDATA[
File staticFile = new File("/path/to/myapp.jar");
NHttpEntity entity = new NFileEntity(staticFile,
"application/java-archive");
]]></programlisting>
<para>
The <classname>NHttpEntity</classname> will make use of the direct channel I/O
whenever possible, provided the content encoder is capable of transferring data
directly from a file to the socket of the underlying connection.
</para>
</section>
</section>
</section>
<section>
<title>Non-blocking HTTP protocol handlers</title>
<section>
<title>Asynchronous HTTP service handler</title>
<para>
<classname>AsyncNHttpServiceHandler</classname> is a fully asynchronous HTTP server
side protocol handler that implements the essential requirements of the HTTP protocol
for the server side message processing as described by RFC 2616. <classname>
AsyncNHttpServiceHandler</classname> is capable of processing HTTP requests with nearly
constant memory footprint for individual HTTP connections. The handler stores headers
of HTTP messages in memory, while content of message bodies is streamed directly from
the entity to the underlying channel (and vice versa) using <interfacename>
ConsumingNHttpEntity</interfacename> and <interfacename>ProducingNHttpEntity
</interfacename> interfaces.
</para>
<para>
When using this implementation, it is important to ensure that entities supplied for
writing implement <interfacename>ProducingNHttpEntity</interfacename>. Doing so will
allow the entity to be written out asynchronously. If entities supplied for writing do
not implement the <interfacename>ProducingNHttpEntity</interfacename> interface,
a delegate is added that buffers the entire contents in memory. Additionally,
the buffering might take place in the I/O dispatch thread, which could cause I/O to
block temporarily. For best results, one must ensure that all entities set on
HTTP responses from <interfacename>NHttpRequestHandler</interfacename> implement
<interfacename>ProducingNHttpEntity</interfacename>.
</para>
<para>
If incoming requests enclose a content entity, <interfacename>NHttpRequestHandler
</interfacename> instances are expected to return a <interfacename>ConsumingNHttpEntity
</interfacename> for reading the content. After the entity is finished reading the
data, <methodname>NHttpRequestHandler#handle()</methodname> method is called to
generate a response.
</para>
<para>
<classname>AsyncNHttpServiceHandler</classname> relies on <interfacename>HttpProcessor
</interfacename> to generate mandatory protocol headers for all outgoing messages and
apply common, cross-cutting message transformations to all incoming and outgoing
messages, whereas individual HTTP request handlers are expected to take care of
application specific content generation and processing.
</para>
<programlisting><![CDATA[
HttpParams params;
// Initialize HTTP parameters
HttpProcessor httpproc;
// Initialize HTTP processor
AsyncNHttpServiceHandler handler = new AsyncNHttpServiceHandler(
httpproc,
new DefaultHttpResponseFactory(),
new DefaultConnectionReuseStrategy(),
params);
]]></programlisting>
<section>
<title>Non-blocking HTTP request handlers</title>
<para>
<interfacename>NHttpRequestHandler</interfacename> interface represents a routine
for processing of a specific group of non-blocking HTTP requests. <interfacename>
NHttpRequestHandler</interfacename> implementations are expected to take care of
protocol specific aspects, whereas individual request handlers are expected take
care of application specific HTTP processing. The main purpose of a request handler
is to generate a response object with a content entity to be send back to the
client in response to the given request.
</para>
<programlisting><![CDATA[
NHttpRequestHandler myRequestHandler = new NHttpRequestHandler() {
public ConsumingNHttpEntity entityRequest(
HttpEntityEnclosingRequest request,
HttpContext context) throws HttpException, IOException {
// Buffer incoming content in memory for simplicity
return new BufferingNHttpEntity(request.getEntity(),
new HeapByteBufferAllocator());
}
public void handle(
HttpRequest request,
HttpResponse response,
NHttpResponseTrigger trigger,
HttpContext context) throws HttpException, IOException {
response.setStatusCode(HttpStatus.SC_OK);
response.addHeader("Content-Type", "text/plain");
response.setEntity(
new NStringEntity("some important message"));
// Submit response immediately for simplicity
trigger.submitResponse(response);
}
};
]]></programlisting>
<para>
Request handlers must be implemented in a thread-safe manner. Similarly to
servlets, request handlers should not use instance variables unless access to those
variables are synchronized.
</para>
</section>
<section>
<title>Asynchronous response trigger</title>
<para>
The most fundamental difference of the non-blocking request handlers compared to
their blocking counterparts is ability to defer transmission of the HTTP response
back to the client without blocking the I/O thread by delegating the process of
handling the HTTP request to a worker thread. The worker thread can use the
instance of <interfacename>NHttpResponseTrigger</interfacename> passed as a
parameter to the <methodname>NHttpRequestHandler#handle</methodname> method to
submit a response as at a later point of time once the response becomes available.
</para>
<programlisting><![CDATA[
NHttpRequestHandler myRequestHandler = new NHttpRequestHandler() {
public ConsumingNHttpEntity entityRequest(
HttpEntityEnclosingRequest request,
HttpContext context) throws HttpException, IOException {
// Buffer incoming content in memory for simplicity
return new BufferingNHttpEntity(request.getEntity(),
new HeapByteBufferAllocator());
}
public void handle(
HttpRequest request,
HttpResponse response,
NHttpResponseTrigger trigger,
HttpContext context)
throws HttpException, IOException {
new Thread() {
@Override
public void run() {
try {
Thread.sleep(10);
}
catch(InterruptedException ie) {}
try {
URI uri = new URI(request.getRequestLine().getUri());
response.setStatusCode(HttpStatus.SC_OK);
response.addHeader("Content-Type", "text/plain");
response.setEntity(
new NStringEntity("some important message"));
trigger.submitResponse(response);
} catch(URISyntaxException ex) {
trigger.handleException(
new HttpException("Invalid request URI: " +
ex.getInput()));
}
}
}.start();
}
};
]]></programlisting>
<para>
Please note <interfacename>HttpResponse</interfacename> objects are not thread-safe
and may not be modified concurrently. Non-blocking request handlers must ensure
the HTTP response cannot be accessed by more than one thread at a time.
</para>
</section>
<section>
<title>Non-blocking request handler resolver</title>
<para>
The management of non-blocking HTTP request handlers is quite similar to that of
blocking HTTP request handlers. Usually an instance of <interfacename>
NHttpRequestHandlerResolver</interfacename> is used to maintain a registry of
request handlers and to matches a request URI to a particular request handler.
HttpCore includes only a very simple implementation of the request handler resolver
based on a trivial pattern matching algorithm: <interfacename>
NHttpRequestHandlerRegistry</interfacename> supports only three formats:
<literal>*</literal>, <literal>&lt;uri&gt;*</literal> and
<literal>*&lt;uri&gt;</literal>.
</para>
<programlisting><![CDATA[
// Initialize asynchronous protocol handler
AsyncNHttpServiceHandler handler;
NHttpRequestHandlerRegistry handlerResolver =
new NHttpRequestHandlerRegistry();
handlerReqistry.register("/service/*", myRequestHandler1);
handlerReqistry.register("*.do", myRequestHandler2);
handlerReqistry.register("*", myRequestHandler3);
handler.setHandlerResolver(handlerResolver);
]]></programlisting>
<para>
Users are encouraged to provide more sophisticated implementations of
<interfacename>NHttpRequestHandlerResolver</interfacename>, for instance, based on
regular expressions.
</para>
</section>
</section>
<section>
<title>Asynchronous HTTP client handler</title>
<para>
<classname>AsyncNHttpClientHandler</classname> is a fully asynchronous HTTP client side
protocol handler that implements the essential requirements of the HTTP protocol for
the client side message processing as described by RFC 2616. <classname>
AsyncNHttpClientHandler</classname> is capable of executing HTTP requests with nearly
constant memory footprint for individual HTTP connections. The handler stores headers
of HTTP messages in memory, while content of message bodies is streamed directly from
the entity to the underlying channel (and vice versa) using <interfacename>
ConsumingNHttpEntity</interfacename> and <interfacename>ProducingNHttpEntity
</interfacename> interfaces.
</para>
<para>
When using this implementation, it is important to ensure that entities supplied for
writing implement <interfacename>ProducingNHttpEntity</interfacename>. Doing so will
allow the entity to be written out asynchronously. If entities supplied for writing do
not implement the <interfacename>ProducingNHttpEntity</interfacename> interface,
a delegate is added that buffers the entire contents in memory. Additionally, the
buffering might take place in the I/O dispatch thread, which could cause I/O to block
temporarily. For best results, one must ensure that all entities set on HTTP requests
from <interfacename>NHttpRequestExecutionHandler</interfacename> implement
<interfacename>ProducingNHttpEntity</interfacename>.
</para>
<para>
If incoming responses enclose a content entity, <interfacename>
NHttpRequestExecutionHandler</interfacename> is expected to return a <interfacename>
ConsumingNHttpEntity</interfacename> for reading the content. After the entity is
finished reading the data, <methodname>NHttpRequestExecutionHandler#handleResponse()
</methodname>method is called to process the response.
</para>
<para>
<classname>AsyncNHttpClientHandler</classname> relies on <interfacename>HttpProcessor
</interfacename>to generate mandatory protocol headers for all outgoing messages and
apply common, cross-cutting message transformations to all incoming and outgoing
messages, whereas HTTP request executor is expected to take care of application
specific content generation and processing.
</para>
<programlisting><![CDATA[
// Initialize HTTP parameters
HttpParams params;
//Initialize HTTP processor
HttpProcessor httpproc;
//Create HTTP request execution handler
NHttpRequestExecutionHandler execHandler;
AsyncNHttpClientHandler handler = new AsyncNHttpClientHandler(
httpproc,
execHandler,
new DefaultConnectionReuseStrategy(),
params);
]]></programlisting>
<section>
<title>Asynchronous HTTP request execution handler</title>
<para>
Asynchronous HTTP request execution handler can be used by client-side protocol
handlers to trigger the submission of a new HTTP request and the processing of an
HTTP response.
</para>
<para>
HTTP request execution events as defined by the <interfacename>
NHttpRequestExecutionHandler</interfacename> interface:
</para>
<itemizedlist>
<listitem>
<formalpara>
<title><methodname>initalizeContext</methodname>:</title>
<para>
Triggered when a new connection has been established and the HTTP context
needs to be initialized. The attachment object passed to this method is
the same object which was passed to the connecting I/O reactor when the
connection request was made. The attachment may optionally contain some
state information required in order to correctly initialize the HTTP
context.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>submitRequest</methodname>:</title>
<para>
Triggered when the underlying connection is ready to send a new HTTP
request to the target host. This method may return null if the client is
not yet ready to send a request. In this case the connection will remain
open and can be activated at a later point. If the request encloses an
entity, the entity must be an instance of <interfacename>
ProducingNHttpEntity</interfacename>.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>responseEntity</methodname>:</title>
<para>
Triggered when a response is received with an entity. This method should
return a <interfacename>ConsumingNHttpEntity</interfacename> that will be
used to consume the entity. Null is a valid response value, and will
indicate that the entity should be silently ignored. After the entity is
fully consumed, <methodname>handleResponse</methodname> method is called
to notify a full response and enclosed entity are ready to be processed.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>handleResponse</methodname>:</title>
<para>
Triggered when an HTTP response is ready to be processed.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>finalizeContext</methodname>:</title>
<para>
Triggered when the connection is terminated. This event can be used to
release objects stored in the context or perform some other kind of
cleanup.
</para>
</formalpara>
</listitem>
</itemizedlist>
<programlisting><![CDATA[
NHttpRequestExecutionHandler execHandler =
new NHttpRequestExecutionHandler() {
private final static String DONE_FLAG = "done";
public void initalizeContext(
HttpContext context,
Object attachment) {
if (attachment != null) {
HttpHost virtualHost = (HttpHost) attachment;
context.setAttribute(ExecutionContext.HTTP_TARGET_HOST,
virtualHost);
}
}
public void finalizeContext(HttpContext context) {
context.removeAttribute(DONE_FLAG);
}
public HttpRequest submitRequest(HttpContext context) {
// Submit HTTP GET once
Object done = context.getAttribute(DONE_FLAG);
if (done == null) {
context.setAttribute(DONE_FLAG, Boolean.TRUE);
return new BasicHttpRequest("GET", "/");
} else {
return null;
}
}
public ConsumingNHttpEntity responseEntity(
HttpResponse response,
HttpContext context) throws IOException {
// Buffer incoming content in memory for simplicity
return new BufferingNHttpEntity(response.getEntity(),
new HeapByteBufferAllocator());
}
public void handleResponse(
HttpResponse response,
HttpContext context) throws IOException {
System.out.println(response.getStatusLine());
if (response.getEntity() != null) {
System.out.println(
EntityUtils.toString(response.getEntity()));
}
}
};
]]></programlisting>
</section>
</section>
<section>
<title>Compatibility with blocking I/O</title>
<para>
In addition to asynchronous protocol handlers described above HttpCore ships two
variants of HTTP protocol handlers that emulate blocking I/O model on top of
non-blocking one and allow message content to be produced and consumed using standard
<classname>java.io.OutputStream</classname> / <classname>java.io.InputStream</classname>
API. Compatibility protocol handlers can work with HTTP request handlers and request
executors that rely on blocking HttpEntity implementations.
</para>
<para>
Compatibility protocol handlers rely on !HttpProcessor to generate mandatory protocol
headers for all outgoing messages and apply common, cross-cutting message
transformations to all incoming and outgoing messages, whereas individual HTTP request
executors / HTTP request processors are expected to take care of application specific
content generation and processing.
</para>
<section>
<title>Buffering protocol handlers</title>
<para>
<classname>BufferingHttpServiceHandler</classname> and <classname>
BufferingHttpClientHandler</classname> are protocol handler implementations that
provide compatibility with the blocking I/O by storing the full content of HTTP
messages in memory. Request / response processing callbacks fire only when the
entire message content has been read into a in-memory buffer. Please note that
request execution / request processing take place the main I/O thread and therefore
individual HTTP request executors / request handlers must ensure they do not block
indefinitely.
</para>
<para>
Buffering protocol handler should be used only when dealing with HTTP messages
that are known to be limited in length.
</para>
</section>
<section>
<title>Throttling protocol handlers</title>
<para>
<classname>ThrottlingHttpServiceHandler</classname> and <classname>
ThrottlingHttpClientHandler</classname> are protocol handler implementations that
provide compatibility with the blocking I/O model by utilizing shared content
buffers and a fairly small pool of worker threads. The throttling protocol handlers
allocate input / output buffers of constant length upon initialization and
control the rate of I/O events in order to ensure those content buffers do not
ever overflow. This helps ensure nearly constant memory footprint for HTTP
connections and avoid out of memory conditions while streaming content in and out.
Request / response processing callbacks fire immediately when a message is
received. The throttling protocol handlers delegate the task of processing requests
and generating response content to an Executor, which is expected to perform those
tasks using dedicated worker threads in order to avoid blocking the I/O thread.
</para>
<para>
Usually throttling protocol handlers need only a modest number of worker threads,
much fewer than the number of concurrent connections. If the length of the message
is smaller or about the size of the shared content buffer worker thread will just
store content in the buffer and terminate almost immediately without blocking.
The I/O dispatch thread in its turn will take care of sending out the buffered
content asynchronously. The worker thread will have to block only when processing
large messages and the shared buffer fills up. It is generally advisable to
allocate shared buffers of a size of an average content body for optimal
performance.
</para>
</section>
</section>
<section>
<title>Connection event listener</title>
<para>
Protocol handlers like the rest of HttpCore classes do not do logging in order to not
impose a choice of a logging framework onto the users. However one can add logging of
the most important connection events by injecting a <interfacename>EventListener
</interfacename> implementation into the protocol handler.
</para>
<para>
Connection events as defined by the <interfacename>EventListener</interfacename>
interface:
</para>
<itemizedlist>
<listitem>
<formalpara>
<title><methodname>fatalIOException</methodname>:</title>
<para>
Triggered when an I/O error caused the connection to be terminated.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>fatalProtocolException</methodname>:</title>
<para>
Triggered when an HTTP protocol error caused the connection to be terminated.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>connectionOpen</methodname>:</title>
<para>
Triggered when a new connection has been established.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>connectionClosed</methodname>:</title>
<para>
Triggered when the connection has been terminated.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>connectionTimeout</methodname>:</title>
<para>
Triggered when the connection has timed out.
</para>
</formalpara>
</listitem>
</itemizedlist>
</section>
</section>
<section>
<title>Non-blocking TLS/SSL</title>
<section>
<title>SSL I/O session</title>
<para>
<classname>SSLIOSession</classname> is a decorator class intended to transparently
extend any arbitrary <interfacename>IOSession</interfacename> with transport layer
security capabilities based on the SSL/TLS protocol. Individual protocol handlers
should be able to work with SSL sessions without special preconditions or
modifications. However, I/O dispatchers need to take some additional actions to ensure
correct functioning of the transport layer encryption.
</para>
<itemizedlist>
<listitem>
<para>
When the underlying I/O session has been created, the I/O dispatch must call
<methodname>SSLIOSession#bind()</methodname> method in order to put the SSL
session either into a client or a server mode.
</para>
</listitem>
<listitem>
<para>
When the underlying I/O session is input ready, the I/O dispatcher should check
whether the SSL I/O session is ready to produce input data by calling
<methodname>SSLIOSession#isAppInputReady()</methodname>, pass control to the
protocol handler if it is, and finally call <methodname>
SSLIOSession#inboundTransport()</methodname> method in order to do the
necessary SSL handshaking and decrypt input data.
</para>
</listitem>
<listitem>
<para>
When the underlying I/O session is output ready, the I/O dispatcher should
check whether the SSL I/O session is ready to accept output data by calling
<methodname>SSLIOSession#isAppOutputReady()</methodname>, pass control to the
protocol handler if it is, and finally call <methodname>
SSLIOSession#outboundTransport()</methodname> method in order to do the nessary
SSL handshaking and encrypt application data.
</para>
</listitem>
</itemizedlist>
<section>
<title>SSL I/O session handler</title>
<para>
Applications can customize various aspects of the TLS/SSl protocol by passing a
custom implementation of the <interfacename>SSLIOSessionHandler</interfacename>
interface.
</para>
<para>
SSL events as defined by the <interfacename>SSLIOSessionHandler</interfacename>
interface:
</para>
<itemizedlist>
<listitem>
<formalpara>
<title><methodname>initalize</methodname>:</title>
<para>
Triggered when the SSL connection is being initialized. The handler can use
this callback to customize properties of the <classname>
javax.net.ssl.SSLEngine</classname> used to establish the SSL session.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title><methodname>verify</methodname>:</title>
<para>
Triggered when the SSL connection has been established and initial SSL
handshake has been successfully completed. The handler can use this
callback to verify properties of the SSLSession. For instance this would
be the right place to enforce SSL cipher strength, validate certificate
chain and do hostname checks.
</para>
</formalpara>
</listitem>
</itemizedlist>
<programlisting><![CDATA[
// Get hold of new I/O session
IOSession iosession;
// Initialize default SSL context
SSLContext sslcontext = SSLContext.getInstance("SSL");
sslcontext.init(null, null, null);
SSLIOSession sslsession = new SSLIOSession(
iosession, sslcontext, new SSLIOSessionHandler() {
public void initalize(
SSLEngine sslengine,
HttpParams params) throws SSLException {
// Ask clients to authenticate
sslengine.setWantClientAuth(true);
// Enforce strong ciphers
sslengine.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" });
}
public void verify(
SocketAddress remoteAddress,
SSLSession session) throws SSLException {
X509Certificate[] certs = session.getPeerCertificateChain();
// Examine peer certificate chain
for (X509Certificate cert: certs) {
System.out.println(cert.toString());
}
}
});
]]></programlisting>
</section>
</section>
<section>
<title>SSL I/O event dispatches</title>
<para>
HttpCore provides <classname>SSLClientIOEventDispatch</classname> and <classname>
SSLServerIOEventDispatch</classname> I/O dispatch implementations that can be used to
SSL enable connections managed by any arbitrary I/O reactor. The dispatches take all
the necessary actions to wrap active I/O sessions with the SSL I/O session decorator
and ensure correct handling of SSL protocol handshaking.
</para>
</section>
</section>
</chapter>