| <?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="statemgmt"> |
| <title>HTTP state management</title> |
| <para>Originally HTTP was designed as a stateless, request / response oriented protocol that |
| made no special provisions for stateful sessions spanning across several logically related |
| request / response exchanges. As HTTP protocol grew in popularity and adoption more and more |
| systems began to use it for applications it was never intended for, for instance as a |
| transport for e-commerce applications. Thus, the support for state management became a |
| necessity.</para> |
| <para>Netscape Communications, at that time a leading developer of web client and server |
| software, implemented support for HTTP state management in their products based on a |
| proprietary specification. Later, Netscape tried to standardise the mechanism by publishing |
| a specification draft. Those efforts contributed to the formal specification defined through |
| the RFC standard track. However, state management in a significant number of applications is |
| still largely based on the Netscape draft and is incompatible with the official |
| specification. All major developers of web browsers felt compelled to retain compatibility |
| with those applications greatly contributing to the fragmentation of standards |
| compliance.</para> |
| <section> |
| <title>HTTP cookies</title> |
| <para>An HTTP cookie is a token or short packet of state information that the HTTP agent and the |
| target server can exchange to maintain a session. Netscape engineers used to refer to it |
| as a "magic cookie" and the name stuck.</para> |
| <para>HttpClient uses the <interfacename>Cookie</interfacename> interface to represent an |
| abstract cookie token. In its simplest form an HTTP cookie is merely a name / value pair. |
| Usually an HTTP cookie also contains a number of attributes such as version, a domain |
| for which is valid, a path that specifies the subset of URLs on the origin server to |
| which this cookie applies, and the maximum period of time for which the cookie is valid.</para> |
| <para>The <interfacename>SetCookie</interfacename> interface represents a |
| <literal>Set-Cookie</literal> response header sent by the origin server to the HTTP |
| agent in order to maintain a conversational state. |
| The <interfacename>SetCookie2</interfacename> interface extends SetCookie with |
| <literal>Set-Cookie2</literal> specific methods.</para> |
| <para>The <interfacename>ClientCookie</interfacename> interface extends |
| <interfacename>Cookie</interfacename> interface with additional client specific |
| functionality such as the ability to retrieve original cookie attributes exactly as they were |
| specified by the origin server. This is important for generating the |
| <literal>Cookie</literal> header because some cookie specifications require that the |
| <literal>Cookie</literal> header should include certain attributes only if they were |
| specified in the <literal>Set-Cookie</literal> or <literal>Set-Cookie2</literal> |
| header.</para> |
| <section> |
| <title>Cookie versions</title> |
| <para>Cookies compatible with Netscape draft specification but non-compliant with the |
| official specification are considered to be of version 0. Standard compliant cookies |
| are expected to have version 1. HttpClient may handle cookies differently depending |
| on the version.</para> |
| <para>Here is an example of re-creating a Netscape cookie:</para> |
| <programlisting><![CDATA[ |
| BasicClientCookie netscapeCookie = new BasicClientCookie("name", "value"); |
| netscapeCookie.setVersion(0); |
| netscapeCookie.setDomain(".mycompany.com"); |
| netscapeCookie.setPath("/"); |
| ]]></programlisting> |
| <para>Here is an example of re-creating a standard cookie. Please note that standard |
| compliant cookie must retain all attributes as sent by the origin server:</para> |
| <programlisting><![CDATA[ |
| BasicClientCookie stdCookie = new BasicClientCookie("name", "value"); |
| stdCookie.setVersion(1); |
| stdCookie.setDomain(".mycompany.com"); |
| stdCookie.setPath("/"); |
| stdCookie.setSecure(true); |
| // Set attributes EXACTLY as sent by the server |
| stdCookie.setAttribute(ClientCookie.VERSION_ATTR, "1"); |
| stdCookie.setAttribute(ClientCookie.DOMAIN_ATTR, ".mycompany.com"); |
| ]]></programlisting> |
| <para>Here is an example of re-creating a <literal>Set-Cookie2</literal> compliant |
| cookie. Please note that standard compliant cookie must retain all attributes as |
| sent by the origin server:</para> |
| <programlisting><![CDATA[ |
| BasicClientCookie2 stdCookie = new BasicClientCookie2("name", "value"); |
| stdCookie.setVersion(1); |
| stdCookie.setDomain(".mycompany.com"); |
| stdCookie.setPorts(new int[] {80,8080}); |
| stdCookie.setPath("/"); |
| stdCookie.setSecure(true); |
| // Set attributes EXACTLY as sent by the server |
| stdCookie.setAttribute(ClientCookie.VERSION_ATTR, "1"); |
| stdCookie.setAttribute(ClientCookie.DOMAIN_ATTR, ".mycompany.com"); |
| stdCookie.setAttribute(ClientCookie.PORT_ATTR, "80,8080"); |
| ]]></programlisting> |
| </section> |
| </section> |
| <section> |
| <title>Cookie specifications</title> |
| <para>The <interfacename>CookieSpec</interfacename> interface represents a cookie management |
| specification. The cookie management specification is expected to enforce:</para> |
| <itemizedlist> |
| <listitem> |
| <para>rules of parsing <literal>Set-Cookie</literal> and optionally |
| <literal>Set-Cookie2</literal> headers.</para> |
| </listitem> |
| <listitem> |
| <para>rules of validation of parsed cookies.</para> |
| </listitem> |
| <listitem> |
| <para>formatting of <literal>Cookie</literal> header for a given host, port and path |
| of origin.</para> |
| </listitem> |
| </itemizedlist> |
| <para>HttpClient ships with several <interfacename>CookieSpec</interfacename> |
| implementations:</para> |
| <itemizedlist> |
| <listitem> |
| <formalpara> |
| <title>Netscape draft:</title> |
| <para>This specification conforms to the original draft specification published |
| by Netscape Communications. It should be avoided unless absolutely necessary |
| for compatibility with legacy code.</para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title>Standard:</title> |
| <para>RFC 2965 HTTP state management specification.</para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title>Browser compatibility:</title> |
| <para>This implementation strives to closely mimic the (mis)behavior of common web |
| browser applications such as Microsoft Internet Explorer and Mozilla |
| FireFox.</para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title>Best match:</title> |
| <para>'Meta' cookie specification that picks up a cookie policy based on the |
| format of cookies sent with the HTTP response. It basically aggregates all |
| above implementations into one class.</para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <title>Ignore cookies:</title> |
| <para>All cookies are ignored.</para> |
| </formalpara> |
| </listitem> |
| </itemizedlist> |
| <para>It is strongly recommended to use the <literal>Best Match</literal> policy and let |
| HttpClient pick up an appropriate compliance level at runtime based on the execution |
| context.</para> |
| </section> |
| <section> |
| <title>Choosing cookie policy</title> |
| <para>Cookie policy can be set at the HTTP client and overridden on the HTTP request level |
| if required.</para> |
| <programlisting><![CDATA[ |
| RequestConfig globalConfig = RequestConfig.custom() |
| .setCookieSpec(CookieSpecs.BEST_MATCH) |
| .build(); |
| CloseableHttpClient httpclient = HttpClients.custom() |
| .setDefaultRequestConfig(globalConfig) |
| .build(); |
| RequestConfig localConfig = RequestConfig.copy(globalConfig) |
| .setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY) |
| .build(); |
| HttpGet httpGet = new HttpGet("/"); |
| httpGet.setConfig(localConfig); |
| ]]></programlisting> |
| </section> |
| <section> |
| <title>Custom cookie policy</title> |
| <para>In order to implement a custom cookie policy one should create a custom implementation |
| of the <interfacename>CookieSpec</interfacename> interface, create a |
| <interfacename>CookieSpecProvider</interfacename> implementation to create and |
| initialize instances of the custom specification and register the factory with |
| HttpClient. Once the custom specification has been registered, it can be activated the |
| same way as a standard cookie specification.</para> |
| <programlisting><![CDATA[ |
| CookieSpecProvider easySpecProvider = new CookieSpecProvider() { |
| |
| public CookieSpec create(HttpContext context) { |
| |
| return new BrowserCompatSpec() { |
| @Override |
| public void validate(Cookie cookie, CookieOrigin origin) |
| throws MalformedCookieException { |
| // Oh, I am easy |
| } |
| }; |
| } |
| |
| }; |
| Registry<CookieSpecProvider> r = RegistryBuilder.<CookieSpecProvider>create() |
| .register(CookieSpecs.BEST_MATCH, |
| new BestMatchSpecFactory()) |
| .register(CookieSpecs.BROWSER_COMPATIBILITY, |
| new BrowserCompatSpecFactory()) |
| .register("easy", easySpecProvider) |
| .build(); |
| |
| RequestConfig requestConfig = RequestConfig.custom() |
| .setCookieSpec("easy") |
| .build(); |
| |
| CloseableHttpClient httpclient = HttpClients.custom() |
| .setDefaultCookieSpecRegistry(r) |
| .setDefaultRequestConfig(requestConfig) |
| .build(); |
| ]]></programlisting> |
| </section> |
| <section> |
| <title>Cookie persistence</title> |
| <para>HttpClient can work with any physical representation of a persistent cookie store that |
| implements the <interfacename>CookieStore</interfacename> interface. The default |
| <interfacename>CookieStore</interfacename> implementation called |
| <classname>BasicCookieStore</classname> is a simple implementation backed by a |
| <classname>java.util.ArrayList</classname>. Cookies stored in an |
| <classname>BasicClientCookie</classname> object are lost when the container object |
| get garbage collected. Users can provide more complex implementations if |
| necessary.</para> |
| <programlisting><![CDATA[ |
| // Create a local instance of cookie store |
| CookieStore cookieStore = new BasicCookieStore(); |
| // Populate cookies if needed |
| BasicClientCookie cookie = new BasicClientCookie("name", "value"); |
| cookie.setVersion(0); |
| cookie.setDomain(".mycompany.com"); |
| cookie.setPath("/"); |
| cookieStore.addCookie(cookie); |
| // Set the store |
| CloseableHttpClient httpclient = HttpClients.custom() |
| .setDefaultCookieStore(cookieStore) |
| .build(); |
| ]]></programlisting> |
| </section> |
| <section> |
| <title>HTTP state management and execution context</title> |
| <para>In the course of HTTP request execution HttpClient adds the following state management |
| related objects to the execution context:</para> |
| <itemizedlist> |
| <listitem> |
| <formalpara> |
| <para><interfacename>Lookup</interfacename> instance representing the actual |
| cookie specification registry. The value of this attribute set in the local |
| context takes precedence over the default one.</para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <para><interfacename>CookieSpec</interfacename> instance representing the actual |
| cookie specification.</para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <para><classname>CookieOrigin</classname> instance representing the actual |
| details of the origin server.</para> |
| </formalpara> |
| </listitem> |
| <listitem> |
| <formalpara> |
| <para><interfacename>CookieStore</interfacename> instance representing the actual |
| cookie store. The value of this attribute set in the local context takes |
| precedence over the default one.</para> |
| </formalpara> |
| </listitem> |
| </itemizedlist> |
| <para>The local <interfacename>HttpContext</interfacename> object can be used to customize |
| the HTTP state management context prior to request execution, or to examine its state after |
| the request has been executed. One can also use separate execution contexts in order |
| to implement per user (or per thread) state management. A cookie specification registry |
| and cookie store defined in the local context will take precedence over the default |
| ones set at the HTTP client level</para> |
| <programlisting><![CDATA[ |
| CloseableHttpClient httpclient = <...> |
| |
| Lookup<CookieSpecProvider> cookieSpecReg = <...> |
| CookieStore cookieStore = <...> |
| |
| HttpClientContext context = HttpClientContext.create(); |
| context.setCookieSpecRegistry(cookieSpecReg); |
| context.setCookieStore(cookieStore); |
| HttpGet httpget = new HttpGet("http://somehost/"); |
| CloseableHttpResponse response1 = httpclient.execute(httpget, context); |
| <...> |
| // Cookie origin details |
| CookieOrigin cookieOrigin = context.getCookieOrigin(); |
| // Cookie spec used |
| CookieSpec cookieSpec = context.getCookieSpec(); |
| ]]></programlisting> |
| </section> |
| </chapter> |