Patterns for Servlet 3.0 suspend usage.
As I have previously blogged, asynchronous coding is hard! The suspend proposal for Servlet 3.0 does take a lot of the pain out of asynchronous programming, but not all. It has been pointed out, that my own async examples make some assumptions that simplify the code. Specifically they assume that there are no upstream suspenders (eg a filter deployed in front) that have already suspended and resumed and thus affected the values returned by isInitial, isResumed and isTimeout.
So these examples need to be a little more complex to deal with all circumstances. One way to deal with such complexity is with patterns, which can help explain the generic cases, provide a template for specific implementations and/or be the basis of frameworks to help developers. Thus I have captured the key usages of the suspend API in the following 5 patterns:
Suspend/Complete Servlet
This is the simplest pattern, where a servlet suspends a request and organizes for the response to be completed by asynchronous threads or call backs. This is not affected by any upstream suspenderspublic void doGet(HttpServletRequest request,
HttpServletResponse response)
{
request.suspend();
// arrange for response to be completed
// by async thread(s) or callback(s)
}
Simple Suspend/Resume Servlet
If a servlet developer knows that the servlet will not be fronted by suspending filters, then it can use a simplified pattern:Note that the suspend call should happen before arranging async thread/callback so that there is not a risk of a resume before the suspend.public void doGet(HttpServletRequest request,
HttpServletResponse response)
{
if(request.isInitial())
{
// handle intial dispatch
request.suspend();
// arrange async thread/callback
return;
}
if(request.isTimeout())
{
// handle timeout
}
// generate response
}
Suspend/Resume Servlet
If a suspending servlet can be downstream of a filter (or dispatching servlet) that also suspends, then the isInitial(), isTimeout() and isResumed() methods may not be set due to this servlets suspend. A request attribute is required to flag that this servlet has performed the suspend. The attribute name needs to be chosen so that it will not clash with other instances. The attribute value may be a simple boolean or a more complex state object to pass information from the initial to he resume/timeout handling.The isInitial() call is still used as an efficiency. If it is is true, then the request is initial for all filters and servlets. The value of the attribute only needs to be checked if initial returns false.public void doGet(HttpServletRequest request,
HttpServletResponse response)
{
if(request.isInitial()
|| request.getAttribute("com.acme.suspend")==null)
{
// handle intial dispatch
request.setAttribute("com.acme.suspend",
Boolean.TRUE);
request.suspend();
// arrange callback
return;
}
Boolean suspended=request.getAttribute("com.acme.suspend");
if (suspended)
{
request.setAttribute("com.acme.suspend",Boolean.FALSE);
if(request.isTimeout())
{
// handle timeout
return;
}
// handle resume
}
// generate response
}
Simple Suspend/Resume Filter
If a filter developer knows that there are no upstream or downstream suspenders, then a simplified pattern similar to the Simpler Suspend/Resume Servlet may be used:public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
{
if(request.isInitial())
{
// handle intial dispatch
request.suspend();
// arrange async callback
return;
}
if(request.isTimeout())
{
// handle timeout
return;
}
// handle resume
chain.doFilter(request,response);
}
Suspend/Resume Filter
If a suspending filter is to be deployed where there may be either/both upstream and/or downstream suspending components, then a request attribute needs to be used to track both the initial handling and to signal that the resume/timeout has been handled. The attribute value may be a simple boolean or a more complex state object to pass information from the initial to the resume/timeout handling.public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
{
if(request.isInitial() || request.getAttribute("com.acme.suspend")==null)
{
// handle intial dispatch
request.setAttribute("com.acme.suspend",Boolean.TRUE);
request.suspend();
// arrange async callback
return;
}
Boolean suspended=request.getAttribute("com.acme.suspend");
if (suspended)
{
request.setAttribute("com.acme.suspend",Boolean.FALSE);
if(request.isTimeout())
{
// handle timeout
return;
}
// handle resume
}
chain.doFilter(request,response);
}
