Hello HttpClient Users List, I have spent the last couple days upgrading
a dozen applications from HttpClient 3.1 to 4.0.

First off, I must say that I'm very pleased that
MultiThreadedHttpConnectionManager (now ThreadSafeClientConnManager) is
using synchronization rather than thread interrupts.  Bugs like
HTTPCLIENT-633 and HTTPCLIENT-731 have been a problem with one
application in particular for a long time.

Also X509HostnameVerifier and MultihomePlainSocketFactory look
interesting and should help with projects in the future.

The rest of this email is a few critiques and wishlist requests,
experiences while migrating, maybe helpful information for other people.
Also I may be incorrect in some cases and would like feedback before
going live with the new versions of these applications.


• Releasing connections has increased the amount of code (finally block
logic) that client applications need:

  HttpClient 3.x:

    HttpMethod method = null;
    try {
      …
    }
    finally {
      if (method != null) {
        try {
          method.releaseConnection()
        }
        catch (Exception e) {
          log.warn("Failed to release connection: "
                   + e.getMessage(), e);
        }
      }
    }

  HttpClient 4.0:

    HttpUriRequest request = null;
    HttpResponse response = null;
    try {
      …
    }
    finally {
      boolean released = false;

      // Try consumeContent first, so that connection may be
      // kept-alive and reused.  Note there is no response entity
      // for HTTP codes like 304
      if (response != null) {
        try {
          HttpEntity entity = response.getEntity();
          if (entity != null) {
            entity.consumeContent();
            released = true;
          }
        }
        catch (Exception e) {
          log.warn("Failed to consume HttpEntity: "
                   + e.getMessage(), e);
        }
      }

      // Otherwise abort the connection, seems allow the
      // connection to be kept-alive and reused in some cases
      // (like when there was no response entity), this is a good
      // thing even if “abort” sounds like it's going to close.
      if (!released && request != null) {
        try {
          request.abort();
        }
        catch (Exception e) {
          log.warn("Failed to abort HttpUriRequest: "
                   + e.getMessage(), e);
        }
      }
    }
  }


• In addition to this complex finally-block, I have created a class
which extends HttpEntity (CompressedEntity, does gzip Transfer-Encoding
of requests), it's assigned to requests, it needs to release resources,
HttpService.handleRequest does call consumeContent, however I am
concerned with HTTP request executions that don't make it that far
(connection manager timeout for instance), in that case my finally block
has to call request.getEntity().consumeContent().


• HttpException does not extend IOException like 3.x did.  During
migration I tripped on some code that was explicitly catching
IOException from HttpClient and other URL encoding, charset
manipulating, and java.io APIs, while letting other exceptions throw up
the stack, nothing a little cast/re-throw magic couldn't fix.


• Wishlist request to add an HttpResponse.getHeader(String) method that
returns the first header or null.  During the HttpClient 3.x → 4
migration I found a couple dozen of these:

  Before:

    Header lastModifiedHeader =
      method.getResponseHeader("Last-Modified");
    if (lastModifiedHeaders != null) {
      Date headerDate =
        DateUtil.parseDate(lastModifiedHeader.getValue());
      …
    }

  After:

    Header[] lastModifiedHeaders =
      response.getHeaders("Last-Modified");
    if (lastModifiedHeaders != null
        && lastModifiedHeaders.length > 0) {
      Date headerDate =
        DateUtils.parseDate(lastModifiedHeaders[0].getValue());
      …
    }

No big deal, just additional array subscripts.  This code is going to be
blind to length > 1, so may as well have a convenience method that
returns only the first element.


• Wishlist request to add an additional constructor to StringEntity
which takes the MIME-type portion of the Content-Type.

  Without additional constructor:

    StringEntity entity = new StringEntity(xml, "UTF-8");
    entity.setContentType("text/xml; charset=UTF-8");

  With additional constructor:

    StringEntity entity = new StringEntity(xml, "text/xml", "UTF-8");


• Wishlist request for preemptive authentication to be included in the
API, like HttpClient 3.x had.  There is an example
ClientPreemptiveBasicAuthentication.java that uses
HttpRequestInterceptor which I had adapted to my application and it
works fine.


• Multi-part requests in HttpClient 3.x were a little easier to work
with in a few special cases, the Part interface included the ‘name’
field, whereas the ‘name’ field is now a parameter in the
MultipartEntity.addPart(String,BodyContent) call.

  Before:

    Part[] parts = {
      new StringPart("Some-Field", "Some-Value"),
      new FilePart("Some-File", f)
    };

    MultipartRequestEntity entity =
      new MultipartRequestEntity(parts, method.getParams());

  After:

    LinkedHashMap<String,ContentBody> parts =
      new LinkedHashMap<String,ContentBody>();
    parts.put("Some-Field", new StringBody("Some-Value"));
    parts.put("Some-File", new FileBody(f));

    MultipartEntity entity = new MultipartEntity();
    for (Entry<String,ContentBody> part : parts)
      entity.addPart(part.getKey(), part.getValue());

In some respects I like the API better, but I do have some code which
needs to assemble the parts (including names) independent of the HTTP
request execution, so in one application I wrote a Part wrapper:

  for (Part part : parts)
    entity.addPart(part.getName(), part.getContent());


• The HttpParams framework does not support nested introspection.  I
have a utility class which configures HttpClient from a configuration
file, and with HttpClient 3.x I used BeanUtils to copy parameters from
the configuration file, for example the following would work:

  PropertyUtils.setProperty
   (client,
    "params.soTimeout",
    "30000");

  PropertyUtils.setProperty
   (client,
    "httpConnectionManager.params.defaultMaxConnectionsPerHost",
    "2");

  /* These are just example statements, the actual parameter names
     are not hard-coded and instead passed from the Configuration
     API like the following: */

  Configuration subset = config.subset("HttpClient");
  for (String key : subset.getKeys())
    PropertyUtils.setProperty(client, key, subset.getProperty(key));

I see there are bean classes that wrap HttpParams (e.g.
ClientParamBean), so potentially similar magic could be done with code
like:

  // config has:
  // HttpClient.ClientParam.maxRedirects = 10
  // HttpClient.ConnManagerParamBean.MaxTotalConnections = 100

  HttpAbstractParamBean bean = null;
  Configuration subset = null;

  bean = new ClientParamBean(client.getParams());
  subset = config.subset("HttpClient.ClientParam");
  for (String key : subset.getKeys())
    PropertyUtils.setProperty(bean, key, subset.getProperty(key));

  bean = new ConnManagerParamBean(client.getParams());
  subset = config.subset("HttpClient.ConnManagerParamBean");
  for (String key : subset.getKeys())
    PropertyUtils.setProperty(bean, key, subset.getProperty(key));

However there are two notable problems:

  1. Not all parameters have a bean class (CoreConnectionPNames
     with SO_TIMEOUT)

  2. Many parameter types are too complex for ConvertUtils, the
     worst being MAX_CONNECTIONS_PER_ROUTE with ConnPerRouteBean
     type.  However this could be alleviated with registering
     Converters with ConvertUtils, maybe that's going too far for
     the convenience of this utility class!


• The method DateUtils#formatDate(Date) is very handy and I use it in a
lot of applications, nothing wrong here except that the package
“….impl.cookie” throws me off — having me rely on any external API
implementation (“impl”) doesn't feel like a smart thing for me to do,
also I don't use cookies at all, I use this method for handling
Last-Modified and If-Modified-Since kinds of headers.


• FileBody does not allow the filename field in the Content-Disposition
header to be overriden, the filename taken from the File object - I have
software that creates temporary files and needs to assign an implicit
logical filename.


• I see there's already a bug filed for the jcip-annotations.jar compile
time dependency in client applications (HTTPCLIENT-866), even if it
doesn't get fixed I'm not too worried about adding the jar to a dozen
build scripts.


Thanks!

-- 
Gerald Turner  Email: [email protected]  JID: [email protected]
GPG: 0xFA8CD6D5  21D9 B2E8 7FE7 F19E 5F7D  4D0C 3FA0 810F FA8C D6D5

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to