| <?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> |