JSR 315 – Servlet 3.0

Asynchronous Servlet Proposal



Abstract

This document contains a proposal to extend the Servlet API as defined by JSR 154 with asynchronous concerns to meet the goals of JSR 315. At this stage, this proposal is only a contribution from the author (Greg Wilkins) and not an official proposal from the JSR.

Introduction

JSR 315 Targeted features

JSR 315 nominates "Async and Comet Support" as a targeted feature, described as:

Non-blocking input

The ability to receive data from a client without blocking if the data is slow arriving.

Non-blocking output

The ability to send data to a client without blocking if the client or network is slow.

Delay request handling

The comet style of Ajax web application can require that a request handling is delayed until either a timeout or an event has occurred. Delaying request handling is also useful if a remote/slow resource must be obtained before servicing the request or if access to a specific resource needs to be throttled to prevent too many simultaneous accesses.

Delay response close

The comet style of Ajax web application can require that a response is held open to allow additional data to be sent when asynchronous events occur.

Blocking/Non-blocking notification

The ability to notify push blocking or non-blocking events. Channels concept - The ability to subscribe to a channel and get asynchronous events from that channel. This implies being able to create, subscribe, unsubscribe and also apply some security restriction on who can join and who cannot.

Additional Requirements

The following additional requirements have been used to guide the design of this proposal. These requirements are only from the experience of the author and should themselves be subject to review and discussion:

Service method

All substantive handling of asynchronous requests and generation of asynchronous responses should take place within the existing calling chain through Filter.doFilter(..) to Servlet.service(...). It is only within this calling chain that we have a well defined environment for authentication, authorization, JNDI, and access to other JEE services. The creation of additional request handling or response generating methods will require substantial redefinition of the servlet environment and will impede frame work compatibility.

First Class Servlet

An asynchronous servlet should be a first class servlet and not limited in any significant way. To this effect an asynchronous servlet should be able to:

Framework compatibility

Handling of asynchronous requests and responses should be able to be done by existing frameworks with little or no modification. For example, JSP and/or JSF should be able to be used to handle a comet request and to generate the comet response.

Container IO empowerment

The servlet container should be empowered to handle common or difficult IO tasks that are currently handled by the servlet developer. With 2.5 servlets, the only content-type that is automatically handed by the container is "application/x-www-form-urlencoded". The servlet developer (or framework) must handle common tasks such as:

If asynchronous IO is enabled within the servlet container, then these common tasks will need to be reimplemented and will be more complex and bug-prone as a result. The remove this source of errors and duplicated effort, the servlet container should instead be empowered to implement common IO tasks on behalf of the servlet developer. The container could then be free to use efficient asynchronous techniques (or even native optimizations) to perform IO on behalf of the servlet developer.



Proposal

Overview

This proposal aims to meet the JSR315 and additional asynchronous requirements by extending the servlet container in the following two areas:

Container Content handling

The ServletRequest and ServletResponse classes are extended with methods to allow HTTP message content to be retrieved and set as complex Java objects. The container will be responsible for the IO, parsing and generation of the content using converters supplied either by the container or the application.

A compliant container will be expected to provide a standard set of converters, including the following:

Mime Type

Java Type

any

byte[]

any

java.lang.String

any

java.io.File

text/xml

org.w3c.dom.Document

text/json

java.util.Map

multipart/form-data

java.util.Map



The container supplied converters may operate asynchronously or synchronously. There will be an API to allow applications to provide additional, replacement, or optional converters.

The current API proposal uses the Object class for content, however it should be possible to extend this proposal to use generics for type safe content handling.

Suspended Servlet life cycle

The request handling lifecycle is extended so that the handling of a single request can span multiple dispatches to the filter chain and the servlet service method. This allows the handling of a request and the completion of a response to be delayed until resources are available or asynchronous events occur or complete.

A request life cycle is started when a request/response pair is first dispatched. Normally the request life cycle will complete when a the dispatch returns and the response will be committed, flushed and completed. However, the extension allows for a request to be suspended, so that when the dispatch returns to the container, the request lifecycle is not completed.

A suspended request is held by the container until either it is resumed or a timeout expires. The request is then retried by being dispatched to the normal filter chain and servlet service method.

This cycled 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. Additional methods are provided to access a "NonStop" output stream or writer, that can be used at any time during the request lifecycle and is not disabled by request suspend.

Java API

ServletRequest

The existing ServletRequest interface is extended with methods to access parsed content and to suspend request handling:

public interface ServletRequest 
{
    /**
     * Get the request body content as an Object. 
     * 
     * <p>The bytes received byte the server will be parsed and converted to the Object
     * by a {@link RequestContentConverter} provided either by the container or the 
     * application and configured in the deployment descriptor or servlet annotations. 
     * Examples of the type of Object that could be returned include 
     * {@link java.nio.ByteBuffer}, {@link String}, {@link java.io.File} and 
     * {@link org.w3c.dom.Document}. If the request specifies a character encoding or 
     * Locale, then this will be used for the parsing of the content, otherwise default 
     * encodings may be specified in the deployment descriptor or servlet annotations.
     * </p>
     * 
     * <p>If the deployment descriptor or servlet annotations indicates that the request 
     * URL is asynchronous, then the container will parse the content before the request 
     * is dispatched to the filter chain and/or servlet and this call will never block. 
     * Otherwise, the content is parsed during this call and may block waiting for the 
     * complete content to be received.</p>
     * 
     * @return   an object containing parsed body of the request
     *
     * @exception IllegalStateException  if the {@link #getReader} or 
     *     {@link #getInputStream} methods has already been called for this request
     *                                   
     * @exception ObjectSTreamException  if there was a problem parsing the content.                                
     *
     * @exception IOException           if an input or output exception occurred
     */
    Object getContent() throws IllegalStateException, ObjectStreamException;

    /**
     * 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#resume()}  is called or the passed timeout expires, at
     * which  time the request will be redispatched via the normal filter chain to the
     * servlet.
     * </p>
     * 
     * <p>The associated {@link ServletResponse} object is disabled by this call, so 
     * that all calls to modify or generate a response are silently ignored, until 
     * such time as the request is retried. With the exception that the objects 
     * returned by {@link ServletResponse#getNonStopOutputStream()} and  
     * {@link ServletResponse#getNonStopWriter} remain active throughout the request 
     * lifecycle.</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)}</p>
     * 
     * @see {@link #resume()}
     * 
     * @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)}
     */
    void suspend(long timeoutMs);

    
    /**
     * Resume a suspended request.
     * 
     * <p>This method can be called by any thread that has been passed a reference to 
     * the request to request that the request be redispatched</p>
     * 
     * <p>If resume 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 resume does not take effect until
     * the call to the filter chain and/or servlet returns to the container. In this 
     * case, until  the service call returns, both {@link #isSuspended()} and 
     * {@link isResumed()} to return true.</p>
     * 
     * <p>If resume is called on an unsuspended request, then the resume will apply to 
     * any subsequent calls to suspend. In this case, {@link #isResumed()} will true 
     * and {@link #isSuspended()} will return false. </p>
     * 
     * <p>If resume is called on a request after the request lifecycle is complete, 
     * then the results are undefined</p>
     * 
     * <p>Multiple calls to resume are ignored</p>
     * 
     * @see {@link #response()}
     * 
     */
    void resume();

    /**
     * @return true after {@link #suspend(long)} has been called and before the request 
     * has been resumed or timedout.
     */
    boolean isSuspended();

    /**
     * @return true after {@link #resume()} has been called and before any subsequent
     * call to suspend
     */
    boolean isResumed();

    /**
     * @return true after a request has been retried as the result of a resume or a 
     * timeout
     */
    boolean isRetry();

    /**
     * @return true after a request has been retried as the result of a timeout
     */
    boolean isTimeout();
}


ServletResponse

The existing ServletResponse interface is extended with methods to asynchronously send content and to obtain non stop streams:

public interface ServletResponse
{
    // existing response methods omitted

    /**
     * Set the response content body
     * 
     * <p>Set the response body to be sent as an Object.  The Object will be
     * converted to bytes byte a {@link ResponseContentConverter} instance 
     * provided either by the container or application and configured in the
     * deployment descriptor or servlet annotations.</p>
     * 
     * <p>The converter will set the content length, content type and content
     * encoding of the response.</p>
     * 
     * <p>A call to {@link #flushBuffer()} will block until the entire content
     * is converted to bytes and flushed. If {@link #flushBuffer()} is not called,
     * then the container may generate and write the byte content asynchronously 
     * after the call to the filter chain and servlet service method return 
     * to the container.
     * 
     * @exception IllegalStateException if any of the {@link #getWriter()}, 
     *     {@link #getNonStopWriter(),  {@link #getOutputStream()} or
     *     {@link #getNonStopOutputStream()} methods have been called on 
     *     this response
     *
     */
    void setContent(Object content);

    /**
     * Returns a {@link PrintWriter} suitable for writing character text
     * in the response.  Unlike the writer returned by {@link #getWriter()},
     * the writer returned by this method is not affected by a call to 
     * {@link ServletRequest#suspend(long)} and may be used by any thread
     * during the request lifecycle. </p>
     * 
     * <p>The writer is not guaranteed to be thread safe, and if multiple 
     * threads are using the stream, they must use some form of mutual
     * exclusion.</p>
     *
     * <p>Calling flush() on the Writer