blob: 32081105db0f8d20c1880f9b824df3f7aa94738d [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="advanced">
<title>Advanced topics</title>
<section>
<title>Custom client connections</title>
<para>In certain situations it may be necessary to customize the way HTTP messages get
transmitted across the wire beyond what is possible using HTTP parameters in
order to be able to deal non-standard, non-compliant behaviours. For instance, for web
crawlers it may be necessary to force HttpClient into accepting malformed response heads
in order to salvage the content of the messages. </para>
<para>Usually the process of plugging in a custom message parser or a custom connection
implementation involves several steps:</para>
<itemizedlist>
<listitem>
<para>Provide a custom <interfacename>LineParser</interfacename> /
<interfacename>LineFormatter</interfacename> interface implementation.
Implement message parsing / formatting logic as required.</para>
<programlisting><![CDATA[
class MyLineParser extends BasicLineParser {
@Override
public Header parseHeader(
final CharArrayBuffer buffer) throws ParseException {
try {
return super.parseHeader(buffer);
} catch (ParseException ex) {
// Suppress ParseException exception
return new BasicHeader("invalid", buffer.toString());
}
}
}
]]></programlisting>
</listitem>
<listitem>
<para>Provide a custom <interfacename>OperatedClientConnection</interfacename>
implementation. Replace default request / response parsers, request / response
formatters with custom ones as required. Implement different message writing /
reading code if necessary.</para>
<programlisting><![CDATA[
class MyClientConnection extends DefaultClientConnection {
@Override
protected HttpMessageParser createResponseParser(
final SessionInputBuffer buffer,
final HttpResponseFactory responseFactory,
final HttpParams params) {
return new DefaultResponseParser(
buffer,
new MyLineParser(),
responseFactory,
params);
}
}
]]></programlisting>
</listitem>
<listitem>
<para>Provide a custom <interfacename>ClientConnectionOperator</interfacename>
interface implementation in order to create connections of new class. Implement
different socket initialization code if necessary.</para>
<programlisting><![CDATA[
class MyClientConnectionOperator extends DefaultClientConnectionOperator {
public MyClientConnectionOperator(final SchemeRegistry sr) {
super(sr);
}
@Override
public OperatedClientConnection createConnection() {
return new MyClientConnection();
}
}
]]></programlisting>
</listitem>
<listitem>
<para>Provide a custom <interfacename>ClientConnectionManager</interfacename>
interface implementation in order to create connection operator of new
class.</para>
<programlisting><![CDATA[
class MyClientConnManager extends SingleClientConnManager {
public MyClientConnManager(
final HttpParams params,
final SchemeRegistry sr) {
super(params, sr);
}
@Override
protected ClientConnectionOperator createConnectionOperator(
final SchemeRegistry sr) {
return new MyClientConnectionOperator(sr);
}
}
]]></programlisting>
</listitem>
</itemizedlist>
</section>
<section id="stateful_conn">
<title>Stateful HTTP connections</title>
<para>While HTTP specification assumes that session state information is always embedded in
HTTP messages in the form of HTTP cookies and therefore HTTP connections are always
stateless, this assumption does not always hold true in real life. There are cases when
HTTP connections are created with a particular user identity or within a particular
security context and therefore cannot be shared with other users and can be reused by
the same user only. Examples of such stateful HTTP connections are
<literal>NTLM</literal> authenticated connections and SSL connections with client
certificate authentication.</para>
<section>
<title>User token handler</title>
<para>HttpClient relies on <interfacename>UserTokenHandler</interfacename> interface to
determine if the given execution context is user specific or not. The token object
returned by this handler is expected to uniquely identify the current user if the
context is user specific or to be null if the context does not contain any resources
or details specific to the current user. The user token will be used to ensure that
user specific resources will not be shared with or reused by other users.</para>
<para>The default implementation of the <interfacename>UserTokenHandler</interfacename>
interface uses an instance of Principal class to represent a state object for HTTP
connections, if it can be obtained from the given execution context.
<classname>DefaultUserTokenHandler</classname> will use the user principle of
connection based authentication schemes such as <literal>NTLM</literal> or that of
the SSL session with client authentication turned on. If both are unavailable, null
token will be returned.</para>
<para>Users can provide a custom implementation if the default one does not satisfy
their needs:</para>
<programlisting><![CDATA[
DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.setUserTokenHandler(new UserTokenHandler() {
public Object getUserToken(HttpContext context) {
return context.getAttribute("my-token");
}
});
]]></programlisting>
</section>
<section>
<title>User token and execution context</title>
<para>In the course of HTTP request execution HttpClient adds the following user
identity related objects to the execution context: </para>
<itemizedlist>
<listitem>
<formalpara>
<title><constant>ClientContext.USER_TOKEN</constant>='http.user-token':</title>
<para>Object instance representing the actual user identity, usually
expected to be an instance of <interfacename>Principle</interfacename>
interface</para>
</formalpara>
</listitem>
</itemizedlist>
<para>One can find out whether or not the connection used to execute the request was
stateful by examining the content of the local HTTP context after the request has
been executed.</para>
<programlisting><![CDATA[
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpGet httpget = new HttpGet("http://localhost:8080/");
HttpResponse response = httpclient.execute(httpget, localContext);
HttpEntity entity = response.getEntity();
EntityUtils.consume(entity);
Object userToken = localContext.getAttribute(ClientContext.USER_TOKEN);
System.out.println(userToken);
]]></programlisting>
<section>
<title>Persistent stateful connections</title>
<para>Please note that a persistent connection that carries a state object can be reused
only if the same state object is bound to the execution context when requests
are executed. So, it is really important to ensure the either same context is
reused for execution of subsequent HTTP requests by the same user or the user
token is bound to the context prior to request execution.</para>
<programlisting><![CDATA[
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext1 = new BasicHttpContext();
HttpGet httpget1 = new HttpGet("http://localhost:8080/");
HttpResponse response1 = httpclient.execute(httpget1, localContext1);
HttpEntity entity1 = response1.getEntity();
EntityUtils.consume(entity1);
Principal principal = (Principal) localContext1.getAttribute(
ClientContext.USER_TOKEN);
HttpContext localContext2 = new BasicHttpContext();
localContext2.setAttribute(ClientContext.USER_TOKEN, principal);
HttpGet httpget2 = new HttpGet("http://localhost:8080/");
HttpResponse response2 = httpclient.execute(httpget2, localContext2);
HttpEntity entity2 = response2.getEntity();
EntityUtils.consume(entity2);
]]></programlisting>
</section>
</section>
</section>
</chapter>