Un servlet es un programa Java que se ejecuta en un servidor Web y
construye o sirve páginas web. De esta forma se pueden construir páginas
dinámicas, basadas en diferentes fuentes variables: datos proporcionados
por el usuario, fuentes de información variable (páginas de noticias,
por ejemplo), o programas que extraigan información de bases de datos.
Comparado con un CGI, un servlet es más sencillo de utilizar, más eficiente (se arranca un hilo por cada petición y no un proceso entero), más potente y portable. Con los servlets podremos, entre otras cosas, procesar, sincronizar y coordinar múltiples peticiones de clientes, reenviar peticiones a otros servlets o a otros servidores, etc.
Normalmente al hablar de servlets se habla de JSP y viceversa, puesto que ambos conceptos están muy interrelacionados. Para trabajar con ellos se necesitan tener presentes algunos recursos:
Para encontrar información sobre servlets y JSP, son de utilidad las siguientes direcciones:
Dentro del paquete javax.servlet tenemos toda la infraestructura para poder trabajar con servlets. El elemento central es la interfaz Servlet, que define los métodos para cualquier servlet. La clase GenericServlet es una clase abstracta que implementa dicha interfaz para un servlet genérico, independiente del protocolo. Para definir un servlet que se utilice vía web, se tiene la clase HttpServlet dentro del subpaquete javax.servlet.http. Esta clase hereda de GenericServlet, y también es una clase abstracta, de la que heredaremos para construir los servlets para nuestras aplicaciones web.
Cuando un servlet acepta una petición de un cliente, se reciben dos objetos:
Figura 1. Arquitectura del paquete servlet
Todos los servlets tienen el mismo ciclo de vida:
1. Inicialización
En cuanto a la inicialización de un servlet, se tiene una por defecto en el método init().
public void init() throws ServletException { ... } public void init(ServletConfig conf) throws ServletException { super.init(conf); ... }
El primer método se utiliza si el servlet no necesita parámetros de configuración externos. El segundo se emplea para tomar dichos parámetros del objeto ServletConfig que se le pasa. La llamada a super.init(...) al principio del método es MUY importante, porque el servlet utiliza esta configuración en otras zonas.
Si queremos definir nuestra propia inicialización, deberemos sobreescribir alguno de estos métodos. Si ocurre algún error al inicializar y el servlet no es capaz de atender peticiones, debemos lanzar una excepción de tipo UnavailableException.
Podemos utilizar la inicialización para establecer una conexión con una base de datos (si trabajamos con base de datos), abrir ficheros, o cualquier tarea que se necesite hacer una sola vez antes de que el servlet comience a funcionar.
2. Procesamiento de peticiones
Una vez inicializado, cada petición de usuario lanza un hilo que llama al método service() del servlet.
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
Este método obtiene el tipo de petición que se ha realizado (GET, POST, PUT, DELETE). Dependiendo del tipo de petición que se tenga, se llama luego a uno de los métodos:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOExceptionPara peticiones de tipo GET (aquellas realizadas al escribir una dirección en un navegador, pinchar un enlace o rellenar un formulario que no tenga METHOD=POST)
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOExceptionPara peticiones POST (aquellas realizadas al rellenar un formulario que tenga METHOD=POST)
3. Destrucción
El método destroy() de los servlets se emplea para eliminar un servlet y sus recursos asociados.
public void destroy() throws ServletException
Aquí debe deshacerse cualquier elemento que se construyó en la inicialización (cerrar conexiones con bases de datos, cerrar ficheros, etc).
El servidor llama a destroy() cuando todas las llamadas de servicios del servlet han concluido, o cuando haya pasado un determinado número de segundos (lo que ocurra primero). Si esperamos que el servlet haga tareas que requieran mucho tiempo, tenemos que asegurarnos de que dichas tareas se completarán. Podemos hacer lo siguiente:
La plantilla común para implementar un servlet es:
import javax.servlet.*; import javax.servlet.http.*; public class ClaseServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // ... codigo para una peticion GET } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // ... codigo para una peticion POST } }
El servlet hereda de la clase HttpServlet. Normalmente se deben sobreescribir los métodos doGet(), doPost() o ambos, colocando el código que queremos que se ejecute cuando se reciba una petición GET o POST, respectivamente. Conviene definir los dos para distinguir ambas peticiones. En caso de que queramos hacer lo mismo para GET o POST, definimos el código en uno de ellos, y hacemos que el otro lo llame.
Aparte de estos métodos, podemos utilizar otros de los que hemos visto: init() (para inicializaciones), doXXX() (para tratar otros tipos de peticiones (PUT, DELETE, etc)), destroy() (para finalizar el servlet), etc, así como nuestros propios métodos internos de la clase.
Para instalar un servlet en una aplicación web, se coloca la clase del servlet dentro del directorio WEB-INF/classes de la aplicación (respetando también la estructura de paquetes, creando tantos subdirectorios como sea necesario). Veremos ahora las formas que tenemos de invocar a ese servlet.
1. Llamada directa de servlets
Sin configurar el fichero descriptor de despliegue de la aplicación para que acepte el servlet, podemos llamarlo con:
http://localhost:8080/<dir>/servlet/<nombre-servlet>
donde <dir> se sustituye por el directorio donde tengamos la aplicación Web, y <nombre-servlet> es el nombre completo del servlet, incluyendo paquetes (separados por '.'). Por ejemplo, si tenemos el servlet paquete1.subpaquete1.MiServlet, dentro de la aplicación miapp, lo llamamos con:
http://localhost:8080/miapp/servlet/paquete1.subpaquete1.MiServlet
Notar que se pone el subdirectorio servlet, aunque la aplicación no contiene dicho subdirectorio. Esto es porque, mediante este subdirectorio "virtual" (inexistente), el servidor Web interpreta que tiene que cargar una clase del directorio classes, y que dicha clase es un servlet.
Si hemos colocado el servlet en el directorio classes de root, lo llamamos con:
http://localhost:8080/servlet/<nombre-servlet>
2. Mapeado de servlets y páginas JSP en el fichero descriptor
Otra opción para llamar al servlet es incluir en el fichero descriptor de la aplicación donde lo hemos colocado (web.xml en Tomcat) la información necesaria para que lo encuentre. Dicha información consiste en introducir una marca <servlet> para cada servlet que se quiera llamar de esta forma:
<servlet> <servlet-name>nombre</servlet-name> <servlet-class>ClaseServlet</servlet-class> </servlet>
Donde <servlet-name> es un nombre identificativo y arbitrario del servlet, y <servlet-class> es la clase del servlet (incluyendo paquetes y subpaquetes, separados por '.'). Con esto, al servlet ClaseServlet lo podemos llamar de dos formas:
http://localhost:8080/<dir>/servlet/ClaseServlet http://localhost:8080/<dir>/servlet/nombre
siendo <dir> el directorio de la aplicación Web. De forma similar se podría mapear una página JSP, sustituyendo la etiqueta <servlet-class> por la etiqueta <jsp-file>:
<servlet> <servlet-name>nombre2</servlet-name> <jsp-file>/mipagina.jsp</servlet-class> </servlet>
con lo que podemos invocar la página de dos formas también:
http://localhost:8080/<dir>/mipagina.jsp http://localhost:8080/<dir>/nombre2
3. Asignar URLs a servlets o páginas JSP
El uso de la ruta .../servlet/... para llamar a los servlets puede ser útil durante la depuración, pero luego podemos querer invocar al servlet utilizando una URL alternativa. Esto se consigue mediante las etiquetas <servlet-mapping>:
<servlet-mapping> <servlet-name>nombre</servlet-name> <url-pattern>/ejemploservlet</url-pattern> </servlet-mapping>
En la subetiqueta <servlet-name> se pone el nombre del servlet al que se quiere asignar la URL (será uno de los nombres dados en alguna etiqueta <servlet> previa), y en <url-pattern> colocamos la URL que le asignamos al servlet, relativa a la raíz de la aplicación web, comenzando con '/'.
Notar que primero se colocan todas las etiquetas <servlet>, y luego las <servlet-mapping> que se requieran.
Así, con lo anterior, podremos llamar al servlet identificado con nombre de otra forma más:
http://localhost:8080/<dir>/ejemploservlet
También podemos asignar en <url-pattern> expresiones como:
<servlet-mapping> <servlet-name>nombre</servlet-name> <url-pattern>/ejemploservlet/*</url-pattern> </servlet-mapping>
o como:
<servlet-mapping> <servlet-name>nombre</servlet-name> <url-pattern>/ejemploservlet/*.jsp</url-pattern> </servlet-mapping>
Con el primero, cualquier URL del directorio de nuestra aplicación Web que comience con /ejemploservlet/ se redirigirá y llamará al servlet identificado con nombre. Por ejemplo, las direcciones:
http://localhost:8080/<dir>/ejemploservlet/unapagina.html http://localhost:8080/<dir>/ejemploservlet/misjsp/maspaginas.jsp
acabarían llamando al servlet nombre.
Con el segundo, cualquier llamada a cualquier página JSP del directorio /ejemploservlet/ de nuestra aplicación se redirigiría al servlet nombre. Podemos hacer que distintas URLs llamen a un mismo servlet, sin más que añadir varios grupos <servlet-mapping>, uno por cada patrón de URL diferente, y todos con el mismo <servlet-name>.
Nos puede interesar que a los servlets no se les llame a través del alias /servlet/*. Para ello, podemos aplicar este procedimiento para mapear rutas con el alias /servlet/... para que se redirijan a un servlet que muestre un mensaje de error
Este mismo procedimiento se aplica, sin cambio alguno, si en lugar de un servlet queremos tratar una página JSP.
NOTA: si en Tomcat no nos reconoce el alias servlet, emitirá un error 404 diciendo que no encuentra el recurso. En ese caso, iremos al fichero conf/web.xml de Tomcat, y buscaremos el <servlet-mapping> correspondiente a un servlet con nombre invoker. Probablemente ese mapeo aparecerá comentado. Basta con quitar el comentario y reiniciar Tomcat.
El hecho de asignar un nombre a un servlet o página JSP mediante la etiqueta <servlet> y sus subetiquetas nos permite identificarlo con ese nombre, y también poderle asignar parámetros de inicio. Para asignar parámetros se colocan etiquetas <init-param> dentro de la etiqueta <servlet> del servlet o página JSP al que le queremos asignar parámetros. Dichas etiquetas tienen como subetiquetas un <param-name> (con el nombre del parámetro) y un <param-value> (con el valor del parámetro). Por ejemplo:
<servlet> <servlet-name>nombre</servlet-name> <servlet-class>ClaseServlet</servlet-class> <init-param> <param-name>param1</param-name> <param-value>valor1</param-value> </init-param> <init-param> <param-name>param2</param-name> <param-value>valor2</param-value> </init-param> </servlet>
Para obtener luego los parámetros desde el servlet se utiliza getServletConfig().getInitParameter(nombre) donde nombre es el valor <param-name> del parámetro que se busca, y devuelve el valor (elemento <param-value> asociado), que es de tipo String siempre. Para obtener estos valores desde páginas JSP se emplean otros métodos.
Los parámetros de inicio sólo se aplican cuando accedemos al servlet o página JSP a través del nombre asignado en <servlet-name>, o a través de la URL asociada en un <servlet-mapping>.
1. Servlet que genera texto plano
El siguiente ejemplo de servlet muestra una página con un mensaje de saludo: "Este es un servlet de prueba". Lo cargamos mediante petición GET.
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class ClaseServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); out.println ("Este es un servlet de prueba"); } }
Se obtiene un Writer para poder enviar datos al usuario. Simplemente se le envía la cadena que se mostrará en la página generada.
2. Servlet que genera una página HTML
Este otro ejemplo escribe código HTML para mostrar una página web.
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class ClaseServletHTML extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println ("<!DOCTYPE HTML PUBLIC \""+ "-//W3C//DTD HTML 4.0 " + "Transitional//EN\">"); out.println ("<HTML>"); out.println ("<BODY>"); out.println ("<h1>Titulo</h1>"); out.println ("<br>Servlet que genera HTML"); out.println ("</BODY>"); out.println ("</HTML>"); } }
Para generar una página HTML con un servlet debemos seguir dos pasos:
response.setContentType("text/html");Esta línea es una cabecera de respuesta, que veremos más adelante cómo utilizar. Hay que ponerla antes de obtener el Writer.
3. Servlet que utiliza parámetros de inicialización
Este otro ejemplo utiliza dos parámetros de inicialización externos:
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class ClaseServletInit extends HttpServlet { // Mensaje que se va a mostrar en la pagina String mensaje = ""; // Numero de veces que se va a repetir el mensaje int contador = 1; // Metodo de inicializacion public void init(ServletConfig conf) throws ServletException { super.init(conf); // MUY IMPORTANTE mensaje = conf.getInitParameter("mensaje"); if (mensaje == null) mensaje = "Hola"; try { contador = Integer.parseInt( conf.getInitParameter("contador")); } catch (NumberFormatException e) { contador = 1; } } // Metodo para procesar una peticion GET public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println ("<!DOCTYPE HTML PUBLIC \""+ "-//W3C//DTD HTML 4.0 " + "Transitional//EN\">"); out.println ("<HTML>"); out.println ("<BODY>"); for (int i = 0; i < contador; i++) { out.println (mensaje); out.println ("<BR>"); } out.println ("</BODY>"); out.println ("</HTML>"); } }
Para probar el ejemplo, tendríamos dos posibilidades
Se proporciona un fichero WAR con los tres servlets colocados en la subcarpeta WEB-INF/classes, y un fichero web.xml con el siguiente contenido:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.// DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd"> <web-app> <servlet> <servlet-name>ejemplo1_1</servlet-name> <servlet-class>ClaseServlet</servlet-class> </servlet> <servlet> <servlet-name>ejemplo1_2</servlet-name> <servlet-class>ClaseServletHTML</servlet-class> </servlet> <servlet> <servlet-name>ejemplo1_3</servlet-name> <servlet-class>ClaseServletInit</servlet-class> <init-param> <param-name> mensaje </param-name> <param-value> Mensaje de prueba </param-value> </init-param> <init-param> <param-name>contador</param-name> <param-value>10</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>ejemplo1_1</servlet-name> <url-pattern>/ejemploservlet</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ejemplo1_2</servlet-name> <url-pattern>/ejemploservletHTML</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ejemplo1_3</servlet-name> <url-pattern>/ejemploservletInit</url-pattern> </servlet-mapping> </web-app>Vemos que se mapean los tres servlets:
Para probar los servlets, copiamos el fichero WAR en el directorio webapps de Tomcat. Para llamar a los servlets, podemos hacerlo de tres formas:
http://localhost:8080/ejemplobasico/servlet/ClaseServlet http://localhost:8080/ejemplobasico/servlet/ClaseServletHTML http://localhost:8080/ejemplobasico/servlet/ClaseServletInit
http://localhost:8080/ejemplobasico/servlet/ejemplo1_1 http://localhost:8080/ejemplobasico/servlet/ejemplo1_2 http://localhost:8080/ejemplobasico/servlet/ejemplo1_3
http://localhost:8080/ejemplobasico/ejemploservlet http://localhost:8080/ejemplobasico/ejemploservletHTML http://localhost:8080/ejemplobasico/ejemploservletInit
NOTA: el servlet ClaseServletInit no tomará los parámetros si lo llamamos del primer modo, debido a que se asignan esos parámetros al mapeo.
Un servlet maneja peticiones de los clientes a través de su método service.
Con él se pueden manejar peticiones HTTP (entre otras), reenviando las
peticiones a los métodos apropiados que las manejan. Por ejemplo, una
petición GET puede redirigirse a un método doGet. Veremos ahora
los elementos principales que intervienen en una interacción vía HTTP.
Como hemos visto anteriormente, los objetos ServletRequest se emplean para obtener información sobre la petición de los clientes. Más en concreto, el subtipo HttpServletRequest se utiliza en las peticiones HTTP. Proporciona acceso a los datos de las cabeceras HTTP, cookies, parámetros pasados por el usuario, etc, sin tener que parsear nosotros a mano los datos de formulario de la petición.
La clase dispone de muchos métodos, pero destacamos los siguientes:
Enumeration getParameterNames() String getParameter (String nombre) String[] getParameterValues (String nombre)Con getParameterNames() se obtiene una lista con los nombres de los parámetros enviados por el cliente. Con getParameter() se obtiene el valor del parámetro de nombre nombre. Si un parámetro tiene varios valores (por ejemplo, si tenemos un array de cuadros de texto con el mismo nombre en un formulario), se pueden obtener todos separados con getParameterValues(). Los nombres de los parámetros normalmente sí distinguen mayúsculas de minúsculas, deberemos tener cuidado al indicarlos.
String getQueryString()que devuelve todos los parámetros de la petición en una cadena, que deberemos parsear nosotros como nos convenga.
BufferedReader getReader() ServletInputStream getInputStream()Con getReader() se obtiene un BufferedReader para peticiones donde esperemos recibir texto. Si esperamos recibir datos binarios, se debe emplear getInputStream(). No es aconsejable utilizar estos métodos a no ser que se vaya a leer un fichero que envíe el cliente, por ejemplo, ya que el utilizarlos implica no poder utilizar los getParameter() y similares.
String getMethod() String getRequestURI() String getProtocol()Con getMethod() obtenemos el comando HTTP solicitado (GET, POST, PUT, etc), con getRequestURI() obtenemos la parte de la URL de petición que está detrás del host y el puerto, pero antes de los datos del formulario. Con getProtocol() obtenemos el protocolo empleado (HTTP/1.1, HTTP/1.0, etc).
Los objetos ServletResponse se emplean para enviar el resultado de procesar una petición a un cliente. El subtipo HttpServletResponse se utiliza en las peticiones HTTP. Proporciona acceso al canal de salida por donde enviar la respuesta al cliente.
La clase dispone de muchos métodos, pero destacamos:
Writer getWriter() ServletOutputStream getOutputStream() void setContentType(String tipo) void sendRedirect(String url)
Con getWriter() se obtiene un Writer para enviar texto al cliente. Si queremos enviar datos binarios, se debe emplear getOutputStream().
Si queremos especificar información de cabecera, debemos establecerla ANTES de obtener el Writer o el ServletOutputStream. Hemos visto en algún ejemplo el método setContentType() para indicar el tipo de contenido. Veremos las cabeceras con más detenimiento más adelante.
También hay otros métodos útiles, como sendRedirect(), que redirige la aplicación a otra página, cuya URL se indica como parámetro.
Veamos un ejemplo: supongamos que tenemos este formulario:
<html> <body> <form action="/ejemploform/servlet/ServletForm"> Valor 1: <input type="text" name="texto1"> <br> Valor2: <select name="lista"> <option name="lista" value="Opcion 1">Opcion 1</option> <option name="lista" value="Opcion 2">Opcion 2</option> <option name="lista" value="Opcion 3">Opcion 3</option> </select> <br> Valores 3: <br> <input type="text" name="texto2"> <input type="text" name="texto2"> <input type="text" name="texto2"> <input type="submit" value="Enviar"> </form> </body> </html>
Al validarlo se llama al servlet ServletForm, que muestra una página HTML con los valores introducidos en los parámetros del formulario:
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class ServletForm extends HttpServlet { // Metodo para GET public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); // Mostramos los datos del formulario out.println ("<HTML>"); out.println ("<BODY>"); out.println ("<H1>Datos del formulario</H1>"); out.println ("<BR>"); String valor1 = request.getParameter("texto1"); String valor2 = request.getParameter("lista"); String[] valor3 = request.getParameterValues("texto2"); out.println ("Valor 1:" + valor1); out.println ("<BR>"); out.println ("Valor 2:" + valor2); out.println ("<BR>"); out.println ("Valor 3:"); out.println ("<BR>"); if (valor3 != null) for (int i = 0; i < valor3.length; i++) { out.println (valor3[i]); out.println ("<BR>"); } out.println ("</BODY>"); out.println ("</HTML>"); } // Metodo para POST public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
Observad cómo utilizamos los métodos getParameter(), getParameterValues(), etc, para obtener información ya procesada de la petición. En un CGI tendríamos que procesar la cadena de la petición para extraer estos parámetros a mano. También utilizamos la respuesta para generar alguna cabecera, generar el flujo de salida y volcar el contenido de la respuesta.
Aquí tenéis el WAR con el ejemplo comprimido. Copiadlo en el directorio webapps y probad el ejemplo con:
http://localhost:8080/ejemploform/index.html