Updated HttpClient tutorial to 4.3 APIs (fundamentals, auth, caching and fluent facade chapters)
git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1514003 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
71ef668258
commit
a418347845
|
@ -42,80 +42,38 @@ class MyLineParser extends BasicLineParser {
|
|||
|
||||
@Override
|
||||
public Header parseHeader(
|
||||
final CharArrayBuffer buffer) throws ParseException {
|
||||
CharArrayBuffer buffer) throws ParseException {
|
||||
try {
|
||||
return super.parseHeader(buffer);
|
||||
} catch (ParseException ex) {
|
||||
// Suppress ParseException exception
|
||||
return new BasicHeader("invalid", buffer.toString());
|
||||
return new BasicHeader(buffer.toString(), null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
]]></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>
|
||||
<para>Provide a custom <interfacename>HttpConnectionFactory</interfacename>
|
||||
implementation. Replace default request writer and / or response parser
|
||||
with custom ones as required. </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);
|
||||
}
|
||||
|
||||
}
|
||||
HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory =
|
||||
new ManagedHttpClientConnectionFactory(
|
||||
new DefaultHttpRequestWriterFactory(),
|
||||
new DefaultHttpResponseParserFactory(
|
||||
new MyLineParser(), new DefaultHttpResponseFactory()));
|
||||
]]></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>
|
||||
<para>Configure HttpClient to use the custom connection factory.</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);
|
||||
}
|
||||
|
||||
}
|
||||
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(
|
||||
connFactory);
|
||||
CloseableHttpClient httpclient = HttpClients.custom()
|
||||
.setConnectionManager(cm)
|
||||
.build();
|
||||
]]></programlisting>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
|
@ -145,73 +103,62 @@ class MyClientConnManager extends SingleClientConnManager {
|
|||
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>
|
||||
<programlisting><![CDATA[
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
HttpGet httpget = new HttpGet("http://localhost:8080/");
|
||||
CloseableHttpResponse response = httpclient.execute(httpget, context);
|
||||
try {
|
||||
Principal principal = context.getUserToken(Principal.class);
|
||||
System.out.println(principal);
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
]]></programlisting>
|
||||
<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() {
|
||||
UserTokenHandler userTokenHandler = new UserTokenHandler() {
|
||||
|
||||
public Object getUserToken(HttpContext context) {
|
||||
return context.getAttribute("my-token");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
CloseableHttpClient httpclient = HttpClients.custom()
|
||||
.setUserTokenHandler(userTokenHandler)
|
||||
.build();
|
||||
]]></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>
|
||||
<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 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);
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
HttpClientContext context1 = HttpClientContext.create();
|
||||
HttpGet httpget1 = new HttpGet("http://localhost:8080/");
|
||||
CloseableHttpResponse response1 = httpclient.execute(httpget1, context1);
|
||||
try {
|
||||
HttpEntity entity1 = response1.getEntity();
|
||||
} finally {
|
||||
response1.close();
|
||||
}
|
||||
Principal principal = context1.getUserToken(Principal.class);
|
||||
|
||||
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);
|
||||
HttpClientContext context2 = HttpClientContext.create();
|
||||
context2.setUserToken(principal);
|
||||
HttpGet httpget2 = new HttpGet("http://localhost:8080/");
|
||||
CloseableHttpResponse response2 = httpclient.execute(httpget2, context2);
|
||||
try {
|
||||
HttpEntity entity2 = response2.getEntity();
|
||||
} finally {
|
||||
response2.close();
|
||||
}
|
||||
]]></programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
</chapter>
|
||||
|
|
|
@ -132,100 +132,6 @@ pwd
|
|||
</listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
<section>
|
||||
<title>HTTP authentication parameters</title>
|
||||
<para>These are parameters that be used to customize the HTTP authentication process and
|
||||
behaviour of individual authentication schemes:</para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>ClientPNames.HANDLE_AUTHENTICATION</constant>='http.protocol.handle-authentication':</title>
|
||||
<para>defines whether authentication should be handled automatically. This
|
||||
parameter expects a value of type <classname>java.lang.Boolean</classname>.
|
||||
If this parameter is not set, HttpClient will handle authentication
|
||||
automatically.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>AuthPNames.CREDENTIAL_CHARSET</constant>='http.auth.credential-charset':</title>
|
||||
<para>defines the charset to be used when encoding user credentials. This
|
||||
parameter expects a value of type <literal>java.lang.String</literal>. If
|
||||
this parameter is not set, <literal>US-ASCII</literal> will be used.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>AuthPNames.TARGET_AUTH_PREF</constant>='http.auth.target-scheme-pref':</title>
|
||||
<para>Defines the order of preference for supported
|
||||
<interfacename>AuthScheme</interfacename>s when authenticating with the
|
||||
target host. This parameter expects a value of type
|
||||
<interface>java.util.Collection</interface>. The collection is expected
|
||||
to contain <classname>java.lang.String</classname> instances representing
|
||||
an id of an authentication scheme.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>AuthPNames.PROXY_AUTH_PREF</constant>='http.auth.proxy-scheme-pref':</title>
|
||||
<para>Defines the order of preference for supported
|
||||
<interfacename>AuthScheme</interfacename>s when authenticating with the
|
||||
proxy host. This parameter expects a value of type
|
||||
<interface>java.util.Collection</interface>. The collection is expected
|
||||
to contain <classname>java.lang.String</classname> instances representing
|
||||
an id of an authentication scheme.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
<para>For example, one can force HttpClient to use a different order of preference for
|
||||
authentication schemes</para>
|
||||
<programlisting><![CDATA[
|
||||
DefaultHttpClient httpclient = new DefaultHttpClient(ccm, params);
|
||||
// Choose BASIC over DIGEST for proxy authentication
|
||||
List<String> authpref = new ArrayList<String>();
|
||||
authpref.add(AuthPolicy.BASIC);
|
||||
authpref.add(AuthPolicy.DIGEST);
|
||||
httpclient.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF, authpref);
|
||||
]]></programlisting>
|
||||
</section>
|
||||
<section>
|
||||
<title>Authentication scheme registry</title>
|
||||
<para>HttpClient maintains a registry of available authentication schemes using
|
||||
the <classname>AuthSchemeRegistry</classname> class. The following schemes are
|
||||
registered per default:</para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title>AuthPolicy.BASIC:</title>
|
||||
<para>Basic authentication</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title>AuthPolicy.DIGEST:</title>
|
||||
<para>Digest authentication</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title>AuthPolicy.NTLM:</title>
|
||||
<para>NTLMv1, NTLMv2, and NTLM2 Session authentication</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title>AuthPolicy.SPNEGO:</title>
|
||||
<para>SPNEGO authentication</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title>AuthPolicy.KERBEROS:</title>
|
||||
<para>Kerberos authentication</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
<section>
|
||||
<title>Credentials provider</title>
|
||||
<para>Credentials providers are intended to maintain a set of user credentials and to be
|
||||
|
@ -289,23 +195,20 @@ null
|
|||
<itemizedlist>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>ClientContext.AUTHSCHEME_REGISTRY</constant>='http.authscheme-registry':</title>
|
||||
<para><classname>AuthSchemeRegistry</classname> instance representing the actual
|
||||
<para><interfacename>Lookup</interfacename> instance representing the actual
|
||||
authentication scheme registry. The value of this attribute set in the local
|
||||
context takes precedence over the default one.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>ClientContext.CREDS_PROVIDER</constant>='http.auth.credentials-provider':</title>
|
||||
<para><interfacename>CookieSpec</interfacename> instance representing the actual
|
||||
credentials provider. The value of this attribute set in the local context
|
||||
takes precedence over the default one.</para>
|
||||
<para><interfacename>CredentialsProvider</interfacename> instance representing
|
||||
the actual credentials provider. The value of this attribute set in the
|
||||
local context takes precedence over the default one.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>ClientContext.TARGET_AUTH_STATE</constant>='http.auth.target-scope':</title>
|
||||
<para><classname>AuthState</classname> instance representing the actual target
|
||||
authentication state. The value of this attribute set in the local context
|
||||
takes precedence over the default one.</para>
|
||||
|
@ -313,7 +216,6 @@ null
|
|||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>ClientContext.PROXY_AUTH_STATE</constant>='http.auth.proxy-scope':</title>
|
||||
<para><classname>AuthState</classname> instance representing the actual proxy
|
||||
authentication state. The value of this attribute set in the local context
|
||||
takes precedence over the default one.</para>
|
||||
|
@ -321,7 +223,6 @@ null
|
|||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>ClientContext.AUTH_CACHE</constant>='http.auth.auth-cache':</title>
|
||||
<para><interfacename>AuthCache</interfacename> instance representing the actual
|
||||
authentication data cache. The value of this attribute set in the local
|
||||
context takes precedence over the default one.</para>
|
||||
|
@ -332,18 +233,25 @@ null
|
|||
the HTTP authentication context prior to request execution, or to examine its state after
|
||||
the request has been executed:</para>
|
||||
<programlisting><![CDATA[
|
||||
HttpClient httpclient = new DefaultHttpClient();
|
||||
HttpContext localContext = new BasicHttpContext();
|
||||
HttpGet httpget = new HttpGet("http://localhost:8080/");
|
||||
HttpResponse response = httpclient.execute(httpget, localContext);
|
||||
CloseableHttpClient httpclient = <...>
|
||||
|
||||
AuthState proxyAuthState = (AuthState) localContext.getAttribute(
|
||||
ClientContext.PROXY_AUTH_STATE);
|
||||
CredentialsProvider credsProvider = <...>
|
||||
Lookup<AuthSchemeProvider> authRegistry = <...>
|
||||
AuthCache authCache = <...>
|
||||
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setCredentialsProvider(credsProvider);
|
||||
context.setAuthSchemeRegistry(authRegistry);
|
||||
context.setAuthCache(authCache);
|
||||
HttpGet httpget = new HttpGet("http://somehost/");
|
||||
CloseableHttpResponse response1 = httpclient.execute(httpget, context);
|
||||
<...>
|
||||
|
||||
AuthState proxyAuthState = context.getProxyAuthState();
|
||||
System.out.println("Proxy auth state: " + proxyAuthState.getState());
|
||||
System.out.println("Proxy auth scheme: " + proxyAuthState.getAuthScheme());
|
||||
System.out.println("Proxy auth credentials: " + proxyAuthState.getCredentials());
|
||||
AuthState targetAuthState = (AuthState) localContext.getAttribute(
|
||||
ClientContext.TARGET_AUTH_STATE);
|
||||
AuthState targetAuthState = context.getTargetAuthState();
|
||||
System.out.println("Target auth state: " + targetAuthState.getState());
|
||||
System.out.println("Target auth scheme: " + targetAuthState.getAuthScheme());
|
||||
System.out.println("Target auth credentials: " + targetAuthState.getCredentials());
|
||||
|
@ -368,12 +276,12 @@ System.out.println("Target auth credentials: " + targetAuthState.getCredentials(
|
|||
<para>Nonethess one can configure HttpClient to authenticate preemptively by prepopulating
|
||||
the authentication data cache.</para>
|
||||
<programlisting><![CDATA[
|
||||
HttpHost targetHost = new HttpHost("localhost", 80, "http");
|
||||
CloseableHttpClient httpclient = <...>
|
||||
|
||||
DefaultHttpClient httpclient = new DefaultHttpClient();
|
||||
|
||||
httpclient.getCredentialsProvider().setCredentials(
|
||||
new AuthScope(targetHost.getHostName(), targetHost.getPort()),
|
||||
HttpHost targetHost = new HttpHost("localhost", 80, "http");
|
||||
CredentialsProvider credsProvider = new BasicCredentialsProvider();
|
||||
credsProvider.setCredentials(
|
||||
new AuthScope(targetHost.getHostName(), targetHost.getPort()),
|
||||
new UsernamePasswordCredentials("username", "password"));
|
||||
|
||||
// Create AuthCache instance
|
||||
|
@ -383,14 +291,19 @@ BasicScheme basicAuth = new BasicScheme();
|
|||
authCache.put(targetHost, basicAuth);
|
||||
|
||||
// Add AuthCache to the execution context
|
||||
BasicHttpContext localcontext = new BasicHttpContext();
|
||||
localcontext.setAttribute(ClientContext.AUTH_CACHE, authCache);
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setCredentialsProvider(credsProvider);
|
||||
|
||||
HttpGet httpget = new HttpGet("/");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
HttpResponse response = httpclient.execute(targetHost, httpget, localcontext);
|
||||
HttpEntity entity = response.getEntity();
|
||||
EntityUtils.consume(entity);
|
||||
CloseableHttpResponse response = httpclient.execute(
|
||||
targetHost, httpget, context);
|
||||
try {
|
||||
HttpEntity entity = response.getEntity();
|
||||
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
]]></programlisting>
|
||||
</section>
|
||||
|
@ -428,28 +341,36 @@ for (int i = 0; i < 3; i++) {
|
|||
connection to execute more expensive methods, especially those enclose a request
|
||||
entity, such as <literal>POST</literal> or <literal>PUT</literal>. </para>
|
||||
<programlisting><![CDATA[
|
||||
DefaultHttpClient httpclient = new DefaultHttpClient();
|
||||
CloseableHttpClient httpclient = <...>
|
||||
|
||||
NTCredentials creds = new NTCredentials("user", "pwd", "myworkstation", "microsoft.com");
|
||||
httpclient.getCredentialsProvider().setCredentials(AuthScope.ANY, creds);
|
||||
CredentialsProvider credsProvider = new BasicCredentialsProvider();
|
||||
credsProvider.setCredentials(AuthScope.ANY,
|
||||
new NTCredentials("user", "pwd", "myworkstation", "microsoft.com"));
|
||||
|
||||
HttpHost target = new HttpHost("www.microsoft.com", 80, "http");
|
||||
|
||||
// Make sure the same context is used to execute logically related requests
|
||||
HttpContext localContext = new BasicHttpContext();
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setCredentialsProvider(credsProvider);
|
||||
|
||||
// Execute a cheap method first. This will trigger NTLM authentication
|
||||
HttpGet httpget = new HttpGet("/ntlm-protected/info");
|
||||
HttpResponse response1 = httpclient.execute(target, httpget, localContext);
|
||||
HttpEntity entity1 = response1.getEntity();
|
||||
EntityUtils.consume(entity1);
|
||||
CloseableHttpResponse response1 = httpclient.execute(target, httpget, context);
|
||||
try {
|
||||
HttpEntity entity1 = response1.getEntity();
|
||||
} finally {
|
||||
response1.close();
|
||||
}
|
||||
|
||||
// Execute an expensive method next reusing the same context (and connection)
|
||||
HttpPost httppost = new HttpPost("/ntlm-protected/form");
|
||||
httppost.setEntity(new StringEntity("lots and lots of data"));
|
||||
HttpResponse response2 = httpclient.execute(target, httppost, localContext);
|
||||
HttpEntity entity2 = response2.getEntity();
|
||||
EntityUtils.consume(entity2);
|
||||
CloseableHttpResponse response2 = httpclient.execute(target, httppost, context);
|
||||
try {
|
||||
HttpEntity entity2 = response2.getEntity();
|
||||
} finally {
|
||||
response2.close();
|
||||
}
|
||||
]]></programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
|
|
@ -29,29 +29,30 @@
|
|||
|
||||
<para>HttpClient Cache provides an HTTP/1.1-compliant caching layer to be
|
||||
used with HttpClient--the Java equivalent of a browser cache. The
|
||||
implementation follows the Decorator design pattern, where the
|
||||
CachingHttpClient class is a drop-in replacement for
|
||||
a DefaultHttpClient; requests that can be satisfied entirely from the cache
|
||||
will not result in actual origin requests. Stale cache entries are
|
||||
automatically validated with the origin where possible, using conditional GETs
|
||||
and the If-Modified-Since and/or If-None-Match request headers.
|
||||
implementation follows the Chain of Responsibility design pattern, where the
|
||||
caching HttpClient implementation can serve a drop-in replacement for
|
||||
the default non-caching HttpClient implementation; requests that can be
|
||||
satisfied entirely from the cache will not result in actual origin requests.
|
||||
Stale cache entries are automatically validated with the origin where possible,
|
||||
using conditional GETs and the If-Modified-Since and/or If-None-Match request
|
||||
headers.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
HTTP/1.1 caching in general is designed to be <emphasis>semantically
|
||||
transparent</emphasis>; that is, a cache should not change the meaning of
|
||||
the request-response exchange between client and server. As such, it should
|
||||
be safe to drop a CachingHttpClient into an existing compliant client-server
|
||||
be safe to drop a caching HttpClient into an existing compliant client-server
|
||||
relationship. Although the caching module is part of the client from an
|
||||
HTTP protocol point of view, the implementation aims to be compatible with
|
||||
the requirements placed on a transparent caching proxy.
|
||||
</para>
|
||||
|
||||
<para>Finally, CachingHttpClient includes support the Cache-Control
|
||||
<para>Finally, caching HttpClient includes support the Cache-Control
|
||||
extensions specified by RFC 5861 (stale-if-error and stale-while-revalidate).
|
||||
</para>
|
||||
|
||||
<para>When CachingHttpClient executes a request, it goes through the
|
||||
<para>When caching HttpClient executes a request, it goes through the
|
||||
following flow:</para>
|
||||
|
||||
<orderedlist>
|
||||
|
@ -90,7 +91,7 @@
|
|||
</listitem>
|
||||
</orderedlist>
|
||||
|
||||
<para>When CachingHttpClient receives a response, it goes through the
|
||||
<para>When caching HttpClient receives a response, it goes through the
|
||||
following flow:</para>
|
||||
|
||||
<orderedlist>
|
||||
|
@ -114,10 +115,10 @@
|
|||
</listitem>
|
||||
</orderedlist>
|
||||
|
||||
<para>It is important to note that CachingHttpClient is not, itself, an
|
||||
implementation of HttpClient, but that it decorates an instance of an
|
||||
HttpClient implementation. If you do not provide an implementation, it
|
||||
will use DefaultHttpClient internally by default.</para>
|
||||
<para>It is important to note that caching HttpClient is not, itself,
|
||||
a different implementation of HttpClient, but that it works by inserting
|
||||
itself as an additonal processing component to the request execution
|
||||
pipeline.</para>
|
||||
</section>
|
||||
|
||||
<section id="rfc2616compliance">
|
||||
|
@ -135,41 +136,50 @@
|
|||
<section>
|
||||
<title>Example Usage</title>
|
||||
|
||||
<para>This is a simple example of how to set up a basic CachingHttpClient.
|
||||
<para>This is a simple example of how to set up a basic caching HttpClient.
|
||||
As configured, it will store a maximum of 1000 cached objects, each of
|
||||
which may have a maximum body size of 8192 bytes. The numbers selected
|
||||
here are for example only and not intended to be prescriptive or
|
||||
considered as recommendations.</para>
|
||||
|
||||
<programlisting><![CDATA[
|
||||
CacheConfig cacheConfig = new CacheConfig();
|
||||
cacheConfig.setMaxCacheEntries(1000);
|
||||
cacheConfig.setMaxObjectSizeBytes(8192);
|
||||
CacheConfig cacheConfig = CacheConfig.custom()
|
||||
.setMaxCacheEntries(1000)
|
||||
.setMaxObjectSize(8192)
|
||||
.build();
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(30000)
|
||||
.setSocketTimeout(30000)
|
||||
.build();
|
||||
CloseableHttpClient cachingClient = caching HttpClients.custom()
|
||||
.setCacheConfig(cacheConfig)
|
||||
.setDefaultRequestConfig(requestConfig)
|
||||
.build();
|
||||
|
||||
HttpClient cachingClient = new CachingHttpClient(new DefaultHttpClient(), cacheConfig);
|
||||
|
||||
HttpContext localContext = new BasicHttpContext();
|
||||
HttpCacheContext context = HttpCacheContext.create();
|
||||
HttpGet httpget = new HttpGet("http://www.mydomain.com/content/");
|
||||
HttpResponse response = cachingClient.execute(httpget, localContext);
|
||||
HttpEntity entity = response.getEntity();
|
||||
EntityUtils.consume(entity);
|
||||
CacheResponseStatus responseStatus = (CacheResponseStatus) localContext.getAttribute(
|
||||
CachingHttpClient.CACHE_RESPONSE_STATUS);
|
||||
switch (responseStatus) {
|
||||
case CACHE_HIT:
|
||||
System.out.println("A response was generated from the cache with no requests " +
|
||||
"sent upstream");
|
||||
break;
|
||||
case CACHE_MODULE_RESPONSE:
|
||||
System.out.println("The response was generated directly by the caching module");
|
||||
break;
|
||||
case CACHE_MISS:
|
||||
System.out.println("The response came from an upstream server");
|
||||
break;
|
||||
case VALIDATED:
|
||||
System.out.println("The response was generated from the cache after validating " +
|
||||
"the entry with the origin server");
|
||||
break;
|
||||
CloseableHttpResponse response = cachingClient.execute(httpget, context);
|
||||
try {
|
||||
CacheResponseStatus responseStatus = context.getCacheResponseStatus();
|
||||
switch (responseStatus) {
|
||||
case CACHE_HIT:
|
||||
System.out.println("A response was generated from the cache with " +
|
||||
"no requests sent upstream");
|
||||
break;
|
||||
case CACHE_MODULE_RESPONSE:
|
||||
System.out.println("The response was generated directly by the " +
|
||||
"caching module");
|
||||
break;
|
||||
case CACHE_MISS:
|
||||
System.out.println("The response came from an upstream server");
|
||||
break;
|
||||
case VALIDATED:
|
||||
System.out.println("The response was generated from the cache " +
|
||||
"after validating the entry with the origin server");
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
]]>
|
||||
</programlisting>
|
||||
|
@ -178,11 +188,11 @@ case VALIDATED:
|
|||
<section id="configuration">
|
||||
<title>Configuration</title>
|
||||
|
||||
<para>As the CachingHttpClient is a decorator, much of the configuration you may
|
||||
want to do can be done on the HttpClient used as the "backend" by the HttpClient
|
||||
(this includes setting options like timeouts and connection pool sizes). For
|
||||
caching-specific configuration, you can provide a CacheConfig instance to
|
||||
customize behavior across the following areas:</para>
|
||||
<para>The caching HttpClient inherits all configuration options and parameters
|
||||
of the default non-caching implementation (this includes setting options like
|
||||
timeouts and connection pool sizes). For caching-specific configuration, you can
|
||||
provide a <classname>CacheConfig</classname> instance to customize behavior
|
||||
across the following areas:</para>
|
||||
|
||||
<para><emphasis>Cache size.</emphasis> If the backend storage supports these limits,
|
||||
you can specify the maximum number of cache entries as well as the maximum cacheable
|
||||
|
@ -217,7 +227,7 @@ case VALIDATED:
|
|||
<section id="storage">
|
||||
<title>Storage Backends</title>
|
||||
|
||||
<para>The default implementation of CachingHttpClient stores cache entries and
|
||||
<para>The default implementation of caching HttpClient stores cache entries and
|
||||
cached response bodies in memory in the JVM of your application. While this
|
||||
offers high performance, it may not be appropriate for your application due to
|
||||
the limitation on size or because the cache entries are ephemeral and don't
|
||||
|
@ -227,18 +237,17 @@ case VALIDATED:
|
|||
|
||||
<para>If none of those options are suitable for your application, it is
|
||||
possible to provide your own storage backend by implementing the HttpCacheStorage
|
||||
interface and then supplying that to CachingHttpClient at construction time. In
|
||||
interface and then supplying that to caching HttpClient at construction time. In
|
||||
this case, the cache entries will be stored using your scheme but you will get to
|
||||
reuse all of the logic surrounding HTTP/1.1 compliance and cache handling.
|
||||
Generally speaking, it should be possible to create an HttpCacheStorage
|
||||
implementation out of anything that supports a key/value store (similar to the
|
||||
Java Map interface) with the ability to apply atomic updates.</para>
|
||||
|
||||
<para>Finally, because the CachingHttpClient is a decorator for HttpClient,
|
||||
it's entirely possible to set up a multi-tier caching hierarchy; for example,
|
||||
wrapping an in-memory CachingHttpClient around one that stores cache entries on
|
||||
disk or remotely in memcached, following a pattern similar to virtual memory,
|
||||
L1/L2 processor caches, etc.
|
||||
<para>Finally, with some extra efforts it's entirely possible to set up
|
||||
a multi-tier caching hierarchy; for example, wrapping an in-memory caching
|
||||
HttpClient around one that stores cache entries on disk or remotely in memcached,
|
||||
following a pattern similar to virtual memory, L1/L2 processor caches, etc.
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
||||
|
|
|
@ -415,12 +415,12 @@ sf.connectSocket(timeout, socket, target, remoteAddress, null, clientContext);
|
|||
<programlisting><![CDATA[
|
||||
ConnectionSocketFactory plainsf = <...>
|
||||
LayeredConnectionSocketFactory sslsf = <...>
|
||||
Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory>create()
|
||||
Registry<ConnectionSocketFactory> r = RegistryBuilder.<ConnectionSocketFactory>create()
|
||||
.register("http", plainsf)
|
||||
.register("https", sslsf)
|
||||
.build();
|
||||
|
||||
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);
|
||||
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(r);
|
||||
HttpClients.custom()
|
||||
.setConnectionManager(cm)
|
||||
.build();
|
||||
|
|
|
@ -108,7 +108,8 @@ Document result = Request.Get("http://somehost/content")
|
|||
DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
|
||||
ContentType contentType = ContentType.getOrDefault(entity);
|
||||
if (!contentType.equals(ContentType.APPLICATION_XML)) {
|
||||
throw new ClientProtocolException("Unexpected content type:" + contentType);
|
||||
throw new ClientProtocolException("Unexpected content type:" +
|
||||
contentType);
|
||||
}
|
||||
String charset = contentType.getCharset();
|
||||
if (charset == null) {
|
||||
|
@ -123,45 +124,6 @@ Document result = Request.Get("http://somehost/content")
|
|||
}
|
||||
|
||||
});
|
||||
]]>
|
||||
</programlisting>
|
||||
</section>
|
||||
<section>
|
||||
<title>Asynchronous execution</title>
|
||||
<para>The fluent facade API can be used to execute multiple requests asynchronously using
|
||||
background threads.
|
||||
</para>
|
||||
<programlisting><![CDATA[
|
||||
ExecutorService threadpool = Executors.newFixedThreadPool(2);
|
||||
Async async = Async.newInstance().use(threadpool);
|
||||
|
||||
Request[] requests = new Request[] {
|
||||
Request.Get("http://www.google.com/"),
|
||||
Request.Get("http://www.yahoo.com/"),
|
||||
Request.Get("http://www.apache.com/"),
|
||||
Request.Get("http://www.apple.com/")
|
||||
};
|
||||
|
||||
Queue<Future<Content>> queue = new LinkedList<Future<Content>>();
|
||||
for (final Request request: requests) {
|
||||
Future<Content> future = async.execute(request, new FutureCallback<Content>() {
|
||||
|
||||
public void failed(final Exception ex) {
|
||||
System.out.println(ex.getMessage() + ": " + request);
|
||||
}
|
||||
|
||||
public void completed(final Content content) {
|
||||
System.out.println("Request completed: " + request);
|
||||
}
|
||||
|
||||
public void cancelled() {
|
||||
}
|
||||
|
||||
});
|
||||
queue.add(future);
|
||||
}
|
||||
|
||||
// Process the queue
|
||||
]]>
|
||||
</programlisting>
|
||||
</section>
|
||||
|
|
|
@ -38,7 +38,7 @@ CloseableHttpClient httpclient = HttpClients.createDefault();
|
|||
HttpGet httpget = new HttpGet("http://localhost/");
|
||||
CloseableHttpResponse response = httpclient.execute(httpget);
|
||||
try {
|
||||
|
||||
<...>
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
@ -283,7 +283,8 @@ important message
|
|||
<para> In order to ensure proper release of system resources one must close either
|
||||
the content stream associated with the entity or the response itself</para>
|
||||
<programlisting><![CDATA[
|
||||
CloseableHttpClient httpclient = <...>
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
HttpGet httpget = new HttpGet("http://localhost/");
|
||||
CloseableHttpResponse response = httpclient.execute(httpget);
|
||||
try {
|
||||
HttpEntity entity = response.getEntity();
|
||||
|
@ -318,7 +319,8 @@ try {
|
|||
remaining content and making the connection reusable is too high, in which case
|
||||
one can terminate the content stream by closing the response.</para>
|
||||
<programlisting><![CDATA[
|
||||
CloseableHttpClient httpclient = <...>
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
HttpGet httpget = new HttpGet("http://localhost/");
|
||||
CloseableHttpResponse response = httpclient.execute(httpget);
|
||||
try {
|
||||
HttpEntity entity = response.getEntity();
|
||||
|
@ -348,7 +350,8 @@ try {
|
|||
strongly discouraged unless the response entities originate from a trusted HTTP
|
||||
server and are known to be of limited length.</para>
|
||||
<programlisting><![CDATA[
|
||||
CloseableHttpClient httpclient = <...>
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
HttpGet httpget = new HttpGet("http://localhost/");
|
||||
CloseableHttpResponse response = httpclient.execute(httpget);
|
||||
try {
|
||||
HttpEntity entity = response.getEntity();
|
||||
|
@ -452,8 +455,8 @@ httppost.setEntity(entity);
|
|||
take care of ensuring release of the connection back to the connection manager
|
||||
regardless whether the request execution succeeds or causes an exception.</para>
|
||||
<programlisting><![CDATA[
|
||||
HttpClient httpclient = <...>
|
||||
HttpRequest request = <...>
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
HttpGet httpget = new HttpGet("http://localhost/json");
|
||||
|
||||
ResponseHandler<MyJsonObject> rh = new ResponseHandler<MyJsonObject>() {
|
||||
|
||||
|
@ -477,7 +480,64 @@ ResponseHandler<MyJsonObject> rh = new ResponseHandler<MyJsonObject>() {
|
|||
return gson.fromJson(reader, MyJsonObject.class);
|
||||
}
|
||||
};
|
||||
MyJsonObject myjson = client.execute(request, rh);
|
||||
MyJsonObject myjson = client.execute(httpget, rh);
|
||||
]]></programlisting>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<title>HttpClient interface</title>
|
||||
<para><interfacename>HttpClient</interfacename> interface represents the most essential
|
||||
contract for HTTP request execution. It imposes no restrictions or particular details on
|
||||
the request execution process and leaves the specifics of connection management, state
|
||||
management, authentication and redirect handling up to individual implementations. This
|
||||
should make it easier to decorate the interface with additional functionality such as
|
||||
response content caching.</para>
|
||||
<para>Generally <interfacename>HttpClient</interfacename> implementations act as a facade
|
||||
to a number of special purpose handler or strategy interface implementations
|
||||
responsible for handling of a particular aspect of the HTTP protocol such as redirect
|
||||
or authentication handling or making decision about connection persistence and keep
|
||||
alive duration. This enables the users to selectively replace default implementation
|
||||
of those aspects with custom, application specific ones.</para>
|
||||
<programlisting><![CDATA[
|
||||
ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() {
|
||||
|
||||
@Override
|
||||
public long getKeepAliveDuration(
|
||||
HttpResponse response,
|
||||
HttpContext context) {
|
||||
long keepAlive = super.getKeepAliveDuration(response, context);
|
||||
if (keepAlive == -1) {
|
||||
// Keep connections alive 5 seconds if a keep-alive value
|
||||
// has not be explicitly set by the server
|
||||
keepAlive = 5000;
|
||||
}
|
||||
return keepAlive;
|
||||
}
|
||||
|
||||
};
|
||||
CloseableHttpClient httpclient = HttpClients.custom()
|
||||
.setKeepAliveStrategy(keepAliveStrat)
|
||||
.build();
|
||||
]]></programlisting>
|
||||
<section>
|
||||
<title>HttpClient thread safety</title>
|
||||
<para><interfacename>HttpClient</interfacename> implementations are expected to be
|
||||
thread safe. It is recommended that the same instance of this class is reused for
|
||||
multiple request executions.</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>HttpClient resource deallocation</title>
|
||||
<para>When an instance <classname>CloseableHttpClient</classname> is no longer needed
|
||||
and is about to go out of scope the connection manager associated with it must
|
||||
be shut down by calling the <methodname>CloseableHttpClient#close()</methodname>
|
||||
method.</para>
|
||||
<programlisting><![CDATA[
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
try {
|
||||
<...>
|
||||
} finally {
|
||||
httpclient.close();
|
||||
}
|
||||
]]></programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
@ -497,6 +557,64 @@ MyJsonObject myjson = client.execute(request, rh);
|
|||
<para><interfacename>HttpContext</interfacename> can contain arbitrary objects and
|
||||
therefore may be unsafe to share between multiple threads. It is recommended that
|
||||
each thread of execution maintains its own context.</para>
|
||||
<para>In the course of HTTP request execution HttpClient adds the following attributes to
|
||||
the execution context:</para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<para><interfacename>HttpConnection</interfacename> instance representing the
|
||||
actual connection to the target server.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<para><classname>HttpHost</classname> instance representing the connection
|
||||
target.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<para><classname>HttpRoute</classname> instance representing the complete
|
||||
connection route</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<para><interfacename>HttpRequest</interfacename> instance representing the
|
||||
actual HTTP request. The final HttpRequest object in the execution context
|
||||
always represents the state of the message <emphasis>exactly</emphasis>
|
||||
as it was sent to the target server. Per default HTTP/1.0 and HTTP/1.1
|
||||
use relative request URIs. However if the request is sent via a proxy
|
||||
in a non-tunneling mode then the URI will be absolute.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<para><interfacename>HttpResponse</interfacename> instance representing the
|
||||
actual HTTP response.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<para><classname>java.lang.Boolean</classname> object representing the flag
|
||||
indicating whether the actual request has been fully transmitted to the
|
||||
connection target.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<para><classname>RequestConfig</classname> object representing the actual
|
||||
request configuation.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<para><classname>URICollection</classname> object representing a collection
|
||||
of all redirect locations received in the process of request
|
||||
execution.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
<para>One can use <classname>HttpClientContext</classname> adaptor class to simplify
|
||||
interractions with the context state.</para>
|
||||
<programlisting><![CDATA[
|
||||
|
@ -505,6 +623,7 @@ HttpClientContext clientContext = HttpClientContext.adapt(context);
|
|||
HttpHost target = clientContext.getTargetHost();
|
||||
HttpRequest request = clientContext.getRequest();
|
||||
HttpResponse response = clientContext.getResponse();
|
||||
RequestConfig config = clientContext.getRequestConfig();
|
||||
]]></programlisting>
|
||||
<para>Multiple request sequences that represent a logically related session should be
|
||||
executed with the same <interfacename>HttpContext</interfacename> instance to ensure
|
||||
|
@ -514,8 +633,7 @@ HttpResponse response = clientContext.getResponse();
|
|||
kept in the execution context and get propagatd to the consecutive requests sharing
|
||||
the same context.</para>
|
||||
<programlisting><![CDATA[
|
||||
CloseableHttpClient httpclient = <...>
|
||||
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setSocketTimeout(1000)
|
||||
.setConnectTimeout(1000)
|
||||
|
@ -718,6 +836,46 @@ for (int i = 0; i < 10; i++) {
|
|||
response.close();
|
||||
}
|
||||
}
|
||||
]]></programlisting>
|
||||
</section>
|
||||
<section>
|
||||
<title>Redirect handling</title>
|
||||
<para>HttpClient handles all types of redirects automatically, except those explicitly
|
||||
prohibited by the HTTP specification as requiring user intervention. <literal>See
|
||||
Other</literal> (status code 303) redirects on <literal>POST</literal> and
|
||||
<literal>PUT</literal> requests are converted to <literal>GET</literal> requests as
|
||||
required by the HTTP specification. One can use a custom redirect strategy to relaxe
|
||||
restrictions on automatic redirection of POST methods imposed by the HTTP
|
||||
specification.</para>
|
||||
<programlisting><![CDATA[
|
||||
LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
|
||||
CloseableHttpClient httpclient = HttpClients.custom()
|
||||
.setRedirectStrategy(redirectStrategy)
|
||||
.build();
|
||||
]]></programlisting>
|
||||
<para>HttpClient often has to rewrite the request message in the process of its execution.
|
||||
Per default HTTP/1.0 and HTTP/1.1 generally use relative request URIs. Likewise,
|
||||
original request may get redirected from location to another multiple times. The final
|
||||
interpreted absolute HTTP location can be built using the original request and
|
||||
the context. The utility method <classname>URIUtils#resolve</classname> can be used
|
||||
to build the interpreted absolute URI used to generate the final request. This method
|
||||
includes the last fragment identifier from the redirect requests or the original
|
||||
request.</para>
|
||||
<programlisting><![CDATA[
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
HttpGet httpget = new HttpGet("http://localhost:8080/");
|
||||
CloseableHttpResponse response = httpclient.execute(httpget, context);
|
||||
try {
|
||||
HttpHost target = context.getTargetHost();
|
||||
URICollection redirectLocations = context.getRedirectLocations();
|
||||
URI location = URIUtils.resolve(httpget.getURI(), target,
|
||||
redirectLocations != null ? redirectLocations.getAll() : null);
|
||||
System.out.println("Final HTTP location: " + location.toASCIIString());
|
||||
// Expected to be an absolute URI
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
]]></programlisting>
|
||||
</section>
|
||||
</chapter>
|
||||
|
|
|
@ -1,267 +0,0 @@
|
|||
<?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="httpagent">
|
||||
<title>HTTP client service</title>
|
||||
<section>
|
||||
<title>HttpClient facade</title>
|
||||
<para><interfacename>HttpClient</interfacename> interface represents the most essential
|
||||
contract for HTTP request execution. It imposes no restrictions or particular details on
|
||||
the request execution process and leaves the specifics of connection management, state
|
||||
management, authentication and redirect handling up to individual implementations. This
|
||||
should make it easier to decorate the interface with additional functionality such as
|
||||
response content caching.</para>
|
||||
<para><classname>DefaultHttpClient</classname> is the default implementation of the
|
||||
<interfacename>HttpClient</interfacename> interface. This class acts as a facade to
|
||||
a number of special purpose handler or strategy interface implementations responsible
|
||||
for handling of a particular aspect of the HTTP protocol such as redirect or
|
||||
authentication handling or making decision about connection persistence and keep alive
|
||||
duration. This enables the users to selectively replace default implementation of those
|
||||
aspects with custom, application specific ones.</para>
|
||||
<programlisting><![CDATA[
|
||||
DefaultHttpClient httpclient = new DefaultHttpClient();
|
||||
|
||||
httpclient.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy() {
|
||||
|
||||
@Override
|
||||
public long getKeepAliveDuration(
|
||||
HttpResponse response,
|
||||
HttpContext context) {
|
||||
long keepAlive = super.getKeepAliveDuration(response, context);
|
||||
if (keepAlive == -1) {
|
||||
// Keep connections alive 5 seconds if a keep-alive value
|
||||
// has not be explicitly set by the server
|
||||
keepAlive = 5000;
|
||||
}
|
||||
return keepAlive;
|
||||
}
|
||||
|
||||
});
|
||||
]]></programlisting>
|
||||
<para><classname>DefaultHttpClient</classname> also maintains a list of protocol
|
||||
interceptors intended for processing outgoing requests and incoming responses and
|
||||
provides methods for managing those interceptors. New protocol interceptors can be
|
||||
introduced to the protocol processor chain or removed from it if needed. Internally
|
||||
protocol interceptors are stored in a simple <classname>java.util.ArrayList</classname>.
|
||||
They are executed in the same natural order as they are added to the list.</para>
|
||||
<programlisting><![CDATA[
|
||||
DefaultHttpClient httpclient = new DefaultHttpClient();
|
||||
httpclient.removeRequestInterceptorByClass(RequestUserAgent.class);
|
||||
httpclient.addRequestInterceptor(new HttpRequestInterceptor() {
|
||||
|
||||
public void process(
|
||||
HttpRequest request, HttpContext context)
|
||||
throws HttpException, IOException {
|
||||
request.setHeader(HTTP.USER_AGENT, "My-own-client");
|
||||
}
|
||||
|
||||
});
|
||||
]]></programlisting>
|
||||
<para><classname>DefaultHttpClient</classname> is thread safe. It is recommended that the
|
||||
same instance of this class is reused for multiple request executions. When an instance
|
||||
of <classname>DefaultHttpClient</classname> is no longer needed and is about to go out
|
||||
of scope the connection manager associated with it must be shut down by calling the
|
||||
<methodname>ClientConnectionManager#shutdown()</methodname> method.</para>
|
||||
<programlisting><![CDATA[
|
||||
HttpClient httpclient = new DefaultHttpClient();
|
||||
// Do something useful
|
||||
httpclient.getConnectionManager().shutdown();
|
||||
]]></programlisting>
|
||||
</section>
|
||||
<section>
|
||||
<title>HttpClient parameters</title>
|
||||
<para>These are parameters that be used to customize the behaviour of the default HttpClient
|
||||
implementation:</para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>ClientPNames.HANDLE_REDIRECTS</constant>='http.protocol.handle-redirects':</title>
|
||||
<para>defines whether redirects should be handled automatically. This parameter
|
||||
expects a value of type <classname>java.lang.Boolean</classname>. If this
|
||||
parameter is not set HttpClient will handle redirects automatically.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>ClientPNames.REJECT_RELATIVE_REDIRECT</constant>='http.protocol.reject-relative-redirect':</title>
|
||||
<para>defines whether relative redirects should be rejected. HTTP specification
|
||||
requires the location value be an absolute URI. This parameter expects a
|
||||
value of type <classname>java.lang.Boolean</classname>. If this parameter is
|
||||
not set relative redirects will be allowed.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>ClientPNames.MAX_REDIRECTS</constant>='http.protocol.max-redirects':</title>
|
||||
<para>defines the maximum number of redirects to be followed. The limit on
|
||||
number of redirects is intended to prevent infinite loops caused by broken
|
||||
server side scripts. This parameter expects a value of type
|
||||
<classname>java.lang.Integer</classname>. If this parameter is not set
|
||||
no more than 100 redirects will be allowed.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>ClientPNames.ALLOW_CIRCULAR_REDIRECTS</constant>='http.protocol.allow-circular-redirects':</title>
|
||||
<para>defines whether circular redirects (redirects to the same location) should
|
||||
be allowed. The HTTP spec is not sufficiently clear whether circular
|
||||
redirects are permitted, therefore optionally they can be enabled. This
|
||||
parameter expects a value of type <classname>java.lang.Boolean</classname>.
|
||||
If this parameter is not set circular redirects will be disallowed.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>ClientPNames.CONNECTION_MANAGER_FACTORY_CLASS_NAME</constant>='http.connection-manager.factory-class-name':</title>
|
||||
<para>defines the class name of the default
|
||||
<interfacename>ClientConnectionManager</interfacename> implementation.
|
||||
This parameter expects a value of type
|
||||
<classname>java.lang.String</classname>. If this parameter is not set
|
||||
<classname>SingleClientConnManager</classname> will be used per
|
||||
default.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>ClientPNames.VIRTUAL_HOST</constant>='http.virtual-host':</title>
|
||||
<para>defines the virtual host settings to be used in the <literal>Host</literal>
|
||||
header instead of the physical host. This parameter expects a value of
|
||||
type <classname>HttpHost</classname>. The HttpHost port does not have to
|
||||
be specified as it will be derived from the target.
|
||||
If this parameter is not set, the name or
|
||||
IP address (and port if required) of the target host will be used.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>ClientPNames.DEFAULT_HEADERS</constant>='http.default-headers':</title>
|
||||
<para>defines the request headers to be sent per default with each request. This
|
||||
parameter expects a value of type
|
||||
<interfacename>java.util.Collection</interfacename> containing
|
||||
<interfacename>Header</interfacename> objects.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<title><constant>ClientPNames.DEFAULT_HOST</constant>='http.default-host':</title>
|
||||
<para>defines the default host. The default value will be used if the target
|
||||
host is not explicitly specified in the request URI (relative URIs). This
|
||||
parameter expects a value of type <classname>HttpHost</classname>.</para>
|
||||
</formalpara>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
<section>
|
||||
<title>Automatic redirect handling</title>
|
||||
<para>HttpClient handles all types of redirects automatically, except those explicitly
|
||||
prohibited by the HTTP specification as requiring user intervention. <literal>See
|
||||
Other</literal> (status code 303) redirects on <literal>POST</literal> and
|
||||
<literal>PUT</literal> requests are converted to <literal>GET</literal> requests as
|
||||
required by the HTTP specification.</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>HTTP client and execution context</title>
|
||||
<para>The <classname>DefaultHttpClient</classname> treats HTTP requests as immutable objects
|
||||
that are never supposed to change in the course of request execution. Instead, it
|
||||
creates a private mutable copy of the original request object, whose properties can be
|
||||
updated depending on the execution context. Therefore the final request properties such
|
||||
as the target host and request URI can be determined by examining the content of the
|
||||
local HTTP context after the request has been executed.</para>
|
||||
<para>The final HttpRequest object in the execution context always represents
|
||||
the state of the message _exactly_ as it was sent to the target server.
|
||||
Per default HTTP/1.0 and HTTP/1.1 use relative request URIs. However if the request
|
||||
is sent via a proxy in a non-tunneling mode then the URI will be absolute.</para>
|
||||
<programlisting><![CDATA[
|
||||
DefaultHttpClient httpclient = new DefaultHttpClient();
|
||||
|
||||
HttpContext localContext = new BasicHttpContext();
|
||||
HttpGet httpget = new HttpGet("http://localhost:8080/");
|
||||
HttpResponse response = httpclient.execute(httpget, localContext);
|
||||
HttpHost target = (HttpHost) localContext.getAttribute(
|
||||
ExecutionContext.HTTP_TARGET_HOST);
|
||||
HttpUriRequest req = (HttpUriRequest) localContext.getAttribute(
|
||||
ExecutionContext.HTTP_REQUEST);
|
||||
|
||||
System.out.println("Target host: " + target);
|
||||
System.out.println("Final request URI: " + req.getURI()); // relative URI (no proxy used)
|
||||
System.out.println("Final request method: " + req.getMethod());
|
||||
]]></programlisting>
|
||||
<para>The final interpreted absolute HTTP location can be built using the original request
|
||||
and the context. The utility method URIUtils.resolve(URI,HttpHost,URICollection)
|
||||
can be used to build the interpreted absolute URI used to generate the final request.
|
||||
This method includes the last fragment identifier from the redirect requests or
|
||||
the original request.</para>
|
||||
<programlisting><![CDATA[
|
||||
DefaultHttpClient httpclient = new DefaultHttpClient();
|
||||
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
HttpGet httpget = new HttpGet("http://localhost:8080/");
|
||||
HttpResponse response = httpclient.execute(httpget, context);
|
||||
HttpHost target = context.getTargetHost();
|
||||
URICollection redirectLocations = context.getRedirectLocations();
|
||||
URI location = URIUtils.resolve(httpget.getURI(), target,
|
||||
redirectLocations != null ? redirectLocations.getAll() : null);
|
||||
System.out.println("Final HTTP location: " + location.toASCIIString()); // absolute URI
|
||||
]]></programlisting>
|
||||
</section>
|
||||
<section>
|
||||
<title>Compressed response content</title>
|
||||
<para>
|
||||
The <classname>ContentEncodingHttpClient</classname> is a simple sub-class of
|
||||
<classname>DefaultHttpClient</classname> which adds support indicating to servers that it will
|
||||
support <literal>gzip</literal> and <literal>deflate</literal> compressed responses. It does
|
||||
this via the existing published APIs of <link linkend="protocol_interceptors">HTTP Protocol
|
||||
Interceptors </link>. Depending on the type of response (text will compress well versus
|
||||
images, which are typically already well-compressed), this can speed up responses due to the
|
||||
smaller amount of network traffic involved, along with saving bandwidth, which can be
|
||||
important in mobile environments. The <classname>RequestAcceptEncoding</classname>
|
||||
and <classname>ResponseContentEncoding</classname> interceptors used as also part of the
|
||||
published API and can be used by other <interfacename>DefaultHttpClient</interfacename>
|
||||
implementations. These provide transparent handling of <literal>gzip</literal> and
|
||||
<literal>deflate</literal> encoding, so it will not be apparent to clients that this
|
||||
processing has happened.
|
||||
</para>
|
||||
<programlisting><![CDATA[
|
||||
ContentEncodingHttpClient httpclient = new ContentEncodingHttpClient();
|
||||
HttpGet httpget = new HttpGet("http://www.yahoo.com/");
|
||||
HttpResponse response = httpclient.execute(httpget);
|
||||
|
||||
Header h = rsp.getFirstHeader("Content-Encoding");
|
||||
if (h != null) {
|
||||
System.out.println("----------------------------------------");
|
||||
System.out.println("Response is " + h.getValue() + " encoded");
|
||||
System.out.println("----------------------------------------");
|
||||
}
|
||||
]]></programlisting>
|
||||
<para>
|
||||
One can also add the <classname>RequestAcceptEncoding</classname> and
|
||||
<classname>ResponseContentEncoding</classname> interceptors to an instance of the
|
||||
<classname>DefaultHttpClient</classname>, if desired.
|
||||
</para>
|
||||
<programlisting><![CDATA[
|
||||
DefaultHttpClient httpclient = new DefaultHttpClient();
|
||||
httpclient.addRequestInterceptor(new RequestAcceptEncoding());
|
||||
httpclient.addResponseInterceptor(new ResponseContentEncoding());
|
||||
]]></programlisting>
|
||||
</section>
|
||||
</chapter>
|
|
@ -32,10 +32,14 @@
|
|||
<firstname>Oleg</firstname>
|
||||
<surname>Kalnichevski</surname>
|
||||
</author>
|
||||
<author>
|
||||
<firstname>Jonathan</firstname>
|
||||
<surname>Moore</surname>
|
||||
</author>
|
||||
</authorgroup>
|
||||
|
||||
<legalnotice>
|
||||
<para>
|
||||
<formalpara>
|
||||
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
|
||||
|
@ -43,18 +47,18 @@
|
|||
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
|
||||
</para>
|
||||
<para>
|
||||
</formalpara>
|
||||
<formalpara>
|
||||
<ulink url="http://www.apache.org/licenses/LICENSE-2.0"/>
|
||||
</para>
|
||||
<para>
|
||||
</formalpara>
|
||||
<formalpara>
|
||||
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.
|
||||
</para>
|
||||
</formalpara>
|
||||
</legalnotice>
|
||||
</bookinfo>
|
||||
|
||||
|
@ -65,7 +69,6 @@
|
|||
<xi:include href="connmgmt.xml"/>
|
||||
<xi:include href="statemgmt.xml"/>
|
||||
<xi:include href="authentication.xml"/>
|
||||
<xi:include href="httpagent.xml"/>
|
||||
<xi:include href="fluent.xml"/>
|
||||
<xi:include href="caching.xml"/>
|
||||
<xi:include href="advanced.xml"/>
|
||||
|
|
|
@ -205,9 +205,11 @@ CookieSpecProvider easySpecProvider = new CookieSpecProvider() {
|
|||
}
|
||||
|
||||
};
|
||||
Registry<CookieSpecProvider> cookieSpecReg = RegistryBuilder.<CookieSpecProvider>create()
|
||||
.register(CookieSpecs.BEST_MATCH, new BestMatchSpecFactory())
|
||||
.register(CookieSpecs.BROWSER_COMPATIBILITY, new BrowserCompatSpecFactory())
|
||||
Registry<CookieSpecProvider> r = RegistryBuilder.<CookieSpecProvider>create()
|
||||
.register(CookieSpecs.BEST_MATCH,
|
||||
new BestMatchSpecFactory())
|
||||
.register(CookieSpecs.BROWSER_COMPATIBILITY,
|
||||
new BrowserCompatSpecFactory())
|
||||
.register("easy", easySpecProvider)
|
||||
.build();
|
||||
|
||||
|
@ -216,7 +218,7 @@ RequestConfig requestConfig = RequestConfig.custom()
|
|||
.build();
|
||||
|
||||
CloseableHttpClient httpclient = HttpClients.custom()
|
||||
.setDefaultCookieSpecRegistry(cookieSpecReg)
|
||||
.setDefaultCookieSpecRegistry(r)
|
||||
.setDefaultRequestConfig(requestConfig)
|
||||
.build();
|
||||
]]></programlisting>
|
||||
|
@ -253,7 +255,7 @@ CloseableHttpClient httpclient = HttpClients.custom()
|
|||
<itemizedlist>
|
||||
<listitem>
|
||||
<formalpara>
|
||||
<para><classname>CookieSpecRegistry</classname> instance representing the actual
|
||||
<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>
|
||||
|
@ -287,13 +289,13 @@ CloseableHttpClient httpclient = HttpClients.custom()
|
|||
<programlisting><![CDATA[
|
||||
CloseableHttpClient httpclient = <...>
|
||||
|
||||
Registry<CookieSpecProvider> cookieSpecReg = <...>
|
||||
Lookup<CookieSpecProvider> cookieSpecReg = <...>
|
||||
CookieStore cookieStore = <...>
|
||||
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setCookieSpecRegistry(cookieSpecReg);
|
||||
context.setCookieStore(cookieStore);
|
||||
HttpGet httpget = new HttpGet("http://localhost/1");
|
||||
HttpGet httpget = new HttpGet("http://somehost/");
|
||||
CloseableHttpResponse response1 = httpclient.execute(httpget, context);
|
||||
<...>
|
||||
// Cookie origin details
|
||||
|
|
Loading…
Reference in New Issue