Jetty-6 Continuations introduced the concept of asynchronous servlets to provide scalability and quality of service to web 2.0 applications such as chat, collaborative editing, price publishing, as well as powering HTTP based frameworks like cometd, apache camel, openfire XMPP and flex BlazeDS. wt58jhp2an
With the introduction of similar asynchronous features in Servlet-3.0, some have suggested that the Continuation API would be deprecated. Instead, the Continuation API has been updated to provide a simplified portability run asynchronously on any servlet 3.0 container as well as on Jetty (6,7 & 8). Continuations will work synchronously (blocking) on any 2.5 servlet container. Thus programming to the Continuations API allows your application to achieve asynchronicity today without waiting for the release of stable 3.0 containers (and needing to upgrade all your associated infrastructure).
Continuation Improvements
The old continuation API threw an exception when the continuation was suspended, so that the thread to exit the service method of the servlet/filter. This caused a potential race condition as a continuation would need to be registered with the asynchronous service before the suspend, so that service could do a resume before the actual suspend, unless a common mutex was used.
Also, the old continuation API had a waiting continuation that would work on non-jetty servers. However the behaviour of this the waiting continuation was a little different to the normal continuation, so code had to be carefully written to work for both.
The new continuation API does not throw an exception from suspend, so
the continuation can be suspended before it is registered with any
services and the mutex is no longer needed. With the use of a ContinuationFilter for non asynchronous containers, the continuation will now behaive identically in all servers.
Continuations and Servlet 3.0
The servlet 3.0 asynchronous API introduced some additional asynchronous features not supported by jetty 6 continuations, including:
- The ability to complete an asynchronous request without dispatching
- Support for wrapped requests and responses.
- Listeners for asynchronous events
- Dispatching asynchronous requests to specific contexts and/or resources
While powerful, these additional features may also be very complicated and confusing. Thus the new Continuation API has cherry picked the good ideas and represents a good compromise between power and complexity. The servlet 3.0 features adopted are:
- The completing a continuation without resuming.
- Support for response wrappers.
- Optional listeners for asynchronous events.
Using The Continuation API
The new continuation API
is available in Jetty-7 and is not expected to significantly change in
future releases. Also the continuation library is intended to be
deployed in WEB-INF/lib and is portable. Thus the jetty-7 continuation
jar will work asynchronously when deployed in jetty-6, jetty-7, jetty-8
or any servlet 3.0 container.
Obtaining a Continuation
Continuation continuation = ContinuationSupport.getContinuation(request);
Suspending a Request
void doGet(HttpServletRequest request, HttpServletResponse response) { ... continuation.suspend(); ... }
After this method has been called, the lifecycle of the request will be extended beyond the return to the container from the Servlet.service(…) method and Filter.doFilter(…) calls. After these dispatch methods return to, as suspended request will not be committed and a response will not be sent to the HTTP client.
Once a request is suspended, the continuation should be registered with an asynchronous service so that it may be used by an asynchronous callback once the waited for event happens.
The request will be suspended until either continuation.resume()
or continuation.complete()
is called. If neither is called then the continuation will timeout after a default period or a time set before the suspend by a call to continuation.setTimeout(long)
. If no timeout listeners resume or complete the continuation, then the continuation is resumed with continuation.isExpired()
true.
There is a variation of suspend for use with request wrappers and the complete lifecycle (see below):
continuation.suspend(response);
Suspension is analogous to the servlet 3.0 request.startAsync()
method. Unlike jetty-6 continuations, an exception is not thrown by suspend and the method should return normally. This allows the registration of the continuation to occur after suspension and avoids the need for a mutex. If an exception is desirable (to bypass code that is unaware of continuations and may try to commit the response), then continuation.undispatch()
may be called to exit the current thread from the current dispatch by throwing a ContinuationThrowable.
Resuming a Request
void myAsyncCallback(Object results) { continuation.setAttribute("results",results); continuation.resume(); }
Once a continuation is resumed, the request is redispatched to the servlet container, almost as if the request had been received again. However during the redispatch, the continuation.isInitial()
method returns false and any attributes set by the asynchronous handler are available.
Continuation resume is analogous to Servlet 3.0 AsyncContext.dispatch()
.
Completing Request
method:
void myAsyncCallback(Object results) { writeResults(continuation.getServletResponse(),results); continuation.complete(); }
After complete is called, the container schedules the response to be committed and flushed.
Continuation resume is analogous to Servlet 3.0 AsyncContext.complete()
.
Continuation Listeners
void doGet(HttpServletRequest request, HttpServletResponse response) { ... Continuation continuation = ContinuationSupport.getContinuation(request); continuation.addContinuationListener(new ContinuationListener() { public void onTimeout(Continuation continuation) { ... } public void onComplete(Continuation continuation) { ... } }); continuation.suspend(); ... }
Continuation listeners are analogous to Servlet 3.0 AsyncListener
s.
Continuation Patterns
Suspend Resume Pattern
void doGet(HttpServletRequest request, HttpServletResponse response) { // if we need to get asynchronous results Object results = request.getAttribute("results); if (results==null) { final Continuation continuation = ContinuationSupport.getContinuation(request); // if this is not a timeout if (continuation.isExpired()) { sendMyTimeoutResponse(response); return; } // suspend the request continuation.suspend(); // always suspend before registration // register with async service. The code here will depend on the // the service used (see Jetty HttpClient for example) myAsyncHandler.register(new MyHandler() { public void onMyEvent(Object result) { continuation.setAttribute("results",results); continuation.resume(); } }); return; // or continuation.undispatch(); } // Send the results sendMyResultResponse(response,results); }
This style is very good when the response needs the facilities of the servlet container (eg it uses a web framework) or if the one event may resume many requests so the containers thread pool can be used to handle each of them.
Suspend Continue Pattern
void doGet(HttpServletRequest request, HttpServletResponse response) { final Continuation continuation = ContinuationSupport.getContinuation(request); // if this is not a timeout if (continuation.isExpired()) { sendMyTimeoutResponse(request,response); return; } // suspend the request continuation.suspend(response); // response may be wrapped. // register with async service. The code here will depend on the // the service used (see Jetty HttpClient for example) myAsyncHandler.register(new MyHandler() { public void onMyEvent(Object result) { sendMyResultResponse(continuation.getServletResponse(),results); continuation.complete(); } }); }
This style is very good when the response does not needs the facilities of the servlet container (eg it does not use a web framework) and if an event will resume only one continuation. If many responses are to be sent (eg a chat room), then writing one response may block and cause a DOS on the other responses.
Continuation Examples
Chat Servlet
Quality of Service Filter
If too many requests are received, the extra requests wait for a short time on a semaphore, before being suspended. As requests within the filter return, they use a priority queue to resume the suspended requests. This allows your authenticated or priority users to get a better share of your servers resources when the machine is under load.
Denial of Service Filter
Proxy Servlet
Gzip Filter
The jetty GzipFilter is a filter that implements dynamic compression by wrapping the response objects. This filter has been enhanced to understand continuations, so that if a request is suspended in suspend/complete style and the wrapped response is passed to the asynchronous handler, then a ContinuationListener is used to finish the wrapped response. This allows the GzipFilter to work with the asynchronous ProxyServlet and to compress the proxied responses.
Where do you get it?
You can read about it, or download it with jetty or include it in your maven project like this pom.xml.
1 Comment
Scott Lewis · 02/07/2009 at 17:46
Hi Greg,
The addition of continuations to jetty/servlet containers is very interesting.
FWIW and FYI over the Equinox 3.5 release cycle there was some new concurrent support added, basically in the form of futures integrated with some of the Equinox codebase…see http://download.eclipse.org/eclipse/downloads/drops/R-3.5-200906111540/eclipse-news-all.html.
I was responsible for this contribution and the ECF project http://www.eclipse.org/ecf is using it. There may be some utility in coordinating these ongoing efforts to add better asynch/concurrency support in Equinox and Jetty.
Comments are closed.