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:
| 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/.
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).
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);
}
}
}
<!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>