Posted on 14-08-2007
Filed Under (Internet) by galifate

Resumen

Este documento contiene una propuesta para extender la API de Servlet como se define en la JSR 154 con asincronía para cumplir los objetivos de la JSR 315.
En este momento, esta propuesta es sólo una contribución de su autor (Greg Wilkins) y no una propuesta oficial de la JSR.

Introducción

Las características objetivo de JSR 315

JSR 315 nombra “Async and Comet Support” como una característica objetivo as a targeted feature, desacrita como:

Entrada no bloqueante

La habilidad de recibir datos de un cliente sin bloquear si los datos tardan en llegar.

Salida no bloqueante

La habilidad de enviar datos a un cliente sin bloquear si el cliente o la red es lenta.

Retrasar la gestión de la petición

El “estilo cometa” de una aplicación web AJAX puede requerir que la gestión de una petición sea retrasada hasta que ocurra un timeout o un evento.
Retrasar la gestión de una petición también es útil si hay que obtener un recurso remoto/lento antes de servir la petición o si el acceso a un recurso
específico necesita ser suprimido para prevenir demasiados accesos simultáneos.

Retrasar el cierre de una respuesta

El “estilo comet” de una aplicación web AJAX puede requerir que una respuesta se mantenga abierta para permitir que datos adicionales sean enviados cuando ocurran eventos asíncronos.

Notificaciones bloqueantes/no bloqueantes

La habilidad de notificar eventos bloqueantes o no bloqueantes. El concepto de canales - la habliddad de suscribirse a un canal y obtener eventos asíncronos de ese canal. Esto implica poder crear, suscribirse, desuscribirse y también aplicar algunas restricciones de seguridad sobre quién puede añadirse o quién no puede añadirse a un canal.

Requerimientos adicionales

Los siguientes requerimientos adicionales han sido usados para guiar el diseño de esta propuesta. Estos requerimientos son sólo desde la experiencia del autor y deben ser sujeto de revisión y discusión:

Service method

Toda gestión relevante de peticiones asíncronas y generación de respuestas asíncronas debe tener lugar dentro de la cadena de llamadas existente de Filter.doFilter(..) a Servlet.service(…). Sólo dentro de esta cadena de llamadas tenemos un entorno bien definido para autenticación, autorización, JNDI, y acceso a otros servicios J2EE. La creación de gestores de peticiones adicionales o métodos de respues requerirá una redefinición sustancial del entorno del servlet e impedirá compatibilidad de marcos de trabajo.

First Class Servlet

Un servlet asíncrono debe ser una first class servlet y no debe estar limitado de un modo significativo. Para ello, un servlet asíncrono debe poder:

  • Ser filtrado por un nombre o dirección URL.
  • Ser asegurado por mecanismos de seguridad estándar.
  • Ser el iniciador o el el receptor de un

Compatibilidad de framework

La gestión de peticiones y respuestas asíncronas debe poder hacerse por frameworks existentes con pocas o ninguna modificación. por ejemplo, JSP y/o JSF deben poder ser usados para gestionar una petición asíncrona y para generar una respuesta asíncrona.

Capacitación Entrada/Salida del Contenedor

El contenedor del servlet debería estar capacitado para gestionar tareas de entrada/salida tanto comunes como complicadas que actualmente las gestionar el desarrollador del servlet. Con los servlets 2.5, el único tipo de contenido gestionado autmáticamnete por el contenedor es “application/x-www-form-urlencoded“. El desarrollador del servlet (o framework) debe gestionar tares corrientes como:

  • Parsear multi-part mime
  • Parsear XML a DOM desde streams de entrada de una petición
  • Escribir DOM a los streams de salida para las respuestas
  • Mover datos desde peticiones a ficheros, o desde ficheros a respuestas.

Si la e/s asíncrona está habilitada dentro de un contenedor de un servlet, estas tareas necesitarán ser reimplementadas y serán más complejas y con más posibilidad de fallos. Eliminar esta fuente de errores y de esfuerzo innecesario implica dar poder a los contenedores de servlets para implementar tareas comunes de e/s en lugar del desarrollador del servlet. El contenedor entonces puede ser libre de usar técnicas asíncronas eficientes (o incluso optimizaciones nativas) para hacer e/s en lugar del desarrollador.

Propuesta

Resumen

Esta propuesta pretende cumplir los requerimientos de la JSR315, además de otros, extendiendo el contenedor de servlets en las siguientes dos areas:

Gestión del Contenido del Contenedor

Las clases ServletRequest y ServletResponse se extienden con métodos para permitir que el contenido de los mensajes HTTP sean devueltos y establecidos como objetos complejos Java. El contenedor será responsable de la entrada/salida, parseo y generación del contenido usando conversores suministrados por el contenedor o por la aplicación.

Se esperará de un contenedor compatible que suministre un conjunto de conversores estándar, incluyendo los siguientes:

Tipo Mime Tipo Java

Los conversores suministrados por el contenedor deben operar asíncronamente o síncronamente. Habrá una API para permitir a a las aplicaciones suministrar conversores adicionales, sustitutivos u opcionales. Las propuesta de API actual usa la clase Object para el contenido, sin embargo puede ser posible extender esta propuesta para usar clases genéricas para gestión de contenido de tipo seguro.

Ciclo de vida de Servlet Suspendido

El ciclo de vida de una petición se extiende de modo que la gestión de una única petición puede englobar múltiples envíos a la cadena de filtros y al método service del servlet. Esto permite que la gestión de una petición y la generación de una respuesta se retrasen hasta que los recursos estén disponibles u ocurran o acaben eventos asíncronos.
El ciclo de vida de una petición empieza cuando una pareja petición/respuesta sea despachada por primera vez. Normalmente, se completa el ciclo de via de una petición cuando retorna el despacho y se confirma, se lanza y se finaliza la respuesta. Sin embargo, la extensión permite que una petición sea suspendida, tal que cuando el dispatch vuelve al contenedor, el ciclo de vida de la petición no sea completado.

El contenedor mantiene la petición suspendida hasta que o se reanuda o expira por sobrepasar el tiempo de espera. Entonces la petición se devuelve siendo despachada a la cadena de filtros normal y al método service del servlet. Este ciclo se puede repetri más de una vez y la suspensión transforma el mecanismo existente de despacho en un callback asíncrono. Para permitir que este mecanismo funcione con los frameworks y códigos existentes, se puede usar en cualquier momento durante el ciclo de vida de la petición y no se deshabilita por la suspensión de la petición.
request suspend.

Java API

ServletRequest

La interfaz ServletRequest existente se extiende cin métoso para acceder al contenido parseado y para suspender la gestión de la petición:

public interface ServletRequest 
{
    /**
     * Obtiene el contenido del cuerpo de la petición como un Object. 
     * 
     * <p>Los bytes recibidos por el servidor serán parseados y
     * convertidos a un Object por un {@link RequestContentConverter}
     * del contenedor o de la aplicación configurado en el descriptor 
     * de despliegue o las anotaciones del servlet. 
     * Ejemplos del tipo de Object que pueden ser retornados incluyen 
     * {@link java.nio.ByteBuffer}, {@link String}, {@link java.io.File} y 
     * {@link org.w3c.dom.Document}. Si la petición especifica una
     * codificación de caracteres o un Locale, entonces esto se usará en el
     * parseo del contenido, sinó se deben especificar codificaciones por
     * defecto en el descriptor de despliegue o en las anotaciones del servlet.
     * </p>
     * 
     * <p>Si el descriptor de despliegue o las anotaciones del servlet indican que la
     * URL de la petición es asíncrona, entonces el contenedor parseará en contenido
     * antes de que la petición sea servida a la cadena de filtros o/y al servlet y
     * esta llamada nunca se bloqueará. De otro modo, el contenido es parseado
     * durante esta llamada y puede bloquearse esperando a recibir el contenido completo.</p>
     * 
     * @return   un objeto que contiene el cuerpo parseado de la petición
     *
     * @exception IllegalStateException  si los métodos {@link #getReader} o 
     *     {@link #getInputStream} ya han sido llamados por esta para esta
     *	   petición
     *                                   
     * @exception ObjectSTreamException  si hubo algún problema parseando el contenido.                                
     *
     * @exception IOException           si ha ocurrido un excepción de entrada o salida.
     */
    Object getContent() throws IllegalStateException, ObjectStreamException;

    /**
     * Suspende el procesador de la petición y de la {@link ServletResponse} asociada.
     * 
     * <p>Despues de que este método sea llamado, el ciclo de vida de la petición será extendida
     * más allá del retorno al contenedor desde el método 
     * {@link Servlet#service(ServletRequest, ServletResponse)}  y las llamadas
     * {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)}. Si una  
     * petición es suspendida, entonces el contenedor no confirmará la respuesta asociada 
     * cuando la llamada a la cadena de filtros y/o al service del servlet vuelve al 
     * contenedor. En su lugar, el contenedor esperará hasta que se llame a  
     * {@link ServletRequest#resume()} o expire el timeout, y entonces la petición se
     * volverá a despachar via la cadena de filtros al servlet.
     * </p>
     * 
     * <p>El objeto {@link ServletResponse} es deshabilitado por esta llamada,  
     * de modo que todas las llamada para modificar o generar una respuesta son
     * ignoradas, hasta que una petición sea devuelta. Con la excepción de que 
     * los objetos devueltos por {@link ServletResponse#getNonStopOutputStream()} y  
     * {@link ServletResponse#getNonStopWriter} permanecen activos durante todo 
     * el ciclo de vida de la petición.</p>
     * 
     * <p>Si ya hay una petición suspendida, cualquiera llamada subsiguiente a
     * suspender establecerá el timeout al mínimo del timeout previo y del nuevo
     * timeout pasado</p>
     *
     * <p>Suspender sólo debe ser llamado por un thread que esté dentro de la pila de
     * llamadas al servicio de {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)}
     * y/o {@link Servlet#service(ServletRequest, ServletResponse)}</p>    
     * 
     * @see {@link #resume()}
     * 
     * @param timeoutMs El tiempo en milisegundos a esperar antes de devolver esta petición.
     * 
     * @exception IllegalStateException Si el thread ejecutor no está dentro de la pila de llamadas
     * de {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)}
     * y/o {@link Servlet#service(ServletRequest, ServletResponse)}
     */
    void suspend(long timeoutMs);

    /**
     * Reanudar una request suspendida.
     * 
     * <p>Este método puede ser llamado por cualquier thread al que se le haya pasado una referencia a
     * a la petición para pedir que la request sea reenviada</p>
     * 
     * <p>Si resume se llama antes de que una petición suspendida sea devuelta al contenedor 
     * (ie el thread que llamó a {@link #suspend(long)} está todavía dentro de la cadena de filtros
     * y/o en el método service del servlet), entonces la reanudación no se lleva a cabo hasta que
     * la llamada a la filter chain y/o al método service vuelvan al contenedor. En este caso, hasta  
     * que vuelva la llamada a service, tanto {@link #isSuspended()} como {@link isResumed()}
     * devuelvan true.</p>
     * 
     * <p>Si se llama a resume en una petición no suspendida, se aplicará la reanudación a  
     * cualquier llamada subsiguiente a suspend. En este caso, {@link #isResumed()} será true 
     * y {@link #isSuspended()} false. </p>
     * 
     * <p>Si resume es llamado en una petición después de que el ciclo de vida de una petición
     * se complete, los resultados quedan indefinidos</p>
     * 
     * <p>Multiples llamadas a resume son ignoradas</p>
     * 
     * @see {@link #response()}
     * 
     */
    void resume();

    /**
     * @return true después de que {@link #suspend(long)} haya sido llamado y antes de que 
     * la petición haya sido reanudada o expirada.
     */
    boolean isSuspended();

    /**
     * @return true despues de que {@link #resume()} haya sido llamado y antes de cualquier llamada
     * subsiguiente a suspend
     */
    boolean isResumed();

    /**
     * @return true después de que una petición haya sido devuelta como el resultado de un resume o 
     * timeout
     */
    boolean isRetry();

    /**
     * @return true después de que una petición haya sido devuelta como resultado de timeout
     */
    boolean isTimeout();
}

 

ServletResponse

La interfaz ServletResponse existente es extendida con métodos para enviar contenidos asíncronamente y para obtener streams sin final:

public interface ServletResponse
{
    // Se han omitido los métodos de response ya existentes

    /**
     * Establece el cuerpo del contenido de la respuesta (response)
     * 
     * <p>Establece el cuerpo de la response para ser enviada como un Object.  
     * El Object será convertido una instancia de bytes {@link ResponseContentConverter} 
     * suministrada por el contenedor o la aplicación y configurado en el descriptor
     * de despliegue o las annotations del servlet.</p>
     * 
     * <p>El conversor establecerá la longitud del contenido, el content type y el 
     * content encoding de la response.</p>
     * 
     * <p>Una llamada a {@link #flushBuffer()} bloqueará hasta que todo el contenido sea
     * convertido a bytes y sea enviado. Si no se llama a {@link #flushBuffer()}, el 
     * contenedor tiene que generar y escribir el contenido asíncronamente después de 
     * de que la llamada a la cadena de filtros y al método service vuelva al contenedor.
     * 
     * @exception IllegalStateException si cualquiera de los métodos {@link #getWriter()}, 
     *     {@link #getNonStopWriter(),  {@link #getOutputStream()} o
     *     {@link #getNonStopOutputStream()} han sido llamados en esta response
     *
     */
    void setContent(Object content);

    /**
     * Devuelve un {@link PrintWriter} adecuado para escribir texto en la response.
     * A diferencia del writer retornado por {@link #getWriter()}, este writer no
     * está afectado por una llamada a {@link ServletRequest#suspend(long)} y
     * puede ser usado por cualquier thread durante el ciclo de vida de la
     * request. </p>
     * 
     * <p>No está garantizado que el writer sea thread safe, y si varios threads 
     * usan el stream, deben usar alguna forma de exclusión mutua.</p>
     *
     * <p>Calling flush() on the Writer commits the response.</p>
     *
     * @return a {@link ServletOutputStream} para escribir datos binarios   
     *
     * @exception IllegalStateException si cualquiera de los métodos {@link #getWriter()}, 
     *     {@link #getNonStopWriter() o {@link #setContent(Object)} han sido llamados
     *     en esta response
     *     
     * @exception UnsupportedEncodingException
     *                  si el char encoding devuelto por <code>getCharacterEncoding</code>
     *                  no se puede usar
     *
     *
     */
    PrintWriter getNonStopWriter();

    /**
     *
     * Devuelve un {@link ServletOutputStream} para escribir datos binarios en la  
     * response. El contenedor no codifica los datos binarios. A diferencia del
     * stream devuelto por {@link #getOutputStream()}, el stream devuelto por este 
     * método no está afectado por una llamada a {@link ServletRequest#suspend(long)}
     * y puede ser usado por cualquier thread durante el ciclo de vida de la 
     * request. </p>
     * 
     * <p>No está garantizado que el stream sea thread safe, y si varios threads están
     * el stream, deben usar alguna forma de exclusión mutua.</p>
     *
     * <p>Calling flush() on the ServletOutputStream commits the response.</p>
     *
     *
     * @return a {@link ServletOutputStream} for writing binary data   
     *
     * @exception IllegalStateException if any of the {@link #getWriter()}, 
     *     {@link #getNonStopWriter() or {@link #setContent(Object)} methods
     *     have been called on this response
     *
     * @exception IOException   if an input or output exception occurred
     *
     */
    ServletOutputStream getNonStopOutputStream();
}

RequestContentConverter

Se suministra una nueva interfaz RequestContentConverter para las aplicaciones para convertir el contenido de una request en Objects. Los conversores del contenedor no son requeridos para usar esta interfaz y se deben usar mecanismos alternativos (y más eficientes) para convertir contenidos.

/**
 * Un conversor para el contenido de la petición.
 * 
 * <p>Las implementaciones de esta interficie son responsables de
 * convertir los bytes recibidos de una request a un Object que
 * estará disponible via el método.</p>
 * 
 * <p>El contenedor creará tantas instancias de un conversors como
 * sean necesarias y una instancia será asociada con una request
 * por una llamada a {@link #init(ServletRequest)}. El contenedor
 * llamará a {@link #convert(byte[])} a medida que reciba contenido
 * hasta que se devuelva un objeto o se lance una excepción.</p>
 * 
 * <p>Para implementar esta interfaz no se requieren los conversores
 * provistos por el contenedor.</p>
 * 
 */
public interface RequestContentConverter
{

    /**
     * Inicializa este converter para la request.
     * @param request La request que contiene el contenido. Se pueden
     * inspeccionar las cabeceras de esta petición, pero no se puede
     * consumir contenido de la petición (ejemplo {@link ServletRequest#getInputStream()}). 
     * @throws IllegalStateException Si este converter no puede convertir la petición.
     */
    void init(ServletRequest request);

    /**
     * Convierte unos bytes del contenido.
     * 
     * <p>Este método se llama cuando un contenedor recibe algunos bytes de
     * del contenido. El contenido puede que no esté completo y será necesario
     * más llamadas a convert para tratar todo el contenido.
     * No se hará una llamada subsiguiente hasta después de que la llamada
     * actual haya retornado.</p>
     * 
     * @param chunk Contenido parcial o completo como una array de bytes.
     * @return El objeto convertido o null si se requiere más contenido.
     * @throws IOException Si no se puede analizar el contenido.
     */
    Object convert(byte[] chunk) throws IOException;

}

ResponseContentConverter

Se suministra una nueva interfaz ResponseContentConverter para las aplicaciones para convertir el contenido de una request en Objects. Los conversores del contenedor no son requeridos para usar esta interfaz y se deben usar mecanismos alternativos (y más eficientes) para convertir el contenido.

/* ------------------------------------------------------------ */
/**
 * Un converter para el contenido de Response.
 * 
 * <p>Las implementaciones de esta interficie son responsables de convertir
 * los Objects pasados en el método {@link ServletResponse#setContent(Object)}  
 * a bytes para ser enviados como contenido de la respuesta.</p>
 * 
 * <p>El contenedor creará tantas instancias como necesite y una instancia
 * se asociará con una response por una llamada a {@link #init(ServletResponse)}.
 * El contenedor llamará a {@link #convert(byte[], int, int)} hasta que se
 * devuelva un -1.</p>
 * 
 * <p>Los conversores del contenedor no son requeridos para implementar esta intarficie.
 *
 */
public interface ResponseContentConverter
{
    /**
     * Asocia este converter con una response.
     * <p>Se pueden establecer cabeceras de la response para indicar el tamaño
     * del contenido, el tipo y la codificación.</p>	
     * 
     * @param response La respuesta.
     * @param content El contenido a convertir.
     * @throws IllegalStateException Si el contenido no se puede  the content cannot be converted
     * by this converter.
     */
    void init(HttpServletResponse response, Object content);

    /** 
     * Convierte el contenido a bytes. Los bytes convertidos se escriben en
     * el buffer pasado comenzando en el offset dado y sin exceder la 
     * longitud dada. Devuelve el número real de bytes escritos.
     * Los bytes escritos puede representar todo el contenido o una parte
     * de él. Pueden ser necesarias más llamadas a converte para convertir
     * el contenido completo.
     * 
     * <p>El contenedor no llamará a converte hasta que una llamada previa
     * a convert termine. El contenedor puede retrasar la llamada a convert
     * hasta que esté listo para enviar más contenido al cliente.</p>
     * 
     * @param buffer El array de bytes dónde se debe escribir el contenido.
     * @param offset El offset dentro del array dónde se debe escribir el
     *               contenido.
     * @param length El número máximo de bytes que pueden ser escritos.
     * @return El número de bytes escritos o -1 para indicar que se ha 
     * escrito todo el contenido.
     * @throws IOException Si el contenido no se puede convertir.
     */
    int convert(byte[] buffer,int offset, int length) throws IOException;
}

Descriptor de Despliegue

Conversores del Contenido de la Request

Los conversores de contenidos de peticiones deben definirse en el descriptor de despliegue y tiene que contener los siguientes elementos:

url-pattern
Patrón de URL que debe seguir el conversor que sigue las mismas convenciones que los mapeos de servlets y filtros. Un elemento converter puede tener múltiples elementos url-pattern.
servlet-name
El nombre de un servlet al que el converter se aplica. Un converter puede tener múltiples elementos servlet-name.
mime-type
El mime-type (content-type excluyendo parámetros y encoding) al que el converter se aplica.
content-class
La clase java del Object que creará el conversor para contener el contenido. Mediante el método ServletRequest.getContent() se podrá obtener una instancia de esta clase.
asynchronous
Si el elementoasynchronous está presente en un converter, el conversor se ejecutará (asíncronamente si es posible) antes del dispatch inicial. Si no está presente, el conversor sólo se ejecutará cuando se llame a ServletRequest.getContent().
provided
Si el elemento provided está presente, la aplicación web usará la clase conversora del contenedor que encaje con el mime-type y content-class. Si el contenedor no te da un conversor adecuado con el matching, y no hay elemento converter-class, entonces la aplicación no se podrá desplegar.
converter-class
Clase que suministra la aplicación que implementa la interficie RequestContentConverter y que se usará para convertir el contenido de la petición. Si sólo hay un elemento converter-class será éste el que se use. Si tanto el elemento converter-class como el provided están presentes, entonces el provided tiene preferencia si el contenedor te da una conversor.

Descriptor de conversor de request de ejemplo:

<request-converter>
  <url-pattern>/comet/*</url-pattern>

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

Este descriptor define el conversor que se aplica a las peticiones de la forma /comet/* que tiene una cabecera de content-type con un mime-type text/json. El contenido se convierte a un objeto java Map asíncronamente antes de que la request sea dirigida. Si el contenedor suministra un conversor que encaje con la petición, éste será usado, si no se usará la clase que da la aplicación.

Converters de Response Content

Los conversores del contenido de la request se pueden definir en el descriptor de despliegue y pueden contener los siguientes elementos:

url-pattern
El patrón URL con el que se aplica el conversor sigue las mismas convenciones que los mappings del servlet y del filter. Un elemento converter puede tener varios elementos url-pattern.
servlet-name
El nombre del servlet al que se aplica el conversor. Un converter puede tener varios elementos servlet-name.
content-class
La clase java a la que el converter se puede aplicar. Es el tipo o supertipo del Object pasado a ServletResponse.setContent(Object).
content-type
El tipo de contenido y la codificación opcional al que el contenido se convertirá.
provided
Si el elemtno provided está presente, la aplicación web usará una clase conversora suministrada por el contenedor que encaje con el mime-type y content-class. Si el contenedor no da un conversor correcto, y no hay un elemento converter-class, entonces la aplicación web no se podrá desplegar.
converter-class
Una clase dada por la aplicación que implementa la interficie RequestContentConverter que se usará para convertir el contenido. Si sólo hay presente un elemento converter-class, entonces se usará éste. Si tanto converter-class como provided están presenet, el elemento provided tiene preferencia si el contenedor da un converter.

Ejemplo de un descriptor de converter para la response:

<response-converter>
  <url-pattern>/comet/*</url-pattern>

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

Este converter se aplica a todas las respuestas generadas por recursos en las URIs que encajen con /comet/*, usando ServletResponse.setContent(Object) para pasar un objeto del tipo org.w3c.dom.Document. El contenido pasado se convertirá a XML codificado en utf-8, usando un conversor dado por el contenedor. Si tal conversor no está disponible, no se podrá desplegar la aplicación web.

Annotations

Annotations apropiadas se pueden definir para filtros y servlets para crear efectivamente entradas de request y converter con la misma semántica de una descriptor de despliegue.

Otros cambios

Esta propuesta requerirá algunos cambios menores o mejoras de las características existentes de la especificación de servlet:

Filter mapping

Actualmente un Filter se puede mapear en un dispatcher de REQUEST, FORWARD, INCLUDE o ERROR. El dispatcher de REQUEST se deben aplicar tanto a las peticiones de initial y retried. Se deben definir tipos de dispatcher adicionales como INITIAL-REQUEST y RETRY-REQUEST para permitir mapear las peticiones initial y retried.

Ejemplos

Ejemplo de fichero JSP para upload

Este ejemplo muestra como una entrada asíncrona puede ser tratada dentro de una JSP.

Este ejemplo usa JSP para generar un formulario multipart para subir ficheros. Se usa la API de ServletRequest.getContent() para permitir al contenedor subir grandes ficheros asíncronamente y guardarlos en u fichero temporal. La petición de upload se despacha a la JSP sólo cuando el fichero enter se ha subido. Entonces la JSP simplemente renombra el fichero temporal en una localización permanente antes de hacerlo disponible vía enlace.

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=“.”