Updated HttpClient tutorial with 4.2 API changes

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1240534 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2012-02-04 16:30:30 +00:00
parent 10f6da691e
commit 2737e5f066
5 changed files with 248 additions and 165 deletions

View File

@ -113,7 +113,7 @@ pwd
</listitem>
<listitem>
<formalpara>
<title>SPNEGO/Kerberos:</title>
<title>SPNEGO:</title>
<para><literal>SPNEGO</literal> (<emphasis>S</emphasis>imple and
<emphasis>P</emphasis>rotected <literal>GSSAPI</literal>
<emphasis>Nego</emphasis>tiation Mechanism) is a <literal>GSSAPI</literal>
@ -124,6 +124,12 @@ pwd
At present HttpClient only supports the Kerberos sub-mechanism. </para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title>Kerberos:</title>
<para>Kerberos authentication implementation. </para>
</formalpara>
</listitem>
</itemizedlist>
</section>
<section>
@ -209,7 +215,13 @@ httpclient.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF, authpref);
<listitem>
<formalpara>
<title>AuthPolicy.SPNEGO:</title>
<para>SPNEGO/Kerberos authentication</para>
<para>SPNEGO authentication</para>
</formalpara>
</listitem>
<listitem>
<formalpara>
<title>AuthPolicy.KERBEROS:</title>
<para>Kerberos authentication</para>
</formalpara>
</listitem>
</itemizedlist>
@ -327,12 +339,12 @@ HttpResponse response = httpclient.execute(httpget, localContext);
AuthState proxyAuthState = (AuthState) localContext.getAttribute(
ClientContext.PROXY_AUTH_STATE);
System.out.println("Proxy auth scope: " + proxyAuthState.getAuthScope());
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);
System.out.println("Target auth scope: " + targetAuthState.getAuthScope());
System.out.println("Target auth state: " + targetAuthState.getState());
System.out.println("Target auth scheme: " + targetAuthState.getAuthScheme());
System.out.println("Target auth credentials: " + targetAuthState.getCredentials());
]]></programlisting>
@ -490,7 +502,7 @@ EntityUtils.consume(entity2);
supports <literal>SPNEGO</literal> authentication more completely.</para>
<para>The Sun JRE provides the supporting classes to do nearly all the Kerberos and
<literal>SPNEGO</literal> token handling. This means that a lot of the setup is
for the GSS classes. The <classname>NegotiateScheme</classname> is a simple class to
for the GSS classes. The <classname>SPNegoScheme</classname> is a simple class to
handle marshalling the tokens and reading and writing the correct headers.</para>
<para>The best way to start is to grab the <literal>KerberosHttpClient.java</literal>
file in examples and try and get it to work. There are a lot of issues that can
@ -583,40 +595,6 @@ Value: 0x01
]]>
</programlisting>
</section>
<section>
<title>Customizing <literal>SPNEGO</literal> authentication scheme</title>
<para>In order to customize <literal>SPNEGO</literal> support a new instance of
the <classname>NegotiateSchemeFactory</classname> class must be created and
registered with the authentication scheme registry of HttpClient. </para>
<programlisting><![CDATA[
DefaultHttpClient httpclient = new DefaultHttpClient();
NegotiateSchemeFactory nsf = new NegotiateSchemeFactory();
httpclient.getAuthSchemes().register(AuthPolicy.SPNEGO, nsf);
]]>
</programlisting>
<para>There are several options that can be used to customize the behaviour of
<classname>NegotiateSchemeFactory</classname>. </para>
<section>
<title>Strip port</title>
<para>Strips the port off service names e.g.
<literal>HTTP/webserver.ad.example.net:8080</literal> ->
<literal>HTTP/webserver.ad.example.net</literal></para>
<para>Found it useful when authenticating against JBoss Negotiation.</para>
</section>
<section>
<title>Custom <literal>SPNEGO</literal> token generator</title>
<para>Use this method to inject a custom
<interfacename>SpnegoTokenGenerator</interfacename> class to do the Kerberos
to <literal>SPNEGO</literal> token wrapping.
The <classname>BouncySpnegoTokenGenerator</classname> implementation is provided
as an unsupported contribution from the contrib package. This requires the
BouncyCastle libraries <ulink url="http://www.bouncycastle.org/java.html"
>"http://www.bouncycastle.org/java.html"</ulink>. Found especially useful
when using Java 1.5, which is known to provide only a limited support for
<literal>SPNEGO</literal> authentication.
</para>
</section>
</section>
</section>
</chapter>

View File

@ -270,37 +270,14 @@ sf.connectSocket(socket, address, null, params);
<interfacename>javax.net.ssl.SSLContext</interfacename> as a parameter and use
it to create custom configured SSL connections.</para>
<programlisting><![CDATA[
TrustManager easyTrustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(
X509Certificate[] chain,
String authType) throws CertificateException {
// Oh, I am easy!
}
@Override
public void checkServerTrusted(
X509Certificate[] chain,
String authType) throws CertificateException {
// Oh, I am easy!
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
HttpParams params = new BasicHttpParams();
SSLContext sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(null, new TrustManager[] { easyTrustManager }, null);
sslcontext.init(null, null, null);
SSLSocketFactory sf = new SSLSocketFactory(sslcontext);
SSLSocket socket = (SSLSocket) sf.createSocket();
SSLSocket socket = (SSLSocket) sf.createSocket(params);
socket.setEnabledCipherSuites(new String[] { "SSL_RSA_WITH_RC4_128_MD5" });
HttpParams params = new BasicHttpParams();
params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000L);
InetSocketAddress address = new InetSocketAddress("locahost", 443);
sf.connectSocket(socket, address, null, params);
@ -485,7 +462,7 @@ httpclient.setRoutePlanner(new HttpRoutePlanner() {
Scheme http = new Scheme("http", 80, PlainSocketFactory.getSocketFactory());
SchemeRegistry sr = new SchemeRegistry();
sr.register(http);
ClientConnectionManager connMrg = new SingleClientConnManager(sr);
ClientConnectionManager connMrg = new BasicClientConnectionManager(sr);
// Request new connection. This can be a long process
ClientConnectionRequest connRequest = connMrg.requestConnection(
@ -539,27 +516,27 @@ try {
</section>
<section>
<title>Simple connection manager</title>
<para><classname>SingleClientConnManager</classname> is a simple connection manager that
maintains only one connection at a time. Even though this class is thread-safe it
ought to be used by one execution thread only.
<classname>SingleClientConnManager</classname> will make an effort to reuse the
connection for subsequent requests with the same route. It will, however, close the
existing connection and re-open it for the given route, if the route of the persistent
connection does not match that of the connection request. If the connection has been
already been allocated, then
<exceptionname>java.lang.IllegalStateException</exceptionname> is thrown.</para>
<para><classname>SingleClientConnManager</classname> is used by HttpClient per
<para><classname>BasicClientConnectionManager</classname> is a simple connection manager
that maintains only one connection at a time. Even though this class is thread-safe
it ought to be used by one execution thread only.
<classname>BasicClientConnectionManager</classname> will make an effort to reuse
the connection for subsequent requests with the same route. It will, however, close
the existing connection and re-open it for the given route, if the route of the
persistent connection does not match that of the connection request.
If the connection has been already been allocated, then <exceptionname>
java.lang.IllegalStateException</exceptionname> is thrown.</para>
<para><classname>BasicClientConnectionManager</classname> is used by HttpClient per
default.</para>
</section>
<section>
<title>Pooling connection manager</title>
<para><classname>ThreadSafeClientConnManager</classname> is a more complex
<para><classname>PoolingClientConnectionManager</classname> is a more complex
implementation that manages a pool of client connections and is able to service
connection requests from multiple execution threads. Connections are pooled on a per
route basis. A request for a route for which the manager already has a persistent
connection available in the pool will be serviced by leasing a connection from
the pool rather than creating a brand new connection.</para>
<para><classname>ThreadSafeClientConnManager</classname> maintains a maximum limit of
<para><classname>PoolingClientConnectionManager</classname> maintains a maximum limit of
connections on a per route basis and in total. Per default this implementation will
create no more than 2 concurrent connections per given route and no more 20
connections in total. For many real-world applications these limits may prove too
@ -573,14 +550,14 @@ schemeRegistry.register(
schemeRegistry.register(
new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));
ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(schemeRegistry);
PoolingClientConnectionManager cm = new PoolingClientConnectionManager(schemeRegistry);
// Increase max total connection to 200
cm.setMaxTotalConnections(200);
cm.setMaxTotal(200);
// Increase default max connection per route to 20
cm.setDefaultMaxPerRoute(20);
// Increase max connections for localhost:80 to 50
HttpHost localhost = new HttpHost("locahost", 80);
cm.setMaxForRoute(new HttpRoute(localhost), 50);
cm.setMaxPerRoute(new HttpRoute(localhost), 50);
HttpClient httpClient = new DefaultHttpClient(cm);
]]></programlisting>
@ -604,22 +581,23 @@ httpclient.getConnectionManager().shutdown();
</section>
<section>
<title>Multithreaded request execution</title>
<para>When equipped with a pooling connection manager such as ThreadSafeClientConnManager,
HttpClient can be used to execute multiple requests simultaneously using multiple
threads of execution.</para>
<para>The <classname>ThreadSafeClientConnManager</classname> will allocate connections based on
its configuration. If all connections for a given route have already been leased, a
request for a connection will block until a connection is released back to the pool. One
can ensure the connection manager does not block indefinitely in the connection request
operation by setting <literal>'http.conn-manager.timeout'</literal> to a positive value.
If the connection request cannot be serviced within the given time period
<exceptionname>ConnectionPoolTimeoutException</exceptionname> will be thrown.</para>
<para>When equipped with a pooling connection manager such as <classname>
PoolingClientConnectionManager</classname>, HttpClient can be used to execute multiple
requests simultaneously using multiple threads of execution.</para>
<para>The <classname>PoolingClientConnectionManager</classname> will allocate connections
based on its configuration. If all connections for a given route have already been
leased, a request for a connection will block until a connection is released back to
the pool. One can ensure the connection manager does not block indefinitely in the
connection request operation by setting <literal>'http.conn-manager.timeout'</literal>
to a positive value. If the connection request cannot be serviced within the given time
period <exceptionname>ConnectionPoolTimeoutException</exceptionname> will be thrown.
</para>
<programlisting><![CDATA[
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(
new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
ClientConnectionManager cm = new ThreadSafeClientConnManager(schemeRegistry);
ClientConnectionManager cm = new PoolingClientConnectionManager(schemeRegistry);
HttpClient httpClient = new DefaultHttpClient(cm);
// URIs to perform GETs on

169
src/docbkx/fluent.xml Normal file
View File

@ -0,0 +1,169 @@
<?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="fluent">
<title>Fluent API</title>
<section>
<title>Easy to use facade API</title>
<para>
As of version of 4.2 HttpClient comes with an easy to use facade API based on the concept
of a fluent interface. Fluent facade API exposes only the most fundamental functions of
HttpClient and is intended for simple use cases that do not require the full flexibility of
HttpClient. For instance, fluent facade API relieves the users from having to deal with
connection management and resource deallocation.
</para>
<para>Here are several examples of HTTP requests executed through the HC fluent API</para>
<programlisting><![CDATA[
// Execute a GET with timeout settings and return response content as String.
Request.Get("http://somehost/")
.connectTimeout(1000)
.socketTimeout(1000)
.execute().returnContent().asString();
]]>
</programlisting>
<programlisting><![CDATA[
// Execute a POST with the 'expect-continue' handshake, using HTTP/1.1,
// containing a request body as String and return response content as byte array.
Request.Post("http://somehost/do-stuff")
.useExpectContinue()
.version(HttpVersion.HTTP_1_1)
.bodyString("Important stuff", ContentType.DEFAULT_TEXT)
.execute().returnContent().asBytes();
]]>
</programlisting>
<programlisting><![CDATA[
// Execute a POST with a custom header through the proxy containing a request body
// as an HTML form and save the result to the file
Request.Post("http://somehost/some-form")
.addHeader("X-Custom-header", "stuff")
.viaProxy(new HttpHost("myproxy", 8080))
.bodyForm(Form.form().add("username", "vip").add("password", "secret").build())
.execute().saveContent(new File("result.dump"));
]]>
</programlisting>
<para>One can also use <classname>Executor</classname> directly in order to execute requests in
a specific security context whereby authentication details are cached and re-used for
subsequent requests.
</para>
<programlisting><![CDATA[
Executor executor = Executor.newInstance()
.auth(new HttpHost("somehost"), "username", "password")
.auth(new HttpHost("myproxy", 8080), "username", "password")
.authPreemptive(new HttpHost("myproxy", 8080));
executor.execute(Request.Get("http://somehost/"))
.returnContent().asString();
executor.execute(Request.Post("http://somehost/do-stuff")
.useExpectContinue()
.bodyString("Important stuff", ContentType.DEFAULT_TEXT))
.returnContent().asString();
]]>
</programlisting>
<section>
<title>Response handling</title>
<para>The fluent facade API generally relieves the users from having to deal with
connection management and resource deallocation. In most cases, though, this comes at
a price of having to buffer content of response messages in memory. It is highly
recommended to use <interfacename>ResponseHandler</interfacename> for HTTP response
processing in order to avoid having to buffer content in memory.</para>
<programlisting><![CDATA[
Document result = Request.Get("http://somehost/content")
.execute().handleResponse(new ResponseHandler<Document>() {
public Document handleResponse(final HttpResponse response) throws IOException {
StatusLine statusLine = response.getStatusLine();
HttpEntity entity = response.getEntity();
if (statusLine.getStatusCode() >= 300) {
throw new HttpResponseException(
statusLine.getStatusCode(),
statusLine.getReasonPhrase());
}
if (entity == null) {
throw new ClientProtocolException("Response contains no content");
}
DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
ContentType contentType = ContentType.getOrDefault(entity);
if (!contentType.equals(ContentType.APPLICATION_XML)) {
throw new ClientProtocolException("Unexpected content type:" + contentType);
}
String charset = contentType.getCharset();
if (charset == null) {
charset = HTTP.DEFAULT_CONTENT_CHARSET;
}
return docBuilder.parse(entity.getContent(), charset);
} catch (ParserConfigurationException ex) {
throw new IllegalStateException(ex);
} catch (SAXException ex) {
throw new ClientProtocolException("Malformed XML document", ex);
}
}
});
]]>
</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>
</section>
</chapter>

View File

@ -40,9 +40,10 @@ HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
int l;
byte[] tmp = new byte[2048];
while ((l = instream.read(tmp)) != -1) {
try {
// do something useful
} finally {
instream.close();
}
}
]]></programlisting>
@ -65,28 +66,16 @@ if (entity != null) {
HttpGet httpget = new HttpGet(
"http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
]]></programlisting>
<para>HttpClient provides a number of utility methods to simplify creation and
modification of request URIs.</para>
<para>URI can be assembled programmatically:</para>
<para>HttpClient provides <classname>URIBuilder</classname> utility class to simplify
creation and modification of request URIs.</para>
<programlisting><![CDATA[
URI uri = URIUtils.createURI("http", "www.google.com", -1, "/search",
"q=httpclient&btnG=Google+Search&aq=f&oq=", null);
HttpGet httpget = new HttpGet(uri);
System.out.println(httpget.getURI());
]]></programlisting>
<para>stdout &gt;</para>
<programlisting><![CDATA[
http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=
]]></programlisting>
<para>Query string can also be generated from individual parameters:</para>
<programlisting><![CDATA[
List<NameValuePair> qparams = new ArrayList<NameValuePair>();
qparams.add(new BasicNameValuePair("q", "httpclient"));
qparams.add(new BasicNameValuePair("btnG", "Google Search"));
qparams.add(new BasicNameValuePair("aq", "f"));
qparams.add(new BasicNameValuePair("oq", null));
URI uri = URIUtils.createURI("http", "www.google.com", -1, "/search",
URLEncodedUtils.format(qparams, "UTF-8"), null);
URIBuilder builder = new URIBuilder();
builder.setScheme("http").setHost("www.google.com").setPath("/search")
.setParameter("q", "httpclient")
.setParameter("btnG", "Google Search")
.setParameter("aq", "f")
.setParameter("oq", "");
URI uri = builder.build();
HttpGet httpget = new HttpGet(uri);
System.out.println(httpget.getURI());
]]></programlisting>
@ -276,19 +265,16 @@ domain=localhost
supplied by the creator of the entity.</para>
<programlisting><![CDATA[
StringEntity myEntity = new StringEntity("important message",
"UTF-8");
ContentType.create("text/plain", "UTF-8"));
System.out.println(myEntity.getContentType());
System.out.println(myEntity.getContentLength());
System.out.println(EntityUtils.getContentCharSet(myEntity));
System.out.println(EntityUtils.toString(myEntity));
System.out.println(EntityUtils.toByteArray(myEntity).length);
]]></programlisting>
System.out.println(EntityUtils.toByteArray(myEntity).length);]]></programlisting>
<para>stdout &gt;</para>
<programlisting><![CDATA[
Content-Type: text/plain; charset=UTF-8
Content-Type: text/plain; charset=utf-8
17
UTF-8
important message
17
]]></programlisting>
@ -394,7 +380,7 @@ if (entity != null) {
<classname>FileEntity</classname>.</para>
<programlisting><![CDATA[
File file = new File("somefile.txt");
FileEntity entity = new FileEntity(file, "text/plain; charset=\"UTF-8\"");
FileEntity entity = new FileEntity(file, ContentType.create("text/plain", "UTF-8"));
HttpPost httppost = new HttpPost("http://localhost/action.do");
httppost.setEntity(entity);
@ -404,34 +390,6 @@ httppost.setEntity(entity);
implement a custom <interfacename>HttpEntity</interfacename> class which is
self-contained instead of using the generic <classname>InputStreamEntity</classname>.
<classname>FileEntity</classname> can be a good starting point.</para>
<section>
<title>Dynamic content entities</title>
<para>Often HTTP entities need to be generated dynamically based a particular
execution context. HttpClient provides support for dynamic entities by using
the <classname>EntityTemplate</classname> entity class and
<interfacename>ContentProducer</interfacename> interface. Content producers
are objects which produce their content on demand, by writing it out to an
output stream. They are expected to be able produce their content every time
they are requested to do so. So entities created with
<classname>EntityTemplate</classname> are generally self-contained and
repeatable.</para>
<programlisting><![CDATA[
ContentProducer cp = new ContentProducer() {
public void writeTo(OutputStream outstream) throws IOException {
Writer writer = new OutputStreamWriter(outstream, "UTF-8");
writer.write("<response>");
writer.write(" <content>");
writer.write(" important stuff");
writer.write(" </content>");
writer.write("</response>");
writer.flush();
}
};
HttpEntity entity = new EntityTemplate(cp);
HttpPost httppost = new HttpPost("http://localhost/handler.do");
httppost.setEntity(entity);
]]></programlisting>
</section>
<section>
<title>HTML forms</title>
<para>Many applications need to simulate the process of submitting an
@ -659,15 +617,6 @@ Final target: http://www.google.ch
target server (i.e. the request has not been fully transmitted to the
server).</para>
</listitem>
<listitem>
<para>HttpClient will automatically retry those methods that have been fully
transmitted to the server, but the server failed to respond with an HTTP
status code (the server simply drops the connection without sending anything
back). In this case it is assumed that the request has not been processed by
the server and the application state has not changed. If this assumption may
not hold true for the web server your application is targeting it is highly
recommended to provide a custom exception handler.</para>
</listitem>
</itemizedlist>
</section>
<section>
@ -688,12 +637,20 @@ HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
// Do not retry if over max retry count
return false;
}
if (exception instanceof NoHttpResponseException) {
// Retry if the server dropped connection on us
return true;
if (exception instanceof InterruptedIOException) {
// Timeout
return false;
}
if (exception instanceof SSLHandshakeException) {
// Do not retry on SSL handshake exception
if (exception instanceof UnknownHostException) {
// Unknown host
return false;
}
if (exception instanceof ConnectException) {
// Connection refused
return false;
}
if (exception instanceof SSLException) {
// SSL handshake exception
return false;
}
HttpRequest request = (HttpRequest) context.getAttribute(

View File

@ -66,6 +66,7 @@
<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"/>