This blog is an update for jetty-9 of one published for Jetty 7 in 2008 as an example web application  that uses Jetty asynchronous HTTP client and the asynchronoous servlets 3.0 API, to call an eBay restful web service. The technique combines the Jetty asynchronous HTTP client with the Jetty servers ability to suspend servlet processing, so that threads are not held while waiting for rest responses. Thus threads can handle many more requests and web applications using this technique should obtain at least ten fold increases in performance.

Screenshot from 2013-04-19 09:15:19

The screen shot above shows four iframes calling either a synchronous or the asynchronous demonstration servlet, with the following results:

Synchronous Call, Single Keyword
A request to lookup ebay auctions with the keyword “kayak” is handled by the synchronous implementation. The call takes 261ms and the servlet thread is blocked for the entire time. A server with a 100 threads in a pool would be able to handle 383 requests per second.
Asynchronous Call, Single Keyword
A request to lookup ebay auctions with the keyword “kayak” is handled by the asynchronous implementation. The call takes 254ms, but the servlet request is suspended so the request thread is held for only 5ms. A server with a 100 threads in a pool would be able to handle 20,000 requests per second (if not constrained by other limitations)
Synchronous Call, Three Keywords
A request to lookup ebay auctions with keywords “mouse”, “beer” and “gnome” is handled by the synchronous implementation. Three calls are made to ebay in series, each taking approx 306ms, with a total time of 917ms and the servlet thread is blocked for the entire time. A server with a 100 threads in a pool would be able to handle only 109 requests per second!
Asynchronous Call, Three Keywords
A request to lookup ebay auctions with keywords “mouse”, “beer” and “gnome” is handled by the asynchronous implementation. The three calls can be made to ebay in parallel, each taking approx 300ms, with a total time of 453ms and the servlet request is suspended, so the request thread is held for only 7ms. A server with a 100 threads in a pool would be able to handle 14,000 requests per second (if not constrained by other limitations).

It can be seen by these results that asynchronous handling of restful requests can dramatically improve both the page load time and the capacity by avoiding thread starvation.
The code for the example asynchronous servlet is available from jetty-9 examples and works as follows:

  1. The servlet is passed the request, which is detected as the first dispatch, so the request is suspended and a list to accumulate results is added as a request attribute:
    // If no results, this must be the first dispatch, so send the REST request(s)
    if (results==null) {
        final Queue> resultsQueue = new ConcurrentLinkedQueue<>();
        request.setAttribute(RESULTS_ATTR, results=resultsQueue);
        final AsyncContext async = request.startAsync();
        async.setTimeout(30000);
        ...
  2. After suspending, the servlet creates and sends an asynchronous HTTP exchange for each keyword:
    for (final String item:keywords) {
      _client.newRequest(restURL(item)).method(HttpMethod.GET).send(
        new AsyncRestRequest() {
          @Override
          void onAuctionFound(Map<String,String> auction) {
            resultsQueue.add(auction);
          }
          @Override
          void onComplete() {
            if (outstanding.decrementAndGet()<=0)
              async.dispatch();
          }
        });
    }
  3. All the rest requests are handled in parallel by the eBay servers and when each of them completes, the call back on the exchange object is called. The code (shown above) extracts auction information in the base class from the JSON response and adds it to the results list in the onAuctionFound method.  In the onComplete method, the count of expected responses is then decremented and when it reaches 0, the suspended request is resumed by a call to dispatch.
  4. After being resumed (dispatched), the request is re-dispatched to the servlet. This time the request is not initial and has results, so the results are retrieved from the request attribute and normal servlet style code is used to generate a response:
    List> results = (List>) request.getAttribute(CLIENT_ATTR);
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.println("");
    for (Map m : results){
      out.print("");
    ...
    out.println("");
    
  5. The example does lack some error and timeout handling.

This example shows how the Jetty asynchronous client can easily be combined with the asynchronous servlets of Jetty-9 (or the Continuations of Jetty-7) to produce very scalable web applications.