This cycle can repeat more that once and the suspension effectively turns the existing dispatch mechanism into an asynchronous callback. To allow this mechanism to work with existing frameworks and code that is unaware of suspension, when a request is suspended, the response object is disabled so that headers and content may not be written.
public interface ServletRequest
{
/**
* Suspend the processing of the request and associated {@link ServletResponse}.
*
* <p>After this method has been called, the lifecycle of the request
* will be extended beyond the return to the container from the
* {@link Servlet#service(ServletRequest, ServletResponse)} method and
* {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)} calls. If a
* request is suspended, then the container will not commit the associated response
* when the call to the filter chain and/or servlet service method returns to the
* container. Instead the container will wait until either
* {@link ServletRequest#complete()} is called, {@link ServletRequest#resume()is
* called or the passed timeout expires. If resume is called or the timeout expires
* then the request will be redispatched via the filter and servlet processing.
* </p>
*
* <p>If a request is already suspended, any subsequent calls to suspend will set
* the timeout to the minimum of the previous timeout and the newly passed
* timeout</p>
*
* <p>Suspend may only be called by a thread that is within the service calling
* stack of {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)}
* and/or {@link Servlet#service(ServletRequest, ServletResponse)}. A request that has
* been dispatched for error handling may not be suspended.
* </p>
*
* @see {@link #resume()}
* @see {@link #complete()}
* @since 3.0
*
* @param timeoutMs The time in milliseconds to wait before retrying this request.
*
* @exception IllegalStateException If the calling thread is not within the calling
* stack of {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)}
* and/or {@link Servlet#service(ServletRequest, ServletResponse)} or if the request
* has been dispatched for error handling.
*/
void suspend(long timeoutMs);
/**
* Resume a suspended request.
*
* <p>This method can be called by any thread that has been passed a reference to
* a suspended request. When called the request is redispatched to the normal filter
* chain and servlet processing.</p>
*
* <p>If resume is called before a suspended request is returned to the container
* (ie the thread that called {@link #suspend()} is still within the filter
* chain and/or servlet service method), then the resume does not take effect until
* the call to the filter chain and/or servlet returns to the container. In this
* case both {@link #isSuspended()} and {@link isResumed()} continue to return true.
* until the request is returned to the container.</p>
*
* <p>Multiple calls to resume are ignored</p>
*
* @see {@link #suspend()}
* @since 3.0
* @exception IllegalStateException if the request is not suspended.
*
*/
void resume();
/**
* Complete a suspended request.
*
* <p>This method can be called by any thread that has been passed a reference to
* a suspended request. When a request is completed, the associated response object
* commited and flushed. The request is not redispatched.</p>
*
* <p>If complete is called before a suspended request is returned to the container
* (ie the thread that called {@link #suspend(long)} is still within the filter
* chain and/or servlet service method), then the complete does not take effect until
* the call to the filter chain and/or servlet returns to the container. In this
* case {@link #isSuspended()} continues to return true until the request is returned to the container.</p>
* <p>Closing the response output stream or writer is equivalent to a call to complete()</p>
*
* @see {@link #suspend()}
* @since 3.0
* @exception IllegalStateException if the request is not suspended.
*
*/
void complete();
/**
* @return true after {@link #suspend(long)} has been called and before the request
* has been resumed or timed out.
* @since 3.0
*/
boolean isSuspended();
/**
* @return true if the request has been redispatched by a call to {@link #resume()} or
* by a timeout. Returns false after any subsequent call to suspend
* @since 3.0
*/
boolean isResumed();
/**
* @return true after a request has been redispatched as the result of a timeout.
* Returns false after any subsequent call to suspend.
* @since 3.0
*/
boolean isTimeout();
// existing methods not shown
}
public interface ServletRequest
{
// existing methods not shown
/**
* A request has been suspended.
* Called by the thread that dispatched the servlet when it
* has returned to the container.
*/
void requestSuspended(ServletRequestEvent rre);
/**
* A request has been resumed.
* Called by the thread that will dispatch to the servlet
* immediately before dispatch.
*/
void requestResumed(ServletRequestEvent rre);
/**
* A request has been completed.
* Called from a call to {@ServletRequest#complete()}
* @since 3.0
*/
void requestCompleted(ServletRequestEvent rre);
}
public class QosFilter implements Filter
{
private final static String PASS = "PASS";
int _passes = 20;
Queue<ServletRequest> _lowPriority = new LinkedList<ServletRequest>();
Queue<ServletRequest> _highPriority = new LinkedList<ServletRequest>();
public void init(FilterConfig filterConfig) {}
public void destroy(){}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
synchronized(this)
{
boolean has_pass=Boolean.TRUE.equals(request.getAttribute(PASS));
if (!has_pass)
{
if (request.isResumed())
{
_lowPriority.remove(request);
_highPriority.remove(request);
((HttpServletResponse)response)
.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
return;
}
if (_passes>0)
{
has_pass=true;
_passes--;
}
else
{
request.suspend();
if (((HttpServletRequest)request).isUserInRole("priority"))
_highPriority.add(request);
else
_lowPriority.add(request);
return;
}
}
}
try
{
assert has_pass;
chain.doFilter(request,response);
}
finally
{
synchronized(this)
{
ServletRequest waiting = _highPriority.poll();
if (waiting==null)
waiting = _lowPriority.poll();
if (waiting==null)
_passes++;
else
{
waiting.setAttribute(PASS,Boolean.TRUE);
waiting.resume();
}
}
}
}
}