Servelts y JSP

Especificar Cabeceras de Respuesta HTTP


  1. Introducción
  2. Cabeceras de Respuesta más comunes y sus Significados.
  3. Ejemplo: Recarga Automática de Páginas con Content Changes

1. Introducción

Una respuesta desde un servidor Web normalmente consiste en una línea de estado, una o más cabeceras de respuesta, una línea en blanco, y el documento. Seleccionar las cabeceras de respuesta normalmente va mano con mano con la selección de códigos de estado en la línea de estado. Por ejemplo, muchos de los códigos de estado "document moved" tienen una cabecera Location de acompañamiento, y un 401 (Unauthorized) debe incluir una cabecera WWW-Authenticate.

Sin embargo, especificar las cabeceras puede jugar un rol muy útil cuando se selecciona códigos de estado no usuales. Las cabeceras de respuesta se pueden usar para especificar cookies, para suministrar la fecha de modificación (para el caché), para instruir al navegador sobre la recarga de la página después de un intervalo designado, para decir cuanto tiempo va a estar el fichero usando conexiones persisitentes, y otras muchas tareas.

La forma más general de especificar cabeceras es mediante el método setHeader de HttpServletResponse, que toma dos strings: el nombre de la cabecera y el valor de ésta. Igual que la selección de los códigos de estado, esto debe hacerse antes de envíar cualquier documento.

Hay también dos métodos especializados para seleccionar cabeceras que contienen fechas (setDateHeader) y enteros (setIntHeader). La primera nos evita el problema de tener que traducir una fecha Java en milisegundos (como al devuelta por los métodos System.currentTimeMillis o getTime aplicados a un objeto Date) en stirng GMT. El segundo nos ahora la incoveniencia menor de convertir un int a un String.

En el caso de una cabecera cuyo nombre ya exista, podemos añadir una nueva cabecera en vez de seleccionarla de nuevo. Usamos addHeader, addDateHeader, y addIntHeader para esto. Si realmente nos importa si una cabecera específica se ha seleccionado, podemos usar containsHeader para comprobarlo.

Finalmente, HttpServletResponse también suministra unos métodos de conveniencia para especificar cabeceras comunes:

2. Cabeceras de Respuesta más Comunes y sus Significados

Cabecera Interpretación/Propósito
Allow ¿Qué métodos de petición (GET, POST, etc.) soporta el servidor?
Content-Encoding ¿Qué método se utilizó para codificar el documento? Necesitamos decodificarlo para obtener el tipo especificado por la cabecera Content-Type.
Content-Length ¿Cuántos bytes han sido enviados? Esta información es sólo necesaria si el navegador está usando conexiones persistentes. Si queremos aprovecharnos de esto cuando el navegador lo soporte, nuestro servlet debería escribir el documento en un ByteArrayOutputStream, preguntar su tamaño cuando se haya terminado, ponerlo en el campo Content-Length, luego enviar el contenido mediante byteArrayStream.writeTo(response.getOutputStream()).
Content-Type ¿Cuál es el tipo MIME del siguiente documento? Por defecto en los servlets es text/plain, pero normalmente especifican explícitamente text/html. Seleccionar esta cabecera es tan común que hay un método especial en HttpServletResponse para el: setContentType.
Date ¿Cuál es la hora actual (en GMT)? Usamos el método setDateHeader para especificar esta cabecera.
Expires ¿En qué momento debería considerarse el documento como caducado y no se pondrá más en el caché?
Last-Modified ¿Cúando se modificó el documento por última vez? El cliente puede suministrar una fecha mediante la cabecera de petición If-Modified-Since. Esta es tratada como un GET condicional, donde sólo se devuelven documentos si la fecha Last-Modified es posterior que la fecha especificada. De otra forma se devuelve una línea de estado 304 (Not Modified). De nuevo se usa el método setDateHeader para especificar esta cabecera.
Location ¿Dónde debería ir cliente para obtener el documento? Se selecciona indirectamente con un código de estado 302, mediante el método sendRedirect de HttpServletResponse.
Refresh ¿Cúando (en milisegundos) debería perdir el navegador una página actualizada? En lugar de recargar la página actual, podemos especificar otra página a cargar mediante setHeader("Refresh", "5; URL=http://host/path"). Nota: esto se selecciona comunmente mediante <META HTTP-EQUIV="Refresh" CONTENT="5; URL=http://host/path"> en la sección HEAD de la página HTML, mejor que una cabecera explícita desde el servidor. Esto es porque la recarga o el reenvio automático es algo deseado por los autores de HTML que no tienen accesos a CGI o servlets. Pero esta cabecera significa "Recarga esta página o ve a URL especificada en n segundos". No significa "recarga esta página o ve la URL especificada cada n segundos". Por eso tenemos que enviar una cabecera Refresh cada vez. Nota: esta cabecera no forma parte oficial del HTTP 1.1, pero es una extensión soportada por Netspace e Internet Explorer
Server ¿Qué servidor soy? Los servlets normalmente no usan esto; lo hace el propio servidor.
Set-Cookie Especifica una Cookie asociada con la página. Los servlets no deberían usar response.setHeader("Set-Cookie", ...), pero en su lugar usan el método de propósito especial addCookie de HttpServletResponse.
WWW-Authenticate ¿Qué tipo de autorización y domino debería suministrar el cliente en su cabecera Authorization? Esta cabecera es necesaria en respuestas que tienen una línea de estado 401 (Unauthorized). Por ejemplo response.setHeader("WWW-Authenticate", "BASIC realm=\"executives\"").

Para más detalles sobre cabeceras HTTP, puedes ver las especificaciones en http://www.w3.org/Protocols/.

3. Ejemplo: Recarga Automática de Páginas como Cambio de Contenido

Aquí tenemos un ejemplo que nos permite pedir una lista de grandes números primos. Como esto podría tardar algún tiempo para números muy largos (por ejemplo 150 dígitos), el servelt devuelve los resultados hasta donde haya llegado, pero sigue calculando, usando un thread de baja prioridad para que no degrade el rendimiento del servidor Web. Si los cálculos no se han completado, instruye al navegador para que pida una nueva página en unos pocos segundos enviando una cabecera Refresh.

Además de ilustrar el valor de las cabeceras de respuesta HTTP, este ejemplo muestra otras dos capacidades de los servlets. Primero, muestra que el mismo servlet puede manejar múltiples conexiones simultáneas, cada una con su propio thread. Por eso, mientras un thread está finalizando los cálculos para un cliente, otro cliente puede conectarse y todavía ver resultados parciales.

Segundo, este ejemplo muestra lo fácil que es para los servlets mantener el estado entre llamadas, algo que es engorroso de implementar en CGI tradicional y sus alternativas. Sólo se crea un ejemplar del Servlet, y cada petición siemplemente resulta en un nuevo thread que llama al método service del servlet (que a su vez llama a doGet o doPost). Por eso los datos compartidos sólo tienen que ser situados en una variable normal de ejemplar (campo) del servlet. Así el servlet puede acceder la cálculo de salida apropiado cuando el navegador recarga la página y puede mantener una lista de los resultados de las N solicitudes más recientes, retornándolas inmediatamente si una nueva solicitud especifica los mismo parámetros que otra reciente. Por supuesto, que se aplican las mismas reglas para sincronizar el acceso multi-thread a datos compartidos.

Los servlets también pueden almacenar datos persistentes en el objeto ServletContext que está disponible a través del método getServletContext. ServletContext tiene métodos setAttribute y getAttribute que nos permiten almacenar datos arbitrarios asociados con claves especificadas. La diferencia entre almacenar los datos en variables de ejemplar y almacenarlos en el ServletContext es que éste es compartido por todos los servlets en el motor servlet (o en la aplicación Web, si nuestro servidor soporta dicha capacidad).

3.1 PrimeNumbers.java (Descargar el código fuente)

Nota: También usa ServletUtilities.java, mostrado anteriormente, PrimeList.java para crear un vector de números primos en un thread de segundo plano, y Primes.java para generar grandes números aleatorios del tipo BigInteger y chequear si son primos:
package hall;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;

public class PrimeNumbers extends HttpServlet {
  private static Vector primeListVector = new Vector();
  private static int maxPrimeLists = 30;
  
  public void doGet(HttpServletRequest request,
                    HttpServletResponse response)
      throws ServletException, IOException {
    int numPrimes =
      ServletUtilities.getIntParameter(request, "numPrimes", 50);
    int numDigits =
      ServletUtilities.getIntParameter(request, "numDigits", 120);
    PrimeList primeList =
      findPrimeList(primeListVector, numPrimes, numDigits);
    if (primeList == null) {
      primeList = new PrimeList(numPrimes, numDigits, true);
      synchronized(primeListVector) {
        if (primeListVector.size() >= maxPrimeLists)
          primeListVector.removeElementAt(0);
        primeListVector.addElement(primeList);
      }
    }
    Vector currentPrimes = primeList.getPrimes();
    int numCurrentPrimes = currentPrimes.size();
    int numPrimesRemaining = (numPrimes - numCurrentPrimes);
    boolean isLastResult = (numPrimesRemaining == 0);
    if (!isLastResult) {
      response.setHeader("Refresh", "5");
    }
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    String title = "Some " + numDigits + "-Digit Prime Numbers";
    out.println(ServletUtilities.headWithTitle(title) +
                "<BODY BGCOLOR=\"#FDF5E6\">\n" +
                "<H2 ALIGN=CENTER>" + title + "</H2>\n" +
                "<H3>Primes found with " + numDigits +
                " or more digits: " + numCurrentPrimes + ".</H3>");
    if (isLastResult)
      out.println("<B>Done searching.</B>");
    else
      out.println("<B>Still looking for " + numPrimesRemaining +
                  " more<BLINK>...</BLINK></B>");
    out.println("<OL>");
    for(int i=0; i<numCurrentPrimes; i++) {
      out.println("  <LI>" + currentPrimes.elementAt(i));
    }
    out.println("</OL>");
    out.println("</BODY></HTML>");
  }

  public void doPost(HttpServletRequest request,
                     HttpServletResponse response)
      throws ServletException, IOException {
    doGet(request, response);
  }

  // See if there is an existing ongoing or completed calculation with
  // the same number of primes and length of prime. If so, return
  // those results instead of starting a new background thread. Keep
  // this list small so that the Web server doesn't use too much memory.
  // Synchronize access to the list since there may be multiple simultaneous
  // requests.
  
  private PrimeList findPrimeList(Vector primeListVector,
                                  int numPrimes,
                                  int numDigits) {
    synchronized(primeListVector) {
      for(int i=0; i<primeListVector.size(); i++) {
        PrimeList primes = (PrimeList)primeListVector.elementAt(i);
        if ((numPrimes == primes.numPrimes()) &&
            (numDigits == primes.numDigits()))
          return(primes);
      }
      return(null);
    }
  }
}

3.3 PrimeNumbers.html

Nota: pulsa con el botón derecho sobre el enlace al código fuente.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
  <TITLE>Finding Large Prime Numbers</TITLE>
</HEAD>

<BODY BGCOLOR="#FDF5E6">
<H2 ALIGN="CENTER">Finding Large Prime Numbers</H2>
<BR><BR>
<CENTER>
<FORM ACTION="/servlet/hall.PrimeNumbers">
  <B>Number of primes to calculate:</B>
  <INPUT TYPE="TEXT" NAME="numPrimes" VALUE=25 SIZE=4><BR>
  <B>Number of digits:</B>
  <INPUT TYPE="TEXT" NAME="numDigits" VALUE=150 SIZE=3><BR>
  <INPUT TYPE="SUBMIT" VALUE="Start Calculating">
</FORM>
</CENTER>

</BODY>
</HTML>

3.4 Inicio

3.5 Resultados Intermedios

3.6 Resultado Final


Ozito