| <?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>Asynchronous I/O based on NIO</title> |
| <para> |
| Asynchronous I/O model may be more appropriate for those scenarios where raw data throughput |
| is less important than the ability to handle thousands of simultaneous connections in |
| a scalable, resource efficient manner. Asynchronous I/O is arguably more complex and usually |
| requires a special care when dealing with large message payloads. |
| </para> |
| <section> |
| <title>Differences from other I/O 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 is 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[ |
| IOReactorConfig config = IOReactorConfig.DEFAULT; |
| IOReactor ioreactor = new DefaultConnectingIOReactor(config); |
| ]]></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[ |
| IOReactor ioreactor = new DefaultConnectingIOReactor(); |
| |
| IOEventDispatch eventDispatch = <...> |
| 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[ |
| IOReactor ioreactor = <...> |
| 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); |
| ... |
| IOSession iosession = <...> |
| 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 iosession = <...> |
| 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 a consumer can read, is 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 = <...> |
| 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> |
| <section> |
| <title>I/O reactor configuration</title> |
| <para> |
| I/O reactors by default use system dependent configuration which in most cases should be |
| sensible enough. |
| </para> |
| <programlisting><![CDATA[ |
| IOReactorConfig config = IOReactorConfig.DEFAULT; |
| IOReactor ioreactor = new DefaultListeningIOReactor(config); |
| ]]></programlisting> |
| <para> |
| However in some cases custom settings may be necessary, for instance, in order to alter |
| default socket properties and timeout values. One should rarely need to change other |
| parameters. |
| </para> |
| <programlisting><![CDATA[ |
| IOReactorConfig config = IOReactorConfig.custom() |
| .setTcpNoDelay(true) |
| .setSoTimeout(5000) |
| .setSoReuseAddress(true) |
| .setConnectTimeout(5000) |
| .build(); |
| IOReactor ioreactor = new DefaultListeningIOReactor(config); |
| ]]></programlisting> |
| <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[ |
| IOReactorConfig config = IOReactorConfig.custom() |
| .setInterestOpQueued(true) |
| .build(); |
| ]]></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 behavior 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[ |
| DefaultNHttpClientConnection conn = <...> |
| 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[ |
| 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[ |
| 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[ |
| 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[ |
| 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[ |
| 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[ |
| 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[ |
| 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[ |
| 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>NHttpClientEventHandler |
| </interfacename> and <interfacename>NHttpServerEventHandler</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>NHttpServerEventHandler |
| </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 |
| temporarily suspended using <interfacename>IOControl</interfacename> interface |
| (super interface of <interfacename>NHttpServerConnection</interfacename>). |
| </para> |
| <para> |
| Please note that the <interfacename>NHttpServerConnection</interfacename> and |
| <interfacename>ContentDecoder</interfacename> objects are not thread-safe and |
| should only be used within the context of this method call. The <interfacename> |
| IOControl</interfacename> object can be shared and used on other thread to resume |
| input event notifications when the handler is capable of processing more content. |
| </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 |
| (super interface of <interfacename>NHttpServerConnection</interfacename>). |
| </para> |
| <para> |
| Please note that the <interfacename>NHttpServerConnection</interfacename> and |
| <interfacename>ContentEncoder</interfacename> objects are not thread-safe and |
| should only be used within the context of this method call. The <interfacename> |
| IOControl</interfacename> object can be shared and used on other thread to resume |
| output event notifications when more content is made available. |
| </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>NHttpClientEventHandler |
| </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 |
| (super interface of <interfacename>NHttpClientConnection</interfacename>). |
| </para> |
| <para> |
| Please note that the <interfacename>NHttpClientConnection</interfacename> and |
| <interfacename>ContentEncoder</interfacename> objects are not thread-safe and |
| should only be used within the context of this method call. The <interfacename> |
| IOControl</interfacename> object can be shared and used on other thread to resume |
| output event notifications when more content is made available. |
| </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 |
| (super interface of <interfacename>NHttpClientConnection</interfacename>). |
| </para> |
| <para> |
| Please note that the <interfacename>NHttpClientConnection</interfacename> and |
| <interfacename>ContentDecoder</interfacename> objects are not thread-safe and |
| should only be used within the context of this method call. The <interfacename> |
| IOControl</interfacename> object can be shared and used on other thread to resume |
| input event notifications when the handler is capable of processing more content. |
| </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 content producers</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 well suited |
| for asynchronous data transfer. In order to avoid inefficient and potentially blocking |
| I/O operation redirection through <methodname>java.nio.channels.Channles#newChannel |
| </methodname> non-blocking HTTP entities are expected to implement NIO specific extension |
| interface <interfacename>HttpAsyncContentProducer</interfacename>. |
| </para> |
| <para> |
| The <interfacename>HttpAsyncContentProducer</interfacename> interface defines several |
| additional method for efficient streaming of content to a non-blocking HTTP connection: |
| </para> |
| <itemizedlist> |
| <listitem> |
| <formalpara> |
| <title><methodname>produceContent</methodname>:</title> |
| <para> |
| Invoked to write out a chunk of content to the <interfacename>ContentEncoder |
| </interfacename>. The <interfacename>IOControl</interfacename> interface can be |
| used to suspend output events if the entity is temporarily unable to produce more |
| content. When all content is finished, the producer MUST call |
| <methodname>ContentEncoder#complete()</methodname>. Failure to do so may cause |
| the entity to be incorrectly delimited. |
| </para> |
| <para> |
| Please note that the <interfacename>ContentEncoder</interfacename> object is |
| not thread-safe and should only be used within the context of this method call. |
| The <interfacename>IOControl</interfacename> object can be shared and used on other |
| thread resume output event notifications when more content is made available. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>isRepeatable</methodname>:</title> |
| <para> |
| Determines whether or not this producer is capable of producing its content more |
| than once. Repeatable content producers are expected to be able to recreate |
| their content even after having been closed. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>close</methodname>:</title> |
| <para> |
| Closes the producer and releases all resources currently allocated by it. |
| </para> |
| </formalpara> |
| </listitem> |
| </itemizedlist> |
| <section> |
| <title>Creating non-blocking entities</title> |
| <para> |
| Several HTTP entity implementations included in HttpCore NIO support |
| <interfacename>HttpAsyncContentProducer</interfacename> interface: |
| </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[ |
| NByteArrayEntity entity = new NByteArrayEntity(new byte[] {1, 2, 3}); |
| ]]></programlisting> |
| </section> |
| <section id="string-n-entity"> |
| <title><classname>NStringEntity</classname></title> |
| <para> |
| This 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[ |
| NStringEntity myEntity = new NStringEntity("important message", |
| Consts.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"); |
| NFileEntity entity = new NFileEntity(staticFile, |
| ContentType.create("application/java-archive", null)); |
| ]]></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</title> |
| <para> |
| <classname>HttpAsyncService</classname> is a fully asynchronous HTTP server side |
| protocol handler based on the non-blocking (NIO) I/O model. <classname> |
| HttpAsyncService</classname> translates individual events fired through the |
| <interfacename>NHttpServerEventHandler</interfacename> interface into logically |
| related HTTP message exchanges. |
| </para> |
| <para> |
| Upon receiving an incoming request the <classname>HttpAsyncService</classname> |
| verifies the message for compliance with the server expectations using <interfacename> |
| HttpAsyncExpectationVerifier</interfacename>, if provided, and then <interfacename> |
| HttpAsyncRequestHandlerResolver</interfacename> is used to resolve the request URI to |
| a particular <interfacename>HttpAsyncRequestHandler</interfacename> intended to handle |
| the request with the given URI. The protocol handler uses the selected <interfacename> |
| HttpAsyncRequestHandler</interfacename> instance to process the incoming request and |
| to generate an outgoing response. |
| </para> |
| <para> |
| <classname>HttpAsyncService</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 implement |
| application specific content generation and processing. |
| </para> |
| <programlisting><![CDATA[ |
| 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); |
| ]]></programlisting> |
| <section> |
| <title>Non-blocking HTTP request handlers</title> |
| <para> |
| <interfacename>HttpAsyncRequestHandler</interfacename> represents a routine for |
| asynchronous processing of a specific group of non-blocking HTTP requests. |
| Protocol handlers are designed to take care of protocol specific aspects, whereas |
| individual request handlers are expected to 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 sent back to the client in response to the given |
| request. |
| </para> |
| <programlisting><![CDATA[ |
| HttpAsyncRequestHandler<HttpRequest> rh = new HttpAsyncRequestHandler<HttpRequest>() { |
| |
| public HttpAsyncRequestConsumer<HttpRequest> processRequest( |
| final HttpRequest request, |
| final HttpContext context) { |
| // Buffer request content in memory for simplicity |
| return new BasicAsyncRequestConsumer(); |
| } |
| |
| public void handle( |
| final HttpRequest request, |
| final HttpAsyncExchange httpexchange, |
| final HttpContext context) throws HttpException, IOException { |
| HttpResponse response = httpexchange.getResponse(); |
| response.setStatusCode(HttpStatus.SC_OK); |
| NFileEntity body = new NFileEntity(new File("static.html"), |
| ContentType.create("text/html", Consts.UTF_8)); |
| response.setEntity(body); |
| httpexchange.submitResponse(new BasicAsyncResponseProducer(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 HTTP exchange</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 or another service. The instance of |
| <interfacename>HttpAsyncExchange</interfacename> passed as a parameter to the |
| <methodname>HttpAsyncRequestHandler#handle</methodname> method to submit |
| a response as at a later point once response content becomes available. |
| </para> |
| <para> |
| The <interfacename>HttpAsyncExchange</interfacename> interface can be interacted |
| with using the following methods: |
| </para> |
| <itemizedlist> |
| <listitem> |
| <formalpara> |
| <title><methodname>getRequest</methodname>:</title> |
| <para> |
| Returns the received HTTP request message. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>getResponse</methodname>:</title> |
| <para> |
| Returns the default HTTP response message that can submitted once ready. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>submitResponse</methodname>:</title> |
| <para> |
| Submits an HTTP response and completed the message exchange. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>isCompleted</methodname>:</title> |
| <para> |
| Determines whether or not the message exchange has been completed. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>setCallback</methodname>:</title> |
| <para> |
| Sets <interfacename>Cancellable</interfacename> callback to be invoked |
| in case the underlying connection times out or gets terminated prematurely |
| by the client. This callback can be used to cancel a long running response |
| generating process if a response is no longer needed. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>setTimeout</methodname>:</title> |
| <para> |
| Sets timeout for this message exchange. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>getTimeout</methodname>:</title> |
| <para> |
| Returns timeout for this message exchange. |
| </para> |
| </formalpara> |
| </listitem> |
| </itemizedlist> |
| <programlisting><![CDATA[ |
| HttpAsyncRequestHandler<HttpRequest> rh = new HttpAsyncRequestHandler<HttpRequest>() { |
| |
| public HttpAsyncRequestConsumer<HttpRequest> processRequest( |
| final HttpRequest request, |
| final HttpContext context) { |
| // Buffer request content in memory for simplicity |
| return new BasicAsyncRequestConsumer(); |
| } |
| |
| public void handle( |
| final HttpRequest request, |
| final HttpAsyncExchange httpexchange, |
| final HttpContext context) throws HttpException, IOException { |
| |
| new Thread() { |
| |
| @Override |
| public void run() { |
| try { |
| Thread.sleep(10); |
| } |
| catch(InterruptedException ie) {} |
| HttpResponse response = httpexchange.getResponse(); |
| response.setStatusCode(HttpStatus.SC_OK); |
| NFileEntity body = new NFileEntity(new File("static.html"), |
| ContentType.create("text/html", Consts.UTF_8)); |
| response.setEntity(body); |
| httpexchange.submitResponse(new BasicAsyncResponseProducer(response)); |
| } |
| }.start(); |
| |
| } |
| |
| }; |
| ]]></programlisting> |
| <para> |
| Please note <interfacename>HttpResponse</interfacename> instances are not |
| thread-safe and may not be modified concurrently. Non-blocking request handlers |
| must ensure HTTP response cannot be accessed by more than one thread at a time. |
| </para> |
| </section> |
| <section> |
| <title>Asynchronous HTTP request consumer</title> |
| <para> |
| <interfacename>HttpAsyncRequestConsumer</interfacename> facilitates the process of |
| asynchronous processing of HTTP requests. It is a callback interface used by |
| <interfacename>HttpAsyncRequestHandler</interfacename>s to process an incoming |
| HTTP request message and to stream its content from a non-blocking server side |
| HTTP connection. |
| </para> |
| <para> |
| HTTP I/O events and methods as defined by the <interfacename> |
| HttpAsyncRequestConsumer</interfacename> interface: |
| </para> |
| <itemizedlist> |
| <listitem> |
| <formalpara> |
| <title><methodname>requestReceived</methodname>:</title> |
| <para> |
| Invoked when a HTTP request message is received. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>consumeContent</methodname>:</title> |
| <para> |
| Invoked to process a chunk of content from the <interfacename> |
| ContentDecoder</interfacename>. The <interfacename>IOControl |
| </interfacename> interface can be used to suspend input events if |
| the consumer is temporarily unable to consume more content. |
| </para> |
| <para> |
| The consumer can use the <methodname>ContentDecoder#isCompleted() |
| </methodname> method to find out whether or not the message content |
| has been fully consumed. |
| </para> |
| <para> |
| Please note that the <interfacename>ContentDecoder</interfacename> object |
| is not thread-safe and should only be used within the context of this |
| method call. The <interfacename>IOControl</interfacename> object can be |
| shared and used on other thread to resume input event notifications |
| when the consumer is capable of processing more content. |
| </para> |
| <para> |
| This event is invoked only if the incoming request message has |
| a content entity enclosed in it. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>requestCompleted</methodname>:</title> |
| <para> |
| Invoked to signal that the request has been fully processed. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>failed</methodname>:</title> |
| <para> |
| Invoked to signal that the request processing terminated abnormally. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>getException</methodname>:</title> |
| <para> |
| Returns an exception in case of an abnormal termination. This method |
| returns <code>null</code> if the request execution is still ongoing or if |
| it completed successfully. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>getResult</methodname>:</title> |
| <para> |
| Returns a result of the request execution, when available. This method |
| returns <code>null</code> if the request execution is still ongoing. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>isDone</methodname>:</title> |
| <para> |
| Determines whether or not the request execution completed. If the |
| request processing terminated normally <methodname>getResult()</methodname> |
| can be used to obtain the result. If the request processing terminated |
| abnormally <methodname>getException()</methodname> can be used to obtain |
| the cause. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>close</methodname>:</title> |
| <para> |
| Closes the consumer and releases all resources currently allocated by it. |
| </para> |
| </formalpara> |
| </listitem> |
| </itemizedlist> |
| <para> |
| <interfacename>HttpAsyncRequestConsumer</interfacename> implementations are |
| expected to be thread-safe. |
| </para> |
| <para> |
| <classname>BasicAsyncRequestConsumer</classname> is a very basic implementation |
| of the <interfacename>HttpAsyncRequestConsumer</interfacename> interface shipped |
| with the library. Please note that this consumer buffers request content in memory and |
| therefore should be used for relatively small request messages. |
| </para> |
| </section> |
| <section> |
| <title>Asynchronous HTTP response producer</title> |
| <para> |
| <interfacename>HttpAsyncResponseProducer</interfacename> facilitates the process of |
| asynchronous generation of HTTP responses. It is a callback interface used by |
| <interfacename>HttpAsyncRequestHandler</interfacename>s to generate an HTTP response |
| message and to stream its content to a non-blocking server side HTTP connection. |
| </para> |
| <para> |
| HTTP I/O events and methods as defined by the |
| <interfacename>HttpAsyncResponseProducer</interfacename> interface: |
| </para> |
| <itemizedlist> |
| <listitem> |
| <formalpara> |
| <title><methodname>generateResponse</methodname>:</title> |
| <para> |
| Invoked to generate a HTTP response message header. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>produceContent</methodname>:</title> |
| <para> |
| Invoked to write out a chunk of content to the <interfacename> |
| ContentEncoder</interfacename>. The <interfacename>IOControl |
| </interfacename> interface can be used to suspend output events if |
| the producer is temporarily unable to produce more content. |
| </para> |
| <para> |
| When all content is finished, the producer MUST call <methodname> |
| ContentEncoder#complete()</methodname>. Failure to do so may cause |
| the entity to be incorrectly delimited. |
| </para> |
| <para> |
| Please note that the <interfacename>ContentEncoder</interfacename> object |
| is not thread-safe and should only be used within the context of this |
| method call. The <interfacename>IOControl</interfacename> object can be |
| shared and used on other thread resume output event notifications when |
| more content is made available. |
| </para> |
| <para> |
| This event is invoked only for if the outgoing response message has |
| a content entity enclosed in it, that is <methodname> |
| HttpResponse#getEntity()</methodname> returns <code>null</code>. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>responseCompleted</methodname>:</title> |
| <para> |
| Invoked to signal that the response has been fully written out. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>failed</methodname>:</title> |
| <para> |
| Invoked to signal that the response processing terminated abnormally. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>close</methodname>:</title> |
| <para> |
| Closes the producer and releases all resources currently allocated by it. |
| </para> |
| </formalpara> |
| </listitem> |
| </itemizedlist> |
| <para> |
| <interfacename>HttpAsyncResponseProducer</interfacename> implementations are |
| expected to be thread-safe. |
| </para> |
| <para> |
| <classname>BasicAsyncResponseProducer</classname> is a basic implementation |
| of the <interfacename>HttpAsyncResponseProducer</interfacename> interface shipped |
| with the library. The producer can make use of the <interfacename> |
| HttpAsyncContentProducer</interfacename> interface to efficiently stream out |
| message content to a non-blocking HTTP connection, if it is implemented by the |
| <interfacename>HttpEntity</interfacename> enclosed in the response. |
| </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> |
| HttpAsyncRequestHandlerResolver</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: <classname> |
| HttpAsyncRequestHandlerRegistry</classname> supports only three formats: |
| <literal>*</literal>, <literal><uri>*</literal> and |
| <literal>*<uri></literal>. |
| </para> |
| <programlisting><![CDATA[ |
| HttpAsyncRequestHandler<?> myRequestHandler1 = <...> |
| HttpAsyncRequestHandler<?> myRequestHandler2 = <...> |
| HttpAsyncRequestHandler<?> myRequestHandler3 = <...> |
| UriHttpAsyncRequestHandlerMapper handlerReqistry = |
| new UriHttpAsyncRequestHandlerMapper(); |
| handlerReqistry.register("/service/*", myRequestHandler1); |
| handlerReqistry.register("*.do", myRequestHandler2); |
| handlerReqistry.register("*", myRequestHandler3); |
| ]]></programlisting> |
| <para> |
| Users are encouraged to provide more sophisticated implementations of |
| <interfacename>HttpAsyncRequestHandlerResolver</interfacename>, for instance, based |
| on regular expressions. |
| </para> |
| </section> |
| </section> |
| <section> |
| <title>Asynchronous HTTP request executor</title> |
| <para> |
| <classname>HttpAsyncRequestExecutor</classname> is a fully asynchronous client side |
| HTTP protocol handler based on the NIO (non-blocking) I/O model. <classname> |
| HttpAsyncRequestExecutor</classname> translates individual events fired through the |
| <interfacename>NHttpClientEventHandler</interfacename> interface into logically |
| related HTTP message exchanges. |
| </para> |
| <para> |
| <classname>HttpAsyncRequestExecutor</classname> relies on <interfacename> |
| HttpAsyncRequestExecutionHandler</interfacename> to implement application specific |
| content generation and processing and to handle logically related series of HTTP |
| request / response exchanges, which may also span across multiple connections. |
| <interfacename>HttpProcessor</interfacename> provided by the <interfacename> |
| HttpAsyncRequestExecutionHandler</interfacename> instance will be used to generate |
| mandatory protocol headers for all outgoing messages and apply common, cross-cutting |
| message transformations to all incoming and outgoing messages. The caller is expected |
| to pass an instance of <interfacename>HttpAsyncRequestExecutionHandler</interfacename> |
| to be used for the next series of HTTP message exchanges through the connection |
| context using <methodname>HttpAsyncRequestExecutor#HTTP_HANDLER</methodname> attribute. |
| HTTP exchange sequence is considered complete when the <methodname> |
| HttpAsyncRequestExecutionHandler#isDone()</methodname> method returns <code>true</code>. |
| </para> |
| <programlisting><![CDATA[ |
| HttpAsyncRequestExecutor ph = new HttpAsyncRequestExecutor(); |
| IOEventDispatch ioEventDispatch = new DefaultHttpClientIODispatch(ph, |
| new DefaultNHttpClientConnectionFactory(ConnectionConfig.DEFAULT)); |
| ConnectingIOReactor ioreactor = new DefaultConnectingIOReactor(); |
| ioreactor.execute(ioEventDispatch); |
| ]]></programlisting> |
| <para> |
| The <classname>HttpAsyncRequester</classname> utility class can be used to abstract |
| away low level details of <interfacename>HttpAsyncRequestExecutionHandler |
| </interfacename> management. Please note <classname>HttpAsyncRequester</classname> |
| supports single HTTP request / response exchanges only. It does not support HTTP |
| authentication and does not handle redirects automatically. |
| </para> |
| <programlisting><![CDATA[ |
| HttpProcessor httpproc = HttpProcessorBuilder.create() |
| .add(new RequestContent()) |
| .add(new RequestTargetHost()) |
| .add(new RequestConnControl()) |
| .add(new RequestUserAgent("MyAgent-HTTP/1.1")) |
| .add(new RequestExpectContinue(true)) |
| .build(); |
| HttpAsyncRequester requester = new HttpAsyncRequester(httpproc); |
| NHttpClientConnection conn = <...> |
| Future<HttpResponse> future = requester.execute( |
| new BasicAsyncRequestProducer( |
| new HttpHost("localhost"), |
| new BasicHttpRequest("GET", "/")), |
| new BasicAsyncResponseConsumer(), |
| conn); |
| HttpResponse response = future.get(); |
| ]]></programlisting> |
| <section> |
| <title>Asynchronous HTTP request producer</title> |
| <para> |
| <interfacename>HttpAsyncRequestProducer</interfacename> facilitates the process of |
| asynchronous generation of HTTP requests. It is a callback interface whose methods |
| get invoked to generate an HTTP request message and to stream message content to |
| a non-blocking client side HTTP connection. |
| </para> |
| <para> |
| Repeatable request producers capable of generating the same request message more |
| than once can be reset to their initial state by calling the <methodname> |
| resetRequest()</methodname> method, at which point request producers are expected |
| to release currently allocated resources that are no longer needed or re-acquire |
| resources needed to repeat the process. |
| </para> |
| <para> |
| HTTP I/O events and methods as defined by the |
| <interfacename>HttpAsyncRequestProducer</interfacename> interface: |
| </para> |
| <itemizedlist> |
| <listitem> |
| <formalpara> |
| <title><methodname>getTarget</methodname>:</title> |
| <para> |
| Invoked to obtain the request target host. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>generateRequest</methodname>:</title> |
| <para> |
| Invoked to generate a HTTP request message header. The message is expected |
| to implement the <interfacename>HttpEntityEnclosingRequest</interfacename> |
| interface if it is to enclose a content entity. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>produceContent</methodname>:</title> |
| <para> |
| Invoked to write out a chunk of content to the <interfacename> |
| ContentEncoder</interfacename>. The <interfacename>IOControl |
| </interfacename> interface can be used to suspend output events if |
| the producer is temporarily unable to produce more content. |
| </para> |
| <para> |
| When all content is finished, the producer MUST call <methodname> |
| ContentEncoder#complete()</methodname>. Failure to do so may cause |
| the entity to be incorrectly delimited |
| </para> |
| <para> |
| Please note that the <interfacename>ContentEncoder</interfacename> object |
| is not thread-safe and should only be used within the context of this |
| method call. The <interfacename>IOControl</interfacename> object can be |
| shared and used on other thread resume output event notifications when |
| more content is made available. |
| </para> |
| <para> |
| This event is invoked only for if the outgoing request message has |
| a content entity enclosed in it, that is <methodname> |
| HttpEntityEnclosingRequest#getEntity()</methodname> returns <code>null |
| </code>. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>requestCompleted</methodname>:</title> |
| <para> |
| Invoked to signal that the request has been fully written out. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>failed</methodname>:</title> |
| <para> |
| Invoked to signal that the request processing terminated abnormally. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>resetRequest</methodname>:</title> |
| <para> |
| Invoked to reset the producer to its initial state. Repeatable request |
| producers are expected to release currently allocated resources that are |
| no longer needed or re-acquire resources needed to repeat the process. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>close</methodname>:</title> |
| <para> |
| Closes the producer and releases all resources currently allocated by it. |
| </para> |
| </formalpara> |
| </listitem> |
| </itemizedlist> |
| <para> |
| <interfacename>HttpAsyncRequestProducer</interfacename> implementations are |
| expected to be thread-safe. |
| </para> |
| <para> |
| <classname>BasicAsyncRequestProducer</classname> is a basic implementation |
| of the <interfacename>HttpAsyncRequestProducer</interfacename> interface shipped |
| with the library. The producer can make use of the <interfacename> |
| HttpAsyncContentProducer</interfacename> interface to efficiently stream out |
| message content to a non-blocking HTTP connection, if it is implemented by the |
| <interfacename>HttpEntity</interfacename> enclosed in the request. |
| </para> |
| </section> |
| <section> |
| <title>Asynchronous HTTP response consumer</title> |
| <para> |
| <interfacename>HttpAsyncResponseConsumer</interfacename> facilitates the process of |
| asynchronous processing of HTTP responses. It is a callback interface whose methods |
| get invoked to process an HTTP response message and to stream message content from |
| a non-blocking client side HTTP connection. |
| </para> |
| <para> |
| HTTP I/O events and methods as defined by the <interfacename> |
| HttpAsyncResponseConsumer</interfacename> interface: |
| </para> |
| <itemizedlist> |
| <listitem> |
| <formalpara> |
| <title><methodname>responseReceived</methodname>:</title> |
| <para> |
| Invoked when a HTTP response message is received. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>consumeContent</methodname>:</title> |
| <para> |
| Invoked to process a chunk of content from the <interfacename> |
| ContentDecoder</interfacename>. The <interfacename>IOControl |
| </interfacename> interface can be used to suspend input events if |
| the consumer is temporarily unable to consume more content. |
| </para> |
| <para> |
| The consumer can use the <methodname>ContentDecoder#isCompleted() |
| </methodname> method to find out whether or not the message content |
| has been fully consumed. |
| </para> |
| <para> |
| Please note that the <interfacename>ContentDecoder</interfacename> object |
| is not thread-safe and should only be used within the context of this |
| method call. The <interfacename>IOControl</interfacename> object can be |
| shared and used on other thread to resume input event notifications |
| when the consumer is capable of processing more content. |
| </para> |
| <para> |
| This event is invoked only for if the incoming response message has |
| a content entity enclosed in it. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>responseCompleted</methodname>:</title> |
| <para> |
| Invoked to signal that the response has been fully processed. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>failed</methodname>:</title> |
| <para> |
| Invoked to signal that the response processing terminated abnormally. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>getException</methodname>:</title> |
| <para> |
| Returns an exception in case of an abnormal termination. This method |
| returns <code>null</code> if the response processing is still ongoing or |
| if it completed successfully. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>getResult</methodname>:</title> |
| <para> |
| Returns a result of the response processing, when available. This method |
| returns <code>null</code> if the response processing is still ongoing. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>isDone</methodname>:</title> |
| <para> |
| Determines whether or not the response processing completed. If the |
| response processing terminated normally <methodname>getResult()</methodname> |
| can be used to obtain the result. If the response processing terminated |
| abnormally <methodname>getException()</methodname> can be used to obtain |
| the cause. |
| </para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title><methodname>close</methodname>:</title> |
| <para> |
| Closes the consumer and releases all resources currently allocated by it. |
| </para> |
| </formalpara> |
| </listitem> |
| </itemizedlist> |
| <para> |
| <interfacename>HttpAsyncResponseConsumer</interfacename> implementations are |
| expected to be thread-safe. |
| </para> |
| <para> |
| <classname>BasicAsyncResponseConsumer</classname> is a very basic implementation |
| of the <interfacename>HttpAsyncResponseConsumer</interfacename> interface shipped |
| with the library. Please note that this consumer buffers response content in memory |
| and therefore should be used for relatively small response messages. |
| </para> |
| </section> |
| </section> |
| </section> |
| <section> |
| <title>Non-blocking connection pools</title> |
| <para> |
| Non-blocking connection pools are quite similar to blocking one with one significant |
| distinction that they have to reply an I/O reactor to establish new connections. |
| As a result connections leased from a non-blocking pool are returned fully initialized and |
| already bound to a particular I/O session. Non-blocking connections managed by a connection |
| pool cannot be bound to an arbitrary I/O session. |
| </para> |
| <programlisting><![CDATA[ |
| HttpHost target = new HttpHost("localhost"); |
| ConnectingIOReactor ioreactor = <...> |
| BasicNIOConnPool connpool = new BasicNIOConnPool(ioreactor); |
| connpool.lease(target, null, |
| 10, TimeUnit.SECONDS, |
| new FutureCallback<BasicNIOPoolEntry>() { |
| @Override |
| public void completed(BasicNIOPoolEntry entry) { |
| NHttpClientConnection conn = entry.getConnection(); |
| System.out.println("Connection successfully leased"); |
| // Update connection context and request output |
| conn.requestOutput(); |
| } |
| |
| @Override |
| public void failed(Exception ex) { |
| System.out.println("Connection request failed"); |
| ex.printStackTrace(); |
| } |
| |
| @Override |
| public void cancelled() { |
| } |
| }); |
| ]]></programlisting> |
| <para> |
| Please note due to event-driven nature of asynchronous communication model it is quite |
| difficult to ensure proper release of persistent connections back to the pool. One can make |
| use of <classname>HttpAsyncRequester</classname> to handle connection lease and release |
| behind the scene. |
| </para> |
| <programlisting><![CDATA[ |
| ConnectingIOReactor ioreactor = <...> |
| HttpProcessor httpproc = <...> |
| BasicNIOConnPool connpool = new BasicNIOConnPool(ioreactor); |
| HttpAsyncRequester requester = new HttpAsyncRequester(httpproc); |
| HttpHost target = new HttpHost("localhost"); |
| Future<HttpResponse> future = requester.execute( |
| new BasicAsyncRequestProducer( |
| new HttpHost("localhost"), |
| new BasicHttpRequest("GET", "/")), |
| new BasicAsyncResponseConsumer(), |
| connpool); |
| ]]></programlisting> |
| </section> |
| <section> |
| <title>Pipelined request execution</title> |
| <para> |
| In addition to the normal request / response execution mode <classname>HttpAsyncRequester |
| </classname> is also capable of executing requests in the so called pipelined mode whereby |
| several requests are immediately written out to the underlying connection. Please note that |
| entity enclosing requests can be executed in the pipelined mode but the 'expect: continue' |
| handshake should be disabled (request messages should contains no 'Expect: 100-continue' |
| header). |
| </para> |
| <programlisting><![CDATA[ |
| HttpProcessor httpproc = <...> |
| HttpAsyncRequester requester = new HttpAsyncRequester(httpproc); |
| HttpHost target = new HttpHost("www.apache.org"); |
| List<BasicAsyncRequestProducer> requestProducers = Arrays.asList( |
| new BasicAsyncRequestProducer(target, new BasicHttpRequest("GET", "/index.html")), |
| new BasicAsyncRequestProducer(target, new BasicHttpRequest("GET", "/foundation/index.html")), |
| new BasicAsyncRequestProducer(target, new BasicHttpRequest("GET", "/foundation/how-it-works.html")) |
| ); |
| List<BasicAsyncResponseConsumer> responseConsumers = Arrays.asList( |
| new BasicAsyncResponseConsumer(), |
| new BasicAsyncResponseConsumer(), |
| new BasicAsyncResponseConsumer() |
| ); |
| HttpCoreContext context = HttpCoreContext.create(); |
| Future<List<HttpResponse>> future = requester.executePipelined( |
| target, requestProducers, responseConsumers, pool, context, null); |
| ]]></programlisting> |
| <para> |
| Please note that older web servers and especially older HTTP proxies may be unable to handle |
| pipelined requests correctly. Use the pipelined execution mode with caution. |
| </para> |
| </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. Default HTTP connection |
| implementations and protocol handlers should be able to work with SSL sessions without |
| special preconditions or modifications. |
| </para> |
| <programlisting><![CDATA[ |
| SSLContext sslcontext = SSLContext.getInstance("Default"); |
| sslcontext.init(null, null, null); |
| // Plain I/O session |
| IOSession iosession = <...> |
| SSLIOSession sslsession = new SSLIOSession( |
| iosession, SSLMode.CLIENT, sslcontext, null); |
| iosession.setAttribute(SSLIOSession.SESSION_KEY, sslsession); |
| NHttpClientConnection conn = new DefaultNHttpClientConnection( |
| sslsession, 8 * 1024); |
| ]]></programlisting> |
| <para> |
| One can also use <classname>SSLNHttpClientConnectionFactory</classname> or <classname> |
| SSLNHttpServerConnectionFactory</classname> classes to conveniently create SSL |
| encrypterd HTTP connections. |
| </para> |
| <programlisting><![CDATA[ |
| SSLContext sslcontext = SSLContext.getInstance("Default"); |
| sslcontext.init(null, null, null); |
| // Plain I/O session |
| IOSession iosession = <...> |
| SSLNHttpClientConnectionFactory connfactory = new SSLNHttpClientConnectionFactory( |
| sslcontext, null, ConnectionConfig.DEFAULT); |
| NHttpClientConnection conn = connfactory.createConnection(iosession); |
| ]]></programlisting> |
| <section> |
| <title>SSL setup handler</title> |
| <para> |
| Applications can customize various aspects of the TLS/SSl protocol by passing a |
| custom implementation of the <interfacename>SSLSetupHandler</interfacename> |
| interface. |
| </para> |
| <para> |
| SSL events as defined by the <interfacename>SSLSetupHandler</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[ |
| SSLContext sslcontext = SSLContexts.createDefault(); |
| // Plain I/O session |
| IOSession iosession = <...> |
| |
| SSLIOSession sslsession = new SSLIOSession( |
| iosession, SSLMode.CLIENT, sslcontext, new SSLSetupHandler() { |
| |
| public void initalize(final SSLEngine sslengine) throws SSLException { |
| // Enforce TLS and disable SSL |
| sslengine.setEnabledProtocols(new String[] { |
| "TLSv1", |
| "TLSv1.1", |
| "TLSv1.2" }); |
| // 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( |
| final IOSession iosession, |
| final SSLSession sslsession) throws SSLException { |
| X509Certificate[] certs = sslsession.getPeerCertificateChain(); |
| // Examine peer certificate chain |
| for (X509Certificate cert: certs) { |
| System.out.println(cert.toString()); |
| } |
| } |
| |
| }); |
| ]]></programlisting> |
| <para> |
| <interfacename>SSLSetupHandler</interfacename> impelemntations can also be used with |
| the <classname>SSLNHttpClientConnectionFactory</classname> or <classname> |
| SSLNHttpServerConnectionFactory</classname> classes. |
| </para> |
| <programlisting><![CDATA[ |
| SSLContext sslcontext = SSLContexts.createDefault(); |
| // Plain I/O session |
| IOSession iosession = <...> |
| |
| SSLSetupHandler mysslhandler = new SSLSetupHandler() { |
| |
| public void initalize(final SSLEngine sslengine) throws SSLException { |
| // Enforce TLS and disable SSL |
| sslengine.setEnabledProtocols(new String[] { |
| "TLSv1", |
| "TLSv1.1", |
| "TLSv1.2" }); |
| } |
| |
| public void verify( |
| final IOSession iosession, final SSLSession sslsession) throws SSLException { |
| } |
| |
| |
| }; |
| SSLNHttpClientConnectionFactory connfactory = new SSLNHttpClientConnectionFactory( |
| sslcontext, mysslhandler, ConnectionConfig.DEFAULT); |
| NHttpClientConnection conn = connfactory.createConnection(iosession); |
| ]]></programlisting> |
| </section> |
| </section> |
| <section> |
| <title>TLS/SSL aware I/O event dispatches</title> |
| <para> |
| Default <interfacename>IOEventDispatch</interfacename> implementations shipped with |
| the library such as <classname>DefaultHttpServerIODispatch</classname> and <classname> |
| DefaultHttpClientIODispatch</classname> automatically detect SSL encrypted sessions |
| and handle SSL transport aspects transparently. However, custom I/O event dispatchers |
| that do not extend <classname>AbstractIODispatch</classname> are required to take some |
| additional actions to ensure correct functioning of the transport layer encryption. |
| </para> |
| <itemizedlist> |
| <listitem> |
| <para> |
| The I/O dispatch may need to call <methodname>SSLIOSession#initalize() |
| </methodname> method in order to put the SSL session either into a client or |
| a server mode, if the SSL session has not been yet initialized. |
| </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 necessary |
| SSL handshaking and encrypt application data. |
| </para> |
| </listitem> |
| </itemizedlist> |
| </section> |
| </section> |
| <section> |
| <title>Embedded non-blocking HTTP server</title> |
| <para> |
| As of version 4.4 HttpCore ships with an embedded non-blocking HTTP server based on |
| non-blocking I/O components described above. |
| </para> |
| <programlisting><![CDATA[ |
| HttpAsyncRequestHandler<?> 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() { |
| @Override |
| public void run() { |
| server.shutdown(5, TimeUnit.SECONDS); |
| } |
| }); |
| ]]></programlisting> |
| </section> |
| </chapter> |