blob: 8651ce9131083726f9b732bb0535cc89e95f0589 [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="blocking-io">
<title>Blocking I/O model</title>
<para>Blocking (or classic) I/O in Java represents a highly efficient and convenient I/O model
well suited for high performance applications where the number of concurrent connections is
relatively moderate. Modern JVMs are capable of efficient context switching and the blocking
I/O model should offer the best performance in terms of raw data throughput as long as
the number of concurrent connections is below one thousand and connections are mostly busy
transmitting data. However for applications where connections stay idle most of the time
the overhead of context switching may become substantial and a non-blocking I/O model may
present a better alternative.</para>
<section>
<title>Blocking HTTP connections</title>
<para>
HTTP connections are responsible for HTTP message serialization and deserialization. One
should rarely need to use HTTP connection objects directly. There are higher level protocol
components intended for execution and processing of HTTP requests. However, in some cases
direct interaction with HTTP connections may be necessary, for instance, to access
properties such as the connection status, the socket timeout or the local and remote
addresses.
</para>
<para>
It is important to bear in mind that HTTP connections are not thread-safe. We strongly
recommend limiting all interactions with HTTP connection objects to one thread. The only
method of <interfacename>HttpConnection</interfacename> interface and its sub-interfaces
which is safe to invoke from another thread is <methodname> HttpConnection#shutdown()
</methodname>.
</para>
<section>
<title>Working with blocking HTTP connections</title>
<para>
HttpCore does not provide full support for opening connections because the process of
establishing a new connection - especially on the client side - can be very complex
when it involves one or more authenticating or/and tunneling proxies. Instead, blocking
HTTP connections can be bound to any arbitrary network socket.
</para>
<programlisting><![CDATA[
Socket socket = <...>
DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1024);
conn.bind(socket);
System.out.println(conn.isOpen());
HttpConnectionMetrics metrics = conn.getMetrics();
System.out.println(metrics.getRequestCount());
System.out.println(metrics.getResponseCount());
System.out.println(metrics.getReceivedBytesCount());
System.out.println(metrics.getSentBytesCount());
]]></programlisting>
<para>
HTTP connection interfaces, both client and server, send and receive messages in two
stages. The message head is transmitted first. Depending on properties of the message
head, a message body may follow it. Please note it is very important to always
close the underlying content stream in order to signal that the processing of
the message is complete. HTTP entities that stream out their content directly from the
input stream of the underlying connection must ensure they fully consume the content
of the message body for that connection to be potentially re-usable.
</para>
<para>
Over-simplified process of request execution on the client side may look like this:
</para>
<programlisting><![CDATA[
Socket socket = <...>
DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1024);
conn.bind(socket);
HttpRequest request = new BasicHttpRequest("GET", "/");
conn.sendRequestHeader(request);
HttpResponse response = conn.receiveResponseHeader();
conn.receiveResponseEntity(response);
HttpEntity entity = response.getEntity();
if (entity != null) {
// Do something useful with the entity and, when done, ensure all
// content has been consumed, so that the underlying connection
// can be re-used
EntityUtils.consume(entity);
}
]]></programlisting>
<para>
Over-simplified process of request handling on the server side may look like this:
</para>
<programlisting><![CDATA[
Socket socket = <...>
DefaultBHttpServerConnection conn = new DefaultBHttpServerConnection(8 * 1024);
conn.bind(socket);
HttpRequest request = conn.receiveRequestHeader();
if (request instanceof HttpEntityEnclosingRequest) {
conn.receiveRequestEntity((HttpEntityEnclosingRequest) request);
HttpEntity entity = ((HttpEntityEnclosingRequest) request)
.getEntity();
if (entity != null) {
// Do something useful with the entity and, when done, ensure all
// content has been consumed, so that the underlying connection
// could be re-used
EntityUtils.consume(entity);
}
}
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
200, "OK") ;
response.setEntity(new StringEntity("Got it") );
conn.sendResponseHeader(response);
conn.sendResponseEntity(response);
]]></programlisting>
<para>
Please note that one should rarely need to transmit messages using these low level
methods and should normally use the appropriate higher level HTTP service implementations instead.
</para>
</section>
<section>
<title>Content transfer with blocking I/O</title>
<para>
HTTP connections manage the process of the content transfer using the <interfacename>
HttpEntity</interfacename> interface. HTTP connections generate an entity object that
encapsulates the content stream of the incoming message. Please note that <methodname>
HttpServerConnection#receiveRequestEntity()</methodname> and <methodname>
HttpClientConnection#receiveResponseEntity()</methodname> do not retrieve or buffer any
incoming data. They merely inject an appropriate content codec based on the properties
of the incoming message. The content can be retrieved by reading from the content input
stream of the enclosed entity using <methodname>HttpEntity#getContent()</methodname>.
The incoming data will be decoded automatically and completely transparently to the data
consumer. Likewise, HTTP connections rely on <methodname>
HttpEntity#writeTo(OutputStream)</methodname> method to generate the content of an
outgoing message. If an outgoing message encloses an entity, the content will be
encoded automatically based on the properties of the message.
</para>
</section>
<section>
<title>Supported content transfer mechanisms</title>
<para>
Default implementations of HTTP connections 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. Maximum entity length: unlimited.
</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title>Chunk coding:</title>
<para>
The content is sent in small chunks. Maximum entity length: unlimited.
</para>
</formalpara>
</listitem>
</itemizedlist>
<para>
The appropriate content stream class will be created automatically depending on
properties of the entity enclosed with the message.
</para>
</section>
<section>
<title>Terminating HTTP connections</title>
<para>
HTTP connections can be terminated either gracefully by calling <methodname>
HttpConnection#close()</methodname> or forcibly by calling <methodname>
HttpConnection#shutdown()</methodname>. The former tries to flush all buffered data
prior to terminating the connection and may block indefinitely. The <methodname>
HttpConnection#close()</methodname> method is not thread-safe. The latter terminates
the connection without flushing internal buffers and returns control to the caller as
soon as possible without blocking for long. The <methodname>HttpConnection#shutdown()
</methodname> method is thread-safe.
</para>
</section>
</section>
<section>
<title>HTTP exception handling</title>
<para>
All HttpCore components potentially throw two types of exceptions: <classname>IOException
</classname>in case of an I/O failure such as socket timeout or an socket reset and
<classname>HttpException</classname> that signals an HTTP failure such as a violation of
the HTTP protocol. Usually I/O errors are considered non-fatal and recoverable, whereas
HTTP protocol errors are considered fatal and cannot be automatically recovered from.
</para>
<section>
<title>Protocol exception</title>
<para>
<classname>ProtocolException</classname> signals a fatal HTTP protocol violation that
usually results in an immediate termination of the HTTP message processing.
</para>
</section>
</section>
<section>
<title>Blocking HTTP protocol handlers</title>
<section>
<title>HTTP service</title>
<para>
<classname>HttpService</classname> is a server side HTTP protocol handler based on the
blocking I/O model that implements the essential requirements of the HTTP protocol for
the server side message processing as described by RFC 2616.
</para>
<para>
<classname>HttpService</classname> relies on <interfacename>HttpProcessor
</interfacename> instance 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 handlers are expected to take care of
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();
HttpService httpService = new HttpService(httpproc, null);
]]></programlisting>
<section>
<title>HTTP request handlers</title>
<para>
The <interfacename>HttpRequestHandler</interfacename> interface represents a
routine for processing of a specific group of HTTP requests. <classname>HttpService
</classname> is 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[
HttpRequestHandler myRequestHandler = new HttpRequestHandler() {
public void handle(
HttpRequest request,
HttpResponse response,
HttpContext context) throws HttpException, IOException {
response.setStatusCode(HttpStatus.SC_OK);
response.setEntity(
new StringEntity("some important message",
ContentType.TEXT_PLAIN));
}
};
]]></programlisting>
</section>
<section>
<title>Request handler resolver</title>
<para>
HTTP request handlers are usually managed by a <interfacename>
HttpRequestHandlerResolver</interfacename> that matches a request URI to a request
handler. HttpCore includes a very simple implementation of the request handler
resolver based on a trivial pattern matching algorithm: <classname>
HttpRequestHandlerRegistry</classname> supports only three formats:
<literal>*</literal>, <literal>&lt;uri&gt;*</literal> and
<literal>*&lt;uri&gt;</literal>.
</para>
<programlisting><![CDATA[
HttpProcessor httpproc = <...>
HttpRequestHandler myRequestHandler1 = <...>
HttpRequestHandler myRequestHandler2 = <...>
HttpRequestHandler myRequestHandler3 = <...>
UriHttpRequestHandlerMapper handlerMapper = new UriHttpRequestHandlerMapper();
handlerMapper.register("/service/*", myRequestHandler1);
handlerMapper.register("*.do", myRequestHandler2);
handlerMapper.register("*", myRequestHandler3);
HttpService httpService = new HttpService(httpproc, handlerMapper);
]]></programlisting>
<para>
Users are encouraged to provide more sophisticated implementations of
<interfacename>HttpRequestHandlerResolver</interfacename> - for instance, based on
regular expressions.
</para>
</section>
<section>
<title>Using HTTP service to handle requests</title>
<para>
When fully initialized and configured, the <classname>HttpService</classname> can
be used to execute and handle requests for active HTTP connections. The
<methodname>HttpService#handleRequest()</methodname> method reads an incoming
request, generates a response and sends it back to the client. This method can be
executed in a loop to handle multiple requests on a persistent connection. The
<methodname>HttpService#handleRequest()</methodname> method is safe to execute from
multiple threads. This allows processing of requests on several connections
simultaneously, as long as all the protocol interceptors and requests handlers used
by the <classname>HttpService</classname> are thread-safe.
</para>
<programlisting><![CDATA[
HttpService httpService = <...>
HttpServerConnection conn = <...>
HttpContext context = <...>
boolean active = true;
try {
while (active && conn.isOpen()) {
httpService.handleRequest(conn, context);
}
} finally {
conn.shutdown();
}
]]></programlisting>
</section>
</section>
<section>
<title>HTTP request executor</title>
<para>
<classname>HttpRequestExecutor</classname> is a client side HTTP protocol handler based
on the blocking I/O model that implements the essential requirements of the HTTP
protocol for the client side message processing, as described by RFC 2616.
The <classname>HttpRequestExecutor</classname> relies on the <interfacename>HttpProcessor
</interfacename> instance to generate mandatory protocol headers for all outgoing
messages and apply common, cross-cutting message transformations to all incoming and
outgoing messages. Application specific processing can be implemented outside
<classname>HttpRequestExecutor</classname> once the request has been executed and a
response has been received.
</para>
<programlisting><![CDATA[
HttpClientConnection conn = <...>
HttpProcessor httpproc = HttpProcessorBuilder.create()
.add(new RequestContent())
.add(new RequestTargetHost())
.add(new RequestConnControl())
.add(new RequestUserAgent("MyClient/1.1"))
.add(new RequestExpectContinue(true))
.build();
HttpRequestExecutor httpexecutor = new HttpRequestExecutor();
HttpRequest request = new BasicHttpRequest("GET", "/");
HttpCoreContext context = HttpCoreContext.create();
httpexecutor.preProcess(request, httpproc, context);
HttpResponse response = httpexecutor.execute(request, conn, context);
httpexecutor.postProcess(response, httpproc, context);
HttpEntity entity = response.getEntity();
EntityUtils.consume(entity);
]]></programlisting>
<para>
Methods of <classname>HttpRequestExecutor</classname> are safe to execute from multiple
threads. This allows execution of requests on several connections simultaneously, as
long as all the protocol interceptors used by the <classname>HttpRequestExecutor
</classname> are thread-safe.
</para>
</section>
<section>
<title>Connection persistence / re-use</title>
<para>
The <interfacename>ConnectionReuseStrategy</interfacename> interface is intended to
determine whether the underlying connection can be re-used for processing of further
messages after the transmission of the current message has been completed. The default
connection re-use strategy attempts to keep connections alive whenever possible.
Firstly, it examines the version of the HTTP protocol used to transmit the message.
<literal>HTTP/1.1</literal> connections are persistent by default, while <literal>
HTTP/1.0</literal> connections are not. Secondly, it examines the value of the
<literal>Connection</literal> header. The peer can indicate whether it intends to
re-use the connection on the opposite side by sending <literal>Keep-Alive</literal> or
<literal>Close</literal> values in the <literal>Connection</literal> header. Thirdly,
the strategy makes the decision whether the connection is safe to re-use based on the
properties of the enclosed entity, if available.
</para>
</section>
</section>
<section>
<title>Connection pools</title>
<para>
Efficient client-side HTTP transports often requires effective re-use of persistent
connections. HttpCore facilitates the process of connection re-use by providing support
for managing pools of persistent HTTP connections. Connection pool implementations are
thread-safe and can be used concurrently by multiple consumers.
</para>
<para>
By default the pool allows only 20 concurrent connections in total and two concurrent
connections per a unique route. The two connection limit is due to the requirements of the
HTTP specification. However, in practical terms this can often be too restrictive. One can
change the pool configuration at runtime to allow for more concurrent connections depending
on a particular application context.
</para>
<programlisting><![CDATA[
HttpHost target = new HttpHost("localhost");
BasicConnPool connpool = new BasicConnPool();
connpool.setMaxTotal(200);
connpool.setDefaultMaxPerRoute(10);
connpool.setMaxPerRoute(target, 20);
Future<BasicPoolEntry> future = connpool.lease(target, null);
BasicPoolEntry poolEntry = future.get();
HttpClientConnection conn = poolEntry.getConnection();
]]></programlisting>
<para>
Please note that the connection pool has no way of knowing whether or not a leased
connection is still being used. It is the responsibility of the connection pool user
to ensure that the connection is released back to the pool once it is not longer needed,
even if the connection is not reusable.
</para>
<programlisting><![CDATA[
BasicConnPool connpool = <...>
Future<BasicPoolEntry> future = connpool.lease(target, null);
BasicPoolEntry poolEntry = future.get();
try {
HttpClientConnection conn = poolEntry.getConnection();
} finally {
connpool.release(poolEntry, true);
}
]]></programlisting>
<para>
The state of the connection pool can be interrogated at runtime.
</para>
<programlisting><![CDATA[
HttpHost target = new HttpHost("localhost");
BasicConnPool connpool = <...>
PoolStats totalStats = connpool.getTotalStats();
System.out.println("total available: " + totalStats.getAvailable());
System.out.println("total leased: " + totalStats.getLeased());
System.out.println("total pending: " + totalStats.getPending());
PoolStats targetStats = connpool.getStats(target);
System.out.println("target available: " + targetStats.getAvailable());
System.out.println("target leased: " + targetStats.getLeased());
System.out.println("target pending: " + targetStats.getPending());
]]></programlisting>
<para>
Please note that connection pools do not pro-actively evict expired connections. Even though
expired connection cannot be leased to the requester, the pool may accumulate stale
connections over time especially after a period of inactivity. It is generally advisable
to force eviction of expired and idle connections from the pool after an extensive period
of inactivity.
</para>
<programlisting><![CDATA[
BasicConnPool connpool = <...>
connpool.closeExpired();
connpool.closeIdle(1, TimeUnit.MINUTES);
]]></programlisting>
<para>
Generally it is considered to be a responsibility of the consumer to keep track of
connections leased from the pool and to ensure their immediate release as soon as they
are no longer needed or actively used. Nevertheless <classname>BasicConnPool </classname>
provides protected methods to enumerate available idle connections and those currently
leased from the pool. This enables the pool consumer to query connection state and
selectively terminate connections meeting a particular criterion.
</para>
<programlisting><![CDATA[
static class MyBasicConnPool extends BasicConnPool {
@Override
protected void enumAvailable(final PoolEntryCallback<HttpHost, HttpClientConnection> callback) {
super.enumAvailable(callback);
}
@Override
protected void enumLeased(final PoolEntryCallback<HttpHost, HttpClientConnection> callback) {
super.enumLeased(callback);
}
}
]]></programlisting>
<programlisting><![CDATA[
MyBasicConnPool connpool = new MyBasicConnPool();
connpool.enumAvailable(new PoolEntryCallback<HttpHost, HttpClientConnection>() {
@Override
public void process(final PoolEntry<HttpHost, HttpClientConnection> entry) {
Date creationTime = new Date(entry.getCreated());
if (creationTime.before(someTime)) {
entry.close();
}
}
});
]]></programlisting>
</section>
<section>
<title>TLS/SSL support</title>
<para>
Blocking connections can be bound to any arbitrary socket. This makes SSL support quite
straight-forward. Any <classname>SSLSocket</classname> instance can be bound to a blocking
connection in order to make all messages transmitted over than connection secured by
TLS/SSL.
</para>
<programlisting><![CDATA[
SSLContext sslcontext = SSLContexts.createSystemDefault();
SocketFactory sf = sslcontext.getSocketFactory();
SSLSocket socket = (SSLSocket) sf.createSocket("somehost", 443);
// Enforce TLS and disable SSL
socket.setEnabledProtocols(new String[] {
"TLSv1",
"TLSv1.1",
"TLSv1.2" });
// Enforce strong ciphers
socket.setEnabledCipherSuites(new String[] {
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA" });
DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1204);
conn.bind(socket);
]]></programlisting>
</section>
<section>
<title>Embedded HTTP server</title>
<para>
As of version 4.4 HttpCore ships with an embedded HTTP server based on blocking I/O components
described above.
</para>
<programlisting><![CDATA[
HttpRequestHandler requestHandler = <...>
HttpProcessor httpProcessor = <...>
SocketConfig socketConfig = SocketConfig.custom()
.setSoTimeout(15000)
.setTcpNoDelay(true)
.build();
final HttpServer server = ServerBootstrap.bootstrap()
.setListenerPort(8080)
.setHttpProcessor(httpProcessor)
.setSocketConfig(socketConfig)
.setExceptionLogger(new StdErrorExceptionLogger())
.registerHandler("*", requestHandler)
.create();
server.start();
server.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
server.shutdown(5, TimeUnit.SECONDS);
}
});
]]></programlisting>
</section>
</chapter>