Introduction

One of the big refactorings in Jetty 9 is the complete rewrite of the HTTP client.

The reasons behind the rewrite are many:

  • We wrote the codebase several years ago; while we have actively maintained, it was starting to show its age.
  • The HTTP client guarded internal data structures from multithreaded access using the synchronized keyword, rather than using non-blocking data structures.
  • We exposed as main concept the HTTP exchange that, while representing correctly what an HTTP request/response cycle is,  did not match user expectations of a request and a response.
  • HTTP client did not have out of the box features such as authentication, redirect and cookie support.
  • Users somehow perceived the Jetty HTTP client as cumbersome to program.

The rewrite takes into account many community inputs, requires JDK 7 to take advantage of the latest programming features, and is forward-looking because the new API is JDK 8 Lambda-ready (that is, you can use Jetty 9’s HTTP client with JDK 7 without Lambda, but if you use it in JDK 8 you can use lambda expressions to specify callbacks; see examples below).

Programming with Jetty 9’s HTTP Client

The main class is named, as in Jetty 7 and Jetty 8, org.eclipse.jetty.client.HttpClient (although it is not backward compatible with the same class in Jetty 7 and Jetty 8).
You can think of an HttpClient instance as a browser instance.
Like a browser, it can make requests to different domains, it manages redirects, cookies and authentications, you can configure it with a proxy, and it provides you with the responses to the requests you make.

You need to configure an HttpClient instance and then start it:

Simple GET requests require just  one line:

Method HttpClient.GET(...) returns a Future<ContentResponse> that you can use to cancel the request or to impose a total timeout for the request/response conversation.

Class ContentResponse represents a response with content; the content is limited by default to 2 MiB, but you can configure it to be larger.

Simple POST requests also require just one line:

Jetty 9’s HttpClient automatically follows redirects, so automatically handles the typical web pattern POST/Redirect/GET, and the response object contains the content of the response of the GET request. Following redirects is a feature that you can enable/disable on a per-request basis or globally.

File uploads also require one line, and make use of JDK 7’s java.nio.file classes:

Asynchronous Programming

So far we have shown how to use HttpClient in a blocking style, that is the thread that issues the request blocks until the request/response conversation is complete. However, to unleash the full power of Jetty 9’s HttpClient you should look at its non-blocking (asynchronous) features.

Jetty 9’s HttpClient fully supports the asynchronous programming style. You can write a simple GET request in this way:

Method send(Response.CompleteListener) returns void and does not block; the Listener provided as a parameter is notified when the request/response conversation is complete, and the Result parameter  allows you to access the response object.

You can write the same code using JDK 8’s lambda expressions:

HttpClient uses Listeners extensively to provide hooks for all possible request and response events, and with JDK 8’s lambda expressions they’re even more fun to use:

This makes Jetty 9’s HttpClient suitable for HTTP load testing because, for example, you can accurately time every step of the request/response conversation (thus knowing where the request/response time is really spent).

Content Handling

Jetty 9’s HTTP client provides a number of utility classes off the shelf to handle request content and response content.

You can provide request content as String, byte[], ByteBuffer, java.nio.file.Path, InputStream, and provide your own implementation of ContentProvider. Here’s an example that provides the request content using an InputStream:

HttpClient can handle Response content in different ways:

The most common is via blocking calls that return a ContentResponse, as shown above.

When using non-blocking calls, you can use a BufferingResponseListener in this way:

To be efficient and avoid copying to a buffer the response content, you can use a Response.ContentListener, or a subclass:

To stream the response content, you can use InputStreamResponseListener in this way:

Cookies Support

HttpClient stores and accesses HTTP cookies through a CookieStore:

You can add cookies that you want to send along with your requests (if they match the domain and path and are not expired), and responses containing cookies automatically populate the cookie store, so that you can query it to find the cookies you are expecting with your responses.

Authentication Support

HttpClient suports HTTP Basic and Digest authentications, and other mechanisms are pluggable.

You can configure authentication credentials in the HTTP client instance as follows:

HttpClient tests authentication credentials against the challenge(s) the server issues, and if they match it automatically sends the right authentication headers to the server for authentication. If the authentication is successful, it caches the result and reuses it for subsequent requests for the same domain and matching URIs.

Proxy Support

You can also configure HttpClient  with a proxy:

Configured in this way, HttpClient makes requests to the proxy (for plain-text HTTP requests) or establishes a tunnel via HTTP CONNECT (for encrypted HTTPS requests).

Conclusions

The new Jetty 9  HTTP client is easier to use, has more features and it’s faster and better than Jetty 7’s or Jetty 8’s.

The Jetty project continues to lead the way when it’s about the Web: years ago with Jetty Continuations, then with Jetty WebSocket, recently with Jetty SPDY and now with the first complete, ready to use, JDK 8’s Lambda -ready HTTP client.

Go get it while it’s hot !

Maven coordinates:

Direct Downloads:
Main jar: jetty-client.jar
Dependencies: jetty-http.jar, jetty-io.jar, jetty-util.jar

The new Jetty 9 HTTP client
Tagged on:                                 

22 thoughts on “The new Jetty 9 HTTP client

  • November 22, 2012 at 8:34 pm
    Permalink

    This is one of the reasons why I love Jetty so much, always leading the way with new features! I can’t wait until I can use the async features with lambda’s. Great job guys!

  • November 23, 2012 at 2:22 pm
    Permalink

    Looks very concise. Did you every try to use your HttpClient implementation in Android?

  • January 28, 2013 at 6:52 pm
    Permalink

    The new Jetty Client does not handle exceptions very well. Each exception is from different family. Sometimes onFailure is not executed 🙁

    Unknown host
    — CUT —
    2013-01-28 19:18:38,117 [pool-1-thread-1] WARN – send() uri=http://localhostx, e=java.nio.channels.UnresolvedAddressException
    — CUT —

    Connection refused:
    — CUT —
    2013-01-28 19:19:33,071 [HttpClient@2078559224-12] INFO – onFailure response=HttpResponse[null 0 null], failure=java.net.ConnectException: Connection refused
    2013-01-28 19:19:33,072 [HttpClient@2078559224-12] INFO – onComplete response.getStatus=0, request.getUri=http://localhost:23
    — CUT —

    If you don’t specify port, invalid port number is put in onComplete() result.getRequest.getUri():
    — CUT —
    2013-01-28 19:34:40,981 [pool-1-thread-1] INFO – send() uri=http://localhost/, duration=76080
    2013-01-28 19:34:40,985 [HttpClient@2078559224-12] INFO – onFailure response=HttpResponse[null 0 null], failure=java.net.ConnectException: Połączenie odrzucone
    2013-01-28 19:34:40,986 [HttpClient@2078559224-12] INFO – onComplete response.getStatus=0, request.getUri=http://localhost:-1/
    — CUT —

    Protocol violation (I try connecting to SSH):
    — CUT —
    2013-01-28 19:24:39,960 [HttpClient@2078559224-13] INFO – onFailure response=HttpResponse[null 400 Unknown Version], failure=org.eclipse.jetty.client.HttpResponseException: HTTP protocol violation: bad response
    2013-01-28 19:24:39,961 [HttpClient@2078559224-13] INFO – onComplete response.getStatus=400, request.getUri=http://localhost:22/
    — CUT —

    But sometimes onFailure isn’t executed:
    — CUT —
    2013-01-28 19:25:55,602 [pool-1-thread-1] INFO – send() uri=http://localhost:22/, duration=69379
    2013-01-28 19:25:55,695 [HttpClient@2078559224-12] INFO – onComplete response.getStatus=0, request.getUri=http://localhost:22/
    — CUT —

    Early EOF:
    — CUT —
    2013-01-28 19:27:53,447 [pool-1-thread-1] INFO – send() uri=http://localhost:9010/, duration=17209
    2013-01-28 19:27:53,502 [HttpClient@2078559224-13] INFO – onFailure response=HttpResponse[null 0 null], failure=java.io.EOFException
    2013-01-28 19:27:53,503 [HttpClient@2078559224-13] INFO – onComplete response.getStatus=0, request.getUri=http://localhost:9010/
    — CUT —

    Timeout:
    — CUT —
    2013-01-28 19:29:22,645 [pool-1-thread-1] INFO – send() uri=http://localhost:80/, duration=17055
    2013-01-28 19:29:37,652 [HttpClient@1208474529-12] INFO – onFailure response=HttpResponse[null 0 null], failure=java.net.SocketTimeoutException
    2013-01-28 19:29:37,654 [HttpClient@1208474529-12] INFO – onComplete response.getStatus=0, request.getUri=http://localhost:80/
    — CUT —

    No A/AAAA:

    — CUT —
    2013-01-28 19:39:50,948 [pool-1-thread-1] WARN – send() uri=http://x3.localhost/, e=java.nio.channels.UnresolvedAddressException, duration=85627
    — CUT —

    If connections gets redirecteded and host could not be resolved, then exception is thrown in client thread pool 🙁

    — CUT —
    2013-01-28 19:48:56,037 [HttpClient@1384238640-12] INFO – Exception while notifying listener org.eclipse.jetty.client.RedirectProtocolHandler@2f6b007f
    java.nio.channels.UnresolvedAddressException
    at sun.nio.ch.Net.checkAddress(Net.java:85)
    at sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:597)
    at org.eclipse.jetty.client.HttpClient.newConnection(HttpClient.java:490)
    at org.eclipse.jetty.client.HttpDestination.newConnection(HttpDestination.java:192)
    at org.eclipse.jetty.client.HttpDestination.acquire(HttpDestination.java:244)
    at org.eclipse.jetty.client.HttpDestination.send(HttpDestination.java:167)
    at org.eclipse.jetty.client.HttpClient.send(HttpClient.java:476)
    at org.eclipse.jetty.client.HttpRequest.send(HttpRequest.java:453)
    at org.eclipse.jetty.client.RedirectProtocolHandler.redirect(RedirectProtocolHandler.java:136)
    at org.eclipse.jetty.client.RedirectProtocolHandler.onComplete(RedirectProtocolHandler.java:76)
    at org.eclipse.jetty.client.ResponseNotifier.notifyComplete(ResponseNotifier.java:199)
    at org.eclipse.jetty.client.ResponseNotifier.notifyComplete(ResponseNotifier.java:191)
    at org.eclipse.jetty.client.HttpReceiver.success(HttpReceiver.java:337)
    at org.eclipse.jetty.client.HttpReceiver.messageComplete(HttpReceiver.java:305)
    at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:1108)
    at org.eclipse.jetty.client.HttpReceiver.parse(HttpReceiver.java:111)
    at org.eclipse.jetty.client.HttpReceiver.receive(HttpReceiver.java:78)
    at org.eclipse.jetty.client.HttpConnection.receive(HttpConnection.java:303)
    at org.eclipse.jetty.client.HttpExchange.receive(HttpExchange.java:104)
    at org.eclipse.jetty.client.HttpConnection.onFillable(HttpConnection.java:291)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.run(AbstractConnection.java:240)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:589)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:520)
    at java.lang.Thread.run(Thread.java:722)
    — CUT —

    It remains to say that the send () uses a blocking DNS client :/

    3/10

    • January 28, 2013 at 10:34 pm
      Permalink

      Michal, thanks for the report.

      #1 is tracked by https://bugs.eclipse.org/bugs/show_bug.cgi?id=399324.

      #2 is expected behavior.

      #3 is tracked by https://bugs.eclipse.org/bugs/show_bug.cgi?id=399319.

      #4 and #5 work fine for me. Do you have additional information or a reproducible test case ?

      #6 is expected behavior.

      #7 is expected behavior.

      #8 is the same bug as #1.

      #9 is the same bug as #1.

      About the use of blocking DNS client, I am not sure what you mean exactly. DNS lookup is performed by the JVM and I am not sure we can control it.
      If you don’t want to wait too much for DNS lookups, you can specify a connect timeout via HttpClient.setConnectTimeout(long).

      Finally, I am not sure I understand your comment about exception handling. Bugs aside, asynchronous processing can only notify applications via callbacks, and does not seem right to have N callbacks for the N possible exceptions that may happen. Hence, there is only one callback for failures, and if you really want to handle exceptions, you have to perform ugly if ... instanceof ... else if ... instanceof ....
      I am open to any suggestion to improve failure handling, if you have ideas.

      Thanks !

  • January 29, 2013 at 10:12 am
    Permalink

    Bugs:
    I will check my milestone version to verify if is actual and create test for onFailure() problem.

    DNS:
    Client is asynchronous at most the time, but DNS resolver isn’t (send will block until get response from DNS). My idea is to resolve DNS asynchronously in client. In my test, I try to send 500 requests to different domains. In this case, send() block horribly.

    Exceptions:
    Maybe it would be good to gather all the exceptions and document the situations in which they occur.

  • February 6, 2013 at 8:56 am
    Permalink

    Hi Simon,
    This is pretty cool, I love the new APIs.
    Is there a way to write entity directly to OutputStream, instead of using built-in content providers?
    -Arul

    • February 7, 2013 at 7:42 am
      Permalink

      Arul, you can use DeferredContentProvider as explained in the documentation.
      We may provide a ContentProvider that uses OutputStream based on DeferredContentProvider, but there would be no relevant difference between the two.
      Cheers

  • February 8, 2013 at 8:03 am
    Permalink

    Thanks for the pointer. It is not exactly what I was looking for. In my case, I would like to delegate the content handling to my JAX-RS Client framework which is quite powerful and it could bind to various content type from the stream. It would be very useful if Jetty Client supported a ContentProvider that writes to OutputStream. Something like this would help :

    public interface StreamingProvider extends ContentProvider {
    void write(OutputStream out) throws IOException;
    }

    • February 8, 2013 at 8:26 am
      Permalink

      Arul, sorry but I don’t understand how you would use this StreamingProvider, can you make an extended example ?

  • March 2, 2013 at 7:28 pm
    Permalink

    I’m using 9.0.0.RC2 and though it’s excellent, have a couple of comments on it:

    a) If you make an HTTP Request and there is an available Connection then the request is processed in the calling thread (HttpDestination line 173) whereas if a Connection needs to be created it’s processed on a new thread (as part of a Promise created at HttpDestination line 226). Is this the intended behavior?

    b) I love the fluid style of the CLient.newRequest method. What’s not really obvious is that if you add listeners for the various events by passing an object that implements more than one of the callback interfaces then the methods of that object are liable to be called more than once, since they’re called irrespective of the event you set the object on. e.g. in the following the events will be triggered twice:

    ...
    .onRequestBegin(listener)
    .onRequestSuccess(listener)
    ...
    public static class Listener implements Response.SuccessListener, Response.BeginListener, ...

    This is presumably intended behavior?

    c) Handling of redirects is nice, but if you’re using the client to measure performance, surely the request and response events should be triggered for each separate request/response pair of the redirection chain? Or would you expect the caller to implement redirects themselves if they are interested in that level of detail? Mind you, if you’re not interested in that level of detail you probably don’t care about most of the various events anyway.

    d) I know this is picky, but I found it a bit confusing to start with: the name of the HttpField class accurately tracks standard usage (though perhaps should be HttpHeaderField), but the HttpHeader class should surely be HttpHeaderFieldName (or HttpFieldName). While I’m being picky, why does Client.setUserAgentField ask for a HttpField (and throw an exception if it’s the wrong one) and not just look for a String for the field value? And also for completeness, the request builder .header(String, String) method might more usefully take (HttpFieldName, String) or (HttpField), and for consistency might be called headerField()!

    Thanks very much for an excellent implementation and thanks also for listening to these comments.

    • March 4, 2013 at 3:42 pm
      Permalink

      a) Yes.

      b) If you register the same object for different events, then that object will be invoked for the events it is registered for. If you mean that one event (e.g. the begin event) is notified twice, then that’s not intended.

      c) If you are interested in knowing redirect performance, you can disable automatic redirect following, either at HttpClient level or at a request level.

      d) We reuse these classes on server. Perhaps you’re right about naming, but once you know them they’re short and simple.
      The user-agent field is an optimization: if you set it, it’s set once and not reconstructed for every request like we would have if we had only the value.
      About the overloaded methods, usually applications have strings and not instances of HttpField. But you convinced me on an overloaded version with HttpHeader.

      • March 4, 2013 at 10:09 pm
        Permalink

        Thanks for your reply.

        a) it seems a little counter-intuitive that a non-blocking client waits till it has sent the request before it becomes non-blocking … i.e. it’s non-blocking on the response, and also on the request but only if there’s no available connection already open.

        b) Yes. An object implementing more than one interface gets called duplicate times for the same event if it is registered for more than one event.

        c) Thanks. It doesn’t seem like it’s possible to find out if there has been one or more redirects, other than by comparing the requested address with the result address.

        Also, It looks like a redirected destination doesn’t work like a non-redirected destination. For example, if I request http://google.com from here I get redirected to http://www.google.co.uk. If I make requests to both of these in a loop, my direct request (to http://www.google.co.uk) ends up being twice as fast even though the google.com redirect is a “Moved Permanently”.

        As noted in a) above, once I’ve made the first request pair the dispatch of the second request of each pair blocks till the first is committed.

        d) Great! Could I possibly convince you also to support a headers(Headers) method, since all my requests always have all the same headers?

        thanks again for your reply.

        • March 4, 2013 at 10:42 pm
          Permalink

          a) I don’t understand. There is no blocking code in HttpClient.
          b) Will double check this.
          c) We could have a cache of 301 URLs, but it’s not implemented. I don’t understand the comment on the loops nor the one on the fact that a second request would be blocked by a first ? There is no blocking code.
          d) Like b) and c) you have to file a request at https://bugs.eclipse.org/bugs/enter_bug.cgi?product=Jetty.

          • March 5, 2013 at 7:18 am
            Permalink

            I didn’t explain myself too well. My application takes a set of URLs and loops over them to time responses.

            The first iteration of calls does not block, the second and subsequent iterations block because the destinations have been established and the call does not return till the request has been committed.

            Illustrated in the following using the Google examples above.


            07:11:59.148 [Thread-9] INFO org.eclipse.jetty.client.HttpClient - Started org.eclipse.jetty.client.HttpClient@14db5866
            07:11:59.148 [Thread-9] INFO com.example.Requester - Starting despatch of iteration 0
            07:11:59.152 [Thread-9] INFO com.example.Requester - Finished despatch of iteration 0 in 1.138 millis
            07:11:59.194 [HttpClient@349919334-23] INFO com.example.Requester - http://google.com, Conversation 2 Request Begin: Increment 45.092 Cumulative 45.099 millis
            07:11:59.194 [HttpClient@349919334-23] INFO com.example.Requester - http://google.com, Conversation 2 Request Commit: Increment 0.44 Cumulative 45.533 millis
            07:11:59.199 [HttpClient@349919334-24] INFO com.example.Requester - http://www.google.co.uk:80/, Conversation 3 Request Begin: Increment 49.986 Cumulative 49.987 millis
            07:11:59.199 [HttpClient@349919334-24] INFO com.example.Requester - http://www.google.co.uk:80/, Conversation 3 Request Commit: Increment 0.339 Cumulative 50.326 millis
            07:11:59.236 [HttpClient@349919334-22] INFO com.example.Requester - http://www.google.co.uk:80/, Conversation 3 Response Begin: Increment 36.963 Cumulative 87.29 millis
            07:11:59.324 [HttpClient@349919334-21] INFO com.example.Requester - http://www.google.co.uk:80/, Conversation 3 Response Complete (OK): Increment 87.449 Total 174.739 millis
            07:11:59.436 [HttpClient@349919334-23] INFO com.example.Requester - http://google.com, Conversation 2 Response Begin: Increment 241.725 Cumulative 287.26 millis
            07:11:59.490 [HttpClient@349919334-22] INFO com.example.Requester - http://www.google.co.uk:80/, Conversation 2 Response Complete (OK): Increment 53.909 Total 341.168 millis
            07:12:09.158 [Thread-9] INFO com.example.Requester - Starting despatch of iteration 1
            07:12:09.158 [Thread-9] INFO com.example.Requester - http://google.com, Conversation 4 Request Begin: Increment 0.389 Cumulative 0.391 millis
            07:12:09.159 [Thread-9] INFO com.example.Requester - http://google.com, Conversation 4 Request Commit: Increment 0.331 Cumulative 0.721 millis
            07:12:09.159 [Thread-9] INFO com.example.Requester - http://www.google.co.uk:80/, Conversation 5 Request Begin: Increment 0.319 Cumulative 0.32 millis
            07:12:09.159 [Thread-9] INFO com.example.Requester - http://www.google.co.uk:80/, Conversation 5 Request Commit: Increment 0.298 Cumulative 0.618 millis
            07:12:09.160 [Thread-9] INFO com.example.Requester - Finished despatch of iteration 1 in 1.819 millis
            07:12:09.211 [HttpClient@349919334-24] INFO com.example.Requester - http://www.google.co.uk:80/, Conversation 5 Response Begin: Increment 51.102 Cumulative 51.72 millis
            07:12:09.358 [HttpClient@349919334-25] INFO com.example.Requester - http://www.google.co.uk:80/, Conversation 5 Response Complete (OK): Increment 147.395 Total 199.116 millis
            07:12:09.359 [HttpClient@349919334-26] INFO com.example.Requester - http://google.com, Conversation 4 Response Begin: Increment 200.579 Cumulative 201.3 millis
            07:12:09.397 [HttpClient@349919334-26] INFO com.example.Requester - http://www.google.co.uk:80/, Conversation 4 Response Complete (OK): Increment 37.491 Total 238.791 millis

            Thanks again for your reply on this – I will file as you request.

  • March 14, 2013 at 7:16 am
    Permalink

    Hi,
    Thanks for the information, it was very helpful. In my application I am using Apache HTTP Client 4.x and now I am thinking to replace it with Jetty 9 HTTP Client because of the out of box listeners and easier to use. Although, I would like to know that whether there is any cache manager or any cache support in Jetty 9.0 HTTP Client ?. In my application I need to use a Cache Manager and Apache HTTP Client do provide a way to implement your own Caching.

    Hope to get your reply, thanks in advance.

    best
    Prashant Singh

      • March 14, 2013 at 10:24 am
        Permalink

        Any ideas if I wanna implement it by my own way ? .. I would drop request anyways but would like to contribute to the community as well.

  • March 25, 2013 at 3:21 pm
    Permalink

    Hi,
    How can I get the following times from the listeners:
    1) Blocking time (in case request is waiting to get idle connection)
    2) DNS Lookup time
    3) Connecting to Remote Host Time

    Actually I am trying to make a chart as like Firebug Net Panel to analyze the Response Time Breakdown. I saw there are many listeners but I didnt find the proper explanations for each listeners. Although most of them are pretty clear with the name but still I am having confusion to get the above mentioned time.

    I would be thankful if you can explain me a little.

    Best
    Prashant

    • March 25, 2013 at 4:03 pm
      Permalink

      1) Blocking time is request begin event minus request queued event (it may include DNS and connect times).
      2) Not currently exposed, although easy to expose.
      3) Same as 2).

      Note that DNS time and connect time are not related to a request.
      For example, a request may trigger a DNS lookup, but a subsequent request may be sent on an existing connection, so it does not perform DNS lookup nor connection attempt.

      Please file feature requests for 2) and 3) at https://bugs.eclipse.org/bugs/enter_bug.cgi?product=Jetty explaining in details your use case.

Comments are closed.