Greg Wilkins is the Chief Technical Officer (CTO) and one of the founding CEO of Webtide. He was also a founder and CEO of Mort Bay Consulting. Greg has deep knowledge of all facets of software development. He has, 22 years experience as a software developer, team leader, architect, trainer, and technical mentor in industry sectors ranging from telecommunications, finance, realtime computing to internet applications. He is closely involved with the open source movement, being the creator of the Jetty web container, a co-founder of Apache Geronimo, and a committer or contributor to a number of other open source projects. Greg sits on the JCP Servlet Expert Group and is active in the Open Ajax Alliance. Greg received his B.S. Computer Science Degree with 1st Class Honors from Sydney University, Australia. Greg has posted 11 posts at DZone. View Full User Profile

Asynchronous Restful Web Application

12.09.2008
| 15499 views |
  • submit to reddit

This blog annotates the Jetty 7 example web application that uses Jetty asynchronous HTTP client and the proposed suspendable 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.

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 "chair" is handled by the synchronous implementation. The call takes 660ms and the servlet thread is blocked for the entire time. A server with a 100 threads in a pool would be able to handle 151 requests per second.
Asynchronous Call, Single Keyword
A request to lookup ebay auctions with the keyword "chair" is handled by the asynchronous implementation. The call takes 669ms, but the servlet request is suspended so the request thread is held for only 2ms.  A server with a 100 threads in a pool would be able to handle 5000 requests per second (if not constrained by other limitations)
Synchronous Call, Three Keywords
A request to lookup ebay auctions with keywords "mouse", "beer" and "gnomes" is handled by the synchronous implementation. Three calls are made to ebay in series, each taking approx 900ms, with a total time of 2706ms 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 40 requests per second!
Asynchronous Call, Three Keywords
A request to lookup ebay auctions with keywords "mouse", "beer" and "gnomes" is handled by the asynchronous implementation. The three calls can be made to ebay in parallel, each taking approx 900ms, with a total time of 906ms and the servlet request is suspended, so the request thread is held for only 2ms. A server with a 100 threads in a pool would be able to handle 5000 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 the capacity by avoiding thread starvation.

The code for the example asynchronous servlet is available here and works as follows:
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 (request.isInitial() || request.getAttribute(CLIENT_ATTR)==null)
{
String[] keywords=request.getParameter(ITEMS_PARAM).split(",");

final List<Map<String, String>> results =
Collections.synchronizedList(new ArrayList<Map<String, String>>());
final AtomicInteger count=new AtomicInteger(keywords.length);

request.suspend();
request.setAttribute(CLIENT_ATTR, results);

The request is suspended before starting the searches in order to avoid races if the searches somehow complete before the request is suspended.

After suspending, the servlet creates and sends an asynchronous HTTP exchange for each keyword:

for (final String item:keywords)
{
ContentExchange exchange = new ContentExchange()
{
protected void onResponseComplete() throws IOException
{
// see step 4 below
}
};
exchange.setMethod("GET");
exchange.setURL("http://open.api.ebay.com/shopping?MaxEntries=5&appid=" +
_appid +
"&version=573&siteid=0&callname=FindItems&responseencoding=JSON&QueryKeywords=" +
URLEncoder.encode(item,"UTF-8"));
_client.send(exchange);
}

The API for the Jetty Http client  exchanges was inspired by the callback style of javascript XHR.
Once all asynchronous HTTP exchanges are sent, the servlet saves some timing information for the demo and then returns. Because the request is suspended, the response is not flushed to the browser, but the thread is returned to the thread pool so it can service other requests:

request.setAttribute(START_ATTR, start);
request.setAttribute(DURATION_ATTR, new Long(System.currentTimeMillis() - start));
return;
}

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 (omitted above, shown below)extracts auction information from the JSON response and adds it to the results list. The count of expected responses is then decremented and when it reaches 0, the suspended request is resumed:

protected void onResponseComplete() throws IOException
{
Map query = (Map) JSON.parse(this.getResponseContent());
Object[] auctions = (Object[]) query.get("Item");
if (auctions != null)
{
for (Object o : auctions)
results.add((Map) o);
}

if (count.decrementAndGet()<=0)
request.resume();
}

After being resumed, 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<Map<String, String>> results = (List<Map<String, String>>) request.getAttribute(CLIENT_ATTR);
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html><head><style type='text/css'>img:hover {height:75px}</style></head><body><small>");

for (Map<String, String> m : results)
{
out.print("<a href=\""+m.get("ViewItemURLForNaturalSearch")+"\">");
...

This example shows how the Jetty asynchronous client can easily be combined with the suspendable servlets of jetty-7 (or the Continuations of Jetty-6) to produce very scalable web applications. Jetty -7now contains similar examples for CXF asynchronous SOAP web services and for calling asynchronous restful services from JSF actions that can suspend.

References
Published at DZone with permission of its author, Greg Wilkins. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)