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.
Arquitectura de servlets
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:
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:
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:
doGet():
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
Para 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)
doPost():
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
Para 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.
La forma "plana" de llamar a un servlet es acceder a una carpeta "virtual" llamada servlet dentro de nuestra aplicación web. Esta carpeta no existe, y es interpretada automáticamente por el servidor web correspondiente como que tiene que cargar un servlet, cuya clase (incluyendo paquetes y subpaquetes) proporcionamos tras esta carpeta. Por ejemplo:
http://localhost:8080/miapp/servlet/paquete1.subpaquete1.MiServlet
Otra opción es añadir unas líneas de configuración en el fichero descriptor WEB-INF/web.xml, para indicar que al servlet, en lugar de llamarlo a través del alias servlet, le vamos a llamar con otra URL alternativa (virtual). Por ejemplo, si añadimos estas líneas para el servlet anterior:
<servlet> <servlet-name>nombre</servlet-name> <servlet-class>paquete1.subpaquete1.MiServlet</servlet-class> </servlet>
Lo que hacemos es asignar al servlet un nombre (en este caso nombre). De esta forma podremos llamar al servlet, además de con la URL anterior, con esta otra:
http://localhost:8080/miapp/setvlet/nombre
evitando así tener que recordar el nombre de la clase y los paquetes.
Una tercera alternativa es añadir otro bloque más en WEB-INF/web.xml para indicar que al servlet (al que previamente le deberemos haber dado un nombre con una etiqueta servlet-name), lo queremos llamar con una URL alternativa:
<servlet-mapping> <servlet-name>nombre</servlet-name> <url-pattern>/ejemploservlet</url-pattern> </servlet-mapping>
Lo que hacemos es mapear el nombre del servlet con una URL (en este caso /ejemploservlet). Así, podemos llamar al servlet, además de con las URL anteriores, con:
http://localhost:8080/miapp/ejemploservlet
evitando así acceder al alias servlet, que muchos servidores dejan deshabilitado por cuestiones de seguridad.
a) 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.
package ejemplos; 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.
b) Servlet que genera contenido HTML
Este otro ejemplo escribe código HTML para mostrar una página web.
package ejemplos; 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:
Indicar que el contenido que se va a enviar es HTML (mediante el método setContentType() de HttpServletResponse):
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.
Sin embargo, lo normal no es que el servlet genere línea a línea la página HTML, sino dejarla definida en un fichero aparte, y hacer que el servlet redirija a esta página (HTML, o JSP):
package ejemplos; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class ClaseServletHTML2 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); response.sendRedirect("miPagina.jsp"); } }