Asychronous Content Handler Proposal


Introduction

This is a proposal to meet part of the Asynchronous Servlets requirements of JSR315.

Use-Cases

This proposal does not address the use-cases of Suspendable Requests.

Asynchronous IO

This proposal addresses the need for Asynchronous IO when a request has large content that
cannot be read without blocking, or content cannot be written to response without blocking.

Common content types

Content types such as multi-part-mime and XML documents must currently be converted to/from bytes by code supplied by the web application. Applications could be simplified and efficiency improved if the container could handle conversion of known content-types to/from java representations.

Proxying

A HTTP proxy implemented in a web application should be able to asynchronously
read content from a request and forward to a remote server and asynchronously receive
content to be written back to the client.

Overview

Container Content handling

In order to avoid the need for servlets to perform complex asynchronous
IO, the ServletRequest and ServletResponse
classes are extended with methods to allow HTTP message content to be
retrieved and set as complex Java objects.

The task of converting the byte content of requests/responses to/from
these complex objects will be performed by handler classes that can
either be supplied by the container (for common types efficiently handled)
or by the application/framework (for less common types of specific handling).

Container provided handlers will be able to directly access the
containers IO mechanisms to most efficiently perform the conversion.
A compliant container will be
expected to provide a standard set of handlers, 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  

Application/framework provided converters would be implemented
against and an API that would likely not be as efficient as possible
(eg byte arrays instead of direct buffers), but would allow
asynchronous handling.

NOTE: ''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.
''

NOTE: ''There has been a suggestion that JAF could be used to assist with
the conversion of bytes streams to/from java objects. This proposal has
not been updated with that suggestion.''

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;
   
}

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);    
}

RequestContentHandler

A new RequestContentHandler
interface is provided for applications to handle request content
asynchronously. The container provided handlers are not
required to use this interface and may use alternate (and more
efficient) mechanisms to handle content.

/**
 * A handler for Request content.
 * 
 * <p>Implementations of this interface are responsible for handling 
 * bytes received from a request, normally by conversion to an Object instance that will be 
 * made available via the {@link ServletRequest#getContent()} method.
 * 
 * <p>The container will create as many instances of a converter as needed
 * and an instance will be associated with a request by a call to 
 * {@link #init(ServletRequest)}. The container will call 
 * {@link #convert(byte[])} as content is received until either an object 
 * is returned or an exception is throw.
 * 
 * <p>Container provided handlers are not required to implement this
 * interface.
 * 
 */
public interface RequestContentHandler
{
    interface Dispatcher
    {
        /**
         * Dispatch the request to a Servlet.
         * <p> The request that was passed to 
         * {@link RequestContentHandler#begin(Dispatcher,ServletRequest)} will be
         * dispatched to a Servlet, potentially via a Filter chain. The content 
         * object passed will be available via the {@link ServletRequest#getContent()} method.
         * Any content bytes not consumed asynchronously will be available via the normal 
         * blocking API of the request.
         */
        void dispatch(Object content);
    }

    /**
     * Initialize this handler for the request.
     * <p>
     * @param dispatcher The dispatcher instance that should be used to 
     * call {@link Dispatcher#dispatch} when the content is converted to
     * an Object. The request will not be dispatched to a servlet until 
     * dispatch is called.
     * @param request The request that contains the content. The request 
     * headers may be inspected, but not methods that consume content may
     * be called (eg {@link ServletRequest#getInputStream()}. 
     * @param response The response.
     * @throws IllegalStateException If this converter cannot convert the
     * request.
     */
    void begin(Dispatcher dispatcher, ServletRequest request, ServletResponse response);
    
    
    /**
     * handle a chunk of request content.
     * 
     * <p>This method is called when the container receives some bytes of 
     * content. The content may not be complete and subsequent calls to
     * convert will be required to deliver the complete content.
     * A subsequent call will not be made until after the current call
     * to handle has returned. Bytes not consumed by a call to handle will
     * be included in the next call to handle.
     * 
     * @param chunk Either a partial or complete content as an
     * array of bytes.
     * @param offset the offset into the chunk array where content starts
     * @param length the length of the content withing the array
     * @return The number of bytes consumed by this call.
     * @throws IOException If the content cannot be parsed.
     */
    int handle(byte[] chunk,int offset,int length) throws IOException;

    /**
     * handle a read error.
     *
     * <p> This method is called when the container detects an error on a 
     * connection that is delivering content.
     * After this call, this instance of the handler is no longer associated
     * with the request passed in begin and may be reused for a new request.
     * @param th a Throwable indicating the error condition.
     */
    void error(Throwable th);

    /**
     * handle a timeout.
     *
     * <p> This method is called when the container detects no activity on
     * a connection that is delivering content.
     * After this call, this instance of the handler is no longer associated
     * with the request passed in begin and may be reused for a new request.
     */
    void timeout();

    /**
     * Complete the request
     *
     * <p> This method is called when the container detects the end of 
     * request content. 
     * After this call, this instance of the handler is no longer associated
     * with the request passed in begin and may be reused for a new request.
     *
    void complete();
    
}

ResponseContentHandler

A new
ResponseContentHandler
interface is provided for applications to handler request content
passed as Objects. The container provided handlers are not
required to use this interface and may use alternate (and more
efficient) mechanisms to convert content.

/* ------------------------------------------------------------ */
/**
 * A handler for Response content.
 * 
 * <p>Implementations of this interface are responsible for converting 
 * Objects passed in the {@link ServletResponse#setContent(Object)} method 
 * to bytes to be sent as the response content.
 * 
 * <p>The container will create as many instances of a converter as needed
 * and an instance will be associated with a response by a call to 
 * {@link #init(ServletResponse)}. The container will call 
 * {@link #convert(byte[], int, int)} until -1 is returned.
 * 
 * <p>Container provided converters are not required to implement this
 * interface.
 *
 */
public interface ResponseContentHandler
{
    /**
     * Associate this handler with a response.
     * <p>The response headers may be set to indicate the content
     * size, type and encoding.
     * 
     * @param response The response
     * @param content The content to convert.
     * @throws IllegalStateException If the content cannot be converted
     * by this converter.
     */
    void begin(HttpServletResponse response, Object content);

    /** 
     * Convert the content to bytes. The converted bytes are written into
     * the passed buffer starting at the given offset and not exceeding the
     * given length. The actual number of bytes written is returned.
     * The bytes written my represent all of the content or a part of the
     * content. Subsequent calls to convert may be required to convert the
     * entire content.
     * 
     * <p>The container will not call convert until a previous call to convert 
     * returns. The container may delay calling convert until it is ready to
     * send more content to the client.
     * 
     * @param buffer The byte array into which content should be written
     * @param offset The offset into the array at which content should be written
     * @param length The maximum number of bytes that may be written.
     * @return The number of bytes written or -1 to indicate that the entire
     * content has been written.
     * @throws IOException If the content cannot be converted.
     */
    int handle(byte[] buffer,int offset, int length) throws IOException;
}

Deployment Descriptor

Request Content Handlers

Request content handlers may be
defined in the deployment descriptor and may contain the following
elements:

url-pattern

The URL pattern to which the converter applies
which follows the same conventions as the servlet and filter
mappings. A content handler element may have multiple url-pattern
elements.

servlet-name

The name of a servlet to which the converter
applies. A converter may have multiple servlet-name
elements.

mime-type

The mime-type (content-type excluding parameters and encoding) to
which the converter applies.

content-class

The java class of the Object that the converter
will create to contain the content. An instance of this class will
be made available via the ServletRequest.getContent()
method.

asynchronous

If the asynchronous
element is present in a converter, then the converter will run
(asynchronously if possible) before the initial dispatch. If it is
not present, then the converter will run only if and when
ServletRequest.getContent()
is called.

provided

If the provided element is present, the web
application will use a converter class provided by the container
that matches the mime-type
and content-class.
If the container does not provide a matching converter, and there is
no converter-class
element, then the web application will not be able to be deployed.

handler-class

An application supplied class that implements the
RequestContentHandler
interface that will be used to convert content. If only a
converter-class
element is present, then it will be used. If both a converter-class
and provided
elements are present, then provided takes precedence if the
container does provide such a converter.

An example request converter descriptor is:

<request-content-handler>    
  <url-pattern>/comet/*</url-pattern>

  <mime-type>text/json</mime-type>
  <content-class>java.util.Map</content-class>
  <asynchronous/>
  <provided/>
  <handler-class>com.acme.ajax.JsonContentParser</handler-class>
</request-content-handler>

This descriptor defines a handler
that applies to requests to /comet/* that have a content type header
indicating a text/json mime type. The content is converted to a java
Map object asynchronously before the request is dispatched. If the
container provides a suitable handler, it will be used, otherwise
the application provided class will be used.

Response Content Handler

Request content handlers may be
defined in the deployment descriptor and may contain the following
elements:

url-pattern

The URL pattern to which the converter applies
which follows the same conventions as the servlet and filter
mappings. A converter element may have multiple url-pattern
elements.

servlet-name

The name of a servlet to which the converter
applies. A converter may have multiple servlet-name
elements.

content-class

The java class to which this converter may be
applied. This is the type or super type of the Object passed to
ServletResponse.setContent(Object).

content-type

The content type and optional content encoding, to which the content
will be converted.

provided

If the provided element is present, the web
application will use a converter class provided by the container
that matches the mime-type
and content-class.
If the container does not provide a matching converter, and there is
no handler-class
element, then the web application will not be able to be deployed.

handler-class

An application supplied class that implements the
RequestContentHander
interface that will be used to convert content. If only a
converter-class
element is present, then it will be used. If both a converter-class
and provided
elements are present, then provided takes precedence if the
container does provide such a converter.

An example response converter descriptor is:

<response-content-handler>
  <url-pattern>/comet/*</url-pattern>

  <content-class>org.w3c.dom.Document</content-class>
  <content-type>text/xml; charset=utf-8</content-type>
  <provided/>
</request-content-handler>

This converter
applies to all responses generated by resources
at URIs matching /comet/* that use the
ServletResponse.setContent(Object)
to pass an object of type org.w3c.dom.Document. The passed content
will be converted to XML encoded as utf-8 using a converter provided
by the container. If no such converter is available, the web
application will not be able to be deployed.

Annotations

Appropriate annotations can be
defined for filters and servlets to effectively create request and
converter entries with the same semantics as the deployment
descriptor.


Examples


JSP file upload Example

This example shows how asynchronous
input may be achieved within a JSP.
This example uses
a JSP to generate an multipart form for uploading file content. The
ServletRequest.getContent()
API is used to allow the container to upload large files
asynchronously and save them to a temporary file. The upload request
is dispatched to the JSP only once the entire file is uploaded. The
JSP then simply renames the temporary file to a permanent location
before making it available via a link.

upload.js:

<%@ page contentType='text/html; charset=UTF-8' import='java.util.Map,java.io.File' %>
<%
Map content=(Map)request.getContent();
if (content==null)
{
%>
    <h2>UPLOAD content</h2>
    <form method='POST' enctype='multipart/form-data' accept-charset='utf-8' action=".">
      Description: <input type='text' name='comment' value='comment'/><br/>
      File: <input type='file' name='file' /><br/>
      <input type='submit' name='Action' value='Submit'><br/>
    </form>
<%
}
else
{
    String comment = (String)content.get("comment");
    File file = (File)content.get("file");
    String path="/store/"+file.getName();
    file.renameTo(new File(request.getRealPath(path)));
%>
    <h2>UPLOADED content</h2>
    Comment: <%= comment%><br/>
    File: <a href="<%=path%>"><%=file.getName()%></a><br/>  
<%
}
%>

web.xml:

<request-content-handler>    
  <url-pattern>/upload.jsp</url-pattern>

  <mime-type>multipart/form-data</mime-type>
  <content-class>java.util.Map</content-class>
  <asynchronous/>
  <provided/>
</request-content-handler>

Streaming Servlet

To be provided..... but will show a ContentHandler writing directly to a response.