Fco. Javier García Castellano


Programación de Servlets
 


Introducción

En esta parte del tutorial se verán en que partes se descompone un servlet, el ciclo de vida de un servlet y como pasar parámetros a un servlet desde un formulario HTML.

1 El Servlet HolaMundo

En la sección de compilación y ejecución de servlets creamos un servlet llamado HolaMundo.java cuyo código fuente era el siguiente:

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

public class HolaMundo extends HttpServlet {

  public void init(ServletConfig conf)
    throws ServletException {
    super.init(conf);
  }

  public void service(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException  {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();

    out.println("<html>");
    out.println("<body>");
    out.println("<h1>Hola Mundo</h1>");
    out.println("</body>");
    out.println("</html>");
  }
}

Comentemos un poco nuestro este primer servlet. Lo primero que nos aparece son las clases que utiliza (las bibliotecas o includes de C):

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

Vemos que utiliza las clases correspondientes a servlets (javax.servlet.*), las clases correspondientes a servlets que utilizan el protocolo HTTP (javax.servlet.http.*). El protocolo HTTP es el protocolo estándar en la web (o WWW - World Wide Web). Por eso, cuando en un navegador abrimos una dirección, la mayoría empieza por http://. También utiliza clases de entrada/salida (java.io.* - io viene input/output) para poder escribir en pantalla.

Lo segundo que vemos es el nombre de la clase (class HolaMundo) que es pública (public), en el sentido de que cualquiera puede usarla sin ningún tipo de restricción) y que hereda (extends) de la clase HttpServlet.

public class HolaMundo extends HttpServlet {

Algo que debemos saber es que toda clase, para que se considere un servlet, debe implementar el interfaz javax.servlet.Servlet. Para conseguirlo lo más sencillo es hacer que nuestra clase herede o bien de la clase javax.servletGenericServlet o javax.servlet.http.HttpServlet. Con la primera obtendremos un servlet independiente del protocolo, mientras que con la segunda tendremos un servlet HTTP. En este tutorial, sólo vamos a ver servlets que funcionen con el protocolo HTTP así que, por tanto, siempre heredarán de HttpServlet. Resumiendo, sólo cambiará el nombre de la clase para cada servlet que hagamos.

El siguiente trozo de código que aparece (y que tenemos que implementar siempre) es la redefinición del método init. El servidor invoca a este método cuando se crea el servlet y en este método podemos hacer todas las operaciones de inicialización que queramos. Como en este servlet no nos hace falta inicialización ninguna, lo único que hacemos es llamar al método init por defecto (al de la superclase).

  public void init(ServletConfig conf)
    throws ServletException {
    super.init(conf);
  }

Podemos observar que el método init es público, no devuelve ningún tipo (void), que puede lanzar una excepción (ServletException) y que tiene un parámetro (ServletConfig conf). De estos dos últimos aspectos (excepción y parámetro) no nos tenemos que preocupar pues es el servidor quien ejecuta el método init. En el peor de los casos, tendríamos que lanzar la excepción (si sabemos hacerlo), si por algún motivo el método init que nosotros implementemos falle (por ejemplo, que no se pueda conectar a la base de datos y evitamos mostrar un mensaje de error)

Lo siguiente que hacemos redefinir el método service, cuando el servidor web recibe una petición para un servlet llama al método public void service(HttpServletRequest req, HttpServletResponse res) con dos parámetros: el primero, de la clase HttpServletRequest, representa la petición del cliente y el segundo, de la clase HttpServletResponse, representa la respuesta del servidor (del servlet, más concretamente).

public void service(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException  {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();

    out.println("<html>");
    out.println("<body>");
    out.println("<h1>Hola Mundo</h1>");
    out.println("</body>");
    out.println("</html>");
  }

Como en este primer ejemplo no necesitamos ninguna información del cliente, no usaremos para nada el parámetro HttpServletRequest, más adelante veremos como hacerlo. De la clase HttpServletResponse usamos dos métodos:

  • setContextType(String str) para establecer el tipo de respuesta que vamos a dar. Para indicar que se trata de una página web, como haremos siempre, usamos el tipo "text/html".
          res.setContentType("text/html");
    
  • PrinterWriter getWriter(void) con el que obtendremos una clase PrinterWriter en donde iremos escribiendo los datos que queremos que el cliente reciba (como podría ser el stdout de C o el cout de C++):
    	
          PrintWriter out = res.getWriter();
    

Una vez que hemos establecido el tipo de respuesta (text/html)y tenemos el flujo de salida (variable out) sólo nos queda utilizar el método println de la clase PrinterWriter para ir escribiendo en dicho flujo de salida la página HTML que queremos que visualice el cliente.

2 Servlet GetPost

Vamos a ver un ejemplo, un poco más complicado. Si llamamos un servlet desde un formulario HTML, podremos hacerlo de dos formas: GET y POST. Con la primera los parámetros del formulario están incluidos la url que se utiliza para invocar el servlet y en el segundo caso los parámetros se almacenan en un buffer especial del servidor.

Para procesar el primer tipo de peticiones (GET) está el método doGet mientras que para el segundo tenemos el método doPost . La implementación por defecto del método service es capaz de determinar el tipo de petición HTTP que en un momento dado recibe el servlet. Una vez identificada llama o al método doGet o al doPost según el caso. Como, en la mayoría de los casos, seremos nosotros quien programe el formulario que llame al servlet, sabremos que tipo de llamada se hará, por lo que podemos optar por redefinir uno sólo de los métodos. En el caso de que no lo supiéramos se deben implementar los métodos doGet y doPost.

Un servlet que tiene diferente respuesta en función de la llamada que se le hace es el ejemplo GetPost.java cuyo código fuente era el siguiente:

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

public class GetPost extends HttpServlet {

  public void init(ServletConfig conf)
    throws ServletException {
    super.init(conf);
  }

  public void doGet(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException
  {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();
    out.println("<html>");
    out.println("<body>");
    out.println("<h1>Hola Mundo (llamada GET)</h1>");
    out.println("</body>");
    out.println("</html>");
  }

  public void doPost(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException
  {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();
    out.println("<html>");
    out.println("<body>");
    out.println("<h1>Hola Mundo (llamada POST)</h1>");
    out.println("</body>");
    out.println("</html>");
  }
}

Para llamar al servlet con un tipo de llamada get, podemos usar el ejemplo get.html. Que podemos colocar en el directorio C:\java\tomcat\webapps\tutorial\ y cuyo código es:

<html>
<body>
   <h1>Método GET</h1>
   <form method="GET" action="/tutorial/servlet/GetPost">
      <input type="submit">
   </form>
</body>
</html>

Para llamar al servlet con un tipo de llamada post, podemos usar el ejemplo post.html. Que podemos colocar en el directorio C:\java\tomcat\webapps\tutorial\ y cuyo código es:

<html>
<body>
   <h1>Método POST</h1>
   <form method="POST" action="/tutorial/servlet/GetPost">
      <input type="submit">
   </form>
</body>
</html>

3 Ciclo de vida de un Servlet

Como hemos visto antes, cuando se crea un servlet, el servidor llama al método init y cada vez que un cliente acceda al servlet el servidor llamará al método service que se encargará de redirigir la llamada doGet o a doPost. Esto nos quiere decir que cuando se llama por primera vez a un servlet se ejecutara primero init y después service, pero ... żY la segunda vez y sucesivas también se llama a init o sólo a service?.

Normalmente, el servidor crea el servlet (llama, por tanto, al método init) y lo mantiene funcionando, si ha pasado un tiempo suficiente (y que puede ir desde unos segundos a nunca) sin que el servlet se llame lo deja de ejecutar. Es decir, un servlet se empieza a ejecutar con la primera llamada y, normalmente, se seguirá ejecutando.

De esta forma, vamos a crear un servlet que cuente el número de visitas que recibe, para ello nos bastará crear una variable contador que inicializaremos en el método init y que incrementaremos en doPost/doGet. Por lo que, el contador se inicializará cuando se llame por primera vez al servlet y se irá incrementando en llamadas sucesivas. Este ejemplo, lo llamaremos Contador.java y su código fuente era el siguiente:

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

public class Contador extends HttpServlet {
  //variable contador
  int contador;

  public void init(ServletConfig conf)
    throws ServletException {
    super.init(conf);

    //inicializamos la variable contador
    contador = 1;
  }

  public void doGet(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();
    int tmp = contador;

    //incrementamos la variable contador
    contador++;

    out.println("<html>");
    out.println("<body>");
    out.println("<h1>Numero de peticiones " + tmp +"</h1>");
    out.println("</body>");
    out.println("</html>");
  }

  public void doPost(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {
    doGet(req, res);
  }
}

NOTA: Observa que un mismo servlet puede ser llamado por más de un cliente a la vez. En este caso, el servidor crea una hebra del servlet por cada petición y esas dos hebras accederán concurrentemente a los mismos datos (la variable contador). Como la lectura e incremento de contador no es una operación atómica, se podría utilizar la primitiva de sincronización syncronized para que se realice de forma atómica:

    .....
    PrintWriter out = res.getWriter();
    int tmp;

    synchronized(this) {
      //leemos el contador
      tmp = contador;
      //incrementamos la variable contador
      contador++;
    }

    out.println("<html>");
    .....

4 Parámetros desde un formulario HTML

Normalmente los servlets tendrán parámetros o fuentes de información que le darán su aspecto dinámico. Es decir, para generar una simple página HTML no nos complicamos tanto la vida, se escribe la página HTML y se ha terminado. Las fuentes de información de las que un servlet hace uso, pueden ser varias: el propio servlet, el servidor web, ficheros o bases de datos a los que pueda acceder o parámetros que le pase el cliente. De todas estas fuentes, nos interesan los accesos a bases de datos que veremos más adelante y los pa;rámetros que nos pasa el cliente mediante formularios HTML.

Cuando pasamos parámetros a través de un formulario, en los Servlets a través de la clase ServletRequest, disponemos de los siguientes métodos para su tratamiento:

  • String getParameter(String nombre): Nos devuelve el valor del parámetro cuyo nombre le indicamos. Si la variable no existiera o no tuviese ningún valor, devolvería null
  • Enumerate getParameterNames(): Nos devuelve una enumeración de los nombres de los parámetros que recibe el servlet.
  • Enumerate getParameterValues(String): Nos devuelve los valores que toma un parámetro dado, esto es útil para listas de selección múltiple donde un parámetro tiene más de un valor.

Veamos un ejemplo de un pequeño formulario que tenga distintos tipos de parámetros, se los envíe a nuestro servlet y éste los muestre por pantalla (aún no sabemos guardarlos en la base de datos). El formulario los llamaremos formulario.html. Que podemos colocar en el directorio C:\java\tomcat\webapps\tutorial\ y cuyo código es:

<html>
<title>Formulario de ejemplo</title>
<body>
   <h1>Formulario</h1>
   <form method="POST" action="/tutorial/servlet/Parametros">
     Nombre: <INPUT TYPE="TEXT" NAME="nombre"><BR>
     Primer Apellido:<INPUT TYPE="TEXT" NAME="apellido1"><BR>
     Segundo Apellido:<INPUT TYPE="TEXT" NAME="apellido2"><BR>
     <hr>
     Correo electronico: <INPUT TYPE="TEXT" NAME="email"><BR>
     Clave: <INPUT TYPE="PASSWORD" NAME="clave"><BR>
     <hr>
     Comentario: <TEXTAREA NAME="comenta" ROWS=3 COLS=40>
                 </TEXTAREA><BR>
     <hr>
     Sexo:<BR> 
	  <INPUT TYPE="RADIO" NAME="sexo" VALUE="hombre">Hombre<BR>
          <INPUT TYPE="RADIO" NAME="sexo" VALUE="mujer">Mujer<BR>

     Areas de interés:<br>
  	  <SELECT NAME="intereses" MULTIPLE>
	 	<OPTION>Informatica</OPTION>
	  	<OPTION>Derecho</OPTION>
		<OPTION>Matematicas</OPTION>
		<OPTION>Fisica</OPTION>
		<OPTION>Musica</OPTION>
	  </SELECT>

      <center><input type="submit" value="Enviar"></center>
   </form>
</body>
</html>

Veamos primero un servlet que conociendo de antemano los distintos parámetros que va a recibir los vaya mostrando en una página HTML. El servlet lo llamaremos Parametros.java y su código es:

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

public class Parametros extends HttpServlet {

  public void init(ServletConfig conf)
    throws ServletException {
    super.init(conf);
  }

  public void doGet(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();

    out.println("<html>");
    out.println("<body>");
    out.println("<h1>Parámetros del servlet desde un formulario HTML</h1>");
    out.println("<br> Nombre:"+req.getParameter("nombre") );
    out.println("<br> Primer apellido:"+req.getParameter("apellido1") );
    out.println("<br> Segundo apellido:"+req.getParameter("apellido2") );
    out.println("<br> Correo electrónico:"+req.getParameter("email") );
    out.println("<br> Contraseña:"+req.getParameter("clave") );
    out.println("<br> Comentario:"+req.getParameter("comenta") );
    out.println("<br> Sexo:"+req.getParameter("sexo") );
    out.println("<br> Areas de interés:"+req.getParameter("intereses") );
    out.println("</body>");
    out.println("</html>");
  }

  public void doPost(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {
    doGet(req, res);
  }
}

Si en la lista de de selección múltiple escogemos más de un valor en la implementación actual sólo mostraría la primera elección, si quisiéramos mostrar todos los valores deberíamos de usar getParameterValues("intereses") e ir recorriendo y mostrando cada uno de los valores seleccionados del parámetro intereses.

Otra posible implementación del servlet Parametros.java sería uno que mostrase los parámetros y sus valores sin tener que conocerlos previamente. El código sería:

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


public class Parametros extends HttpServlet
{
  public void init(ServletConfig conf)
        throws ServletException
  {
        super.init(conf);
  }

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

    response.setContentType("text/html");
    PrintWriter out = response.getWriter();

    //Escribimos el principio de la página HTML
    out.println("<html>");
    out.println("<body>");
    out.println("<h1>Parámetros del servlet desde un formulario HTML</h1>");

    //cogemos los nombres de los parametros
    Enumeration paramNames = request.getParameterNames();

    //vamos mostrando los parámetros en unwhile
    while(paramNames.hasMoreElements()) {
      //cogemos el siguiente parámetro
      String paramName = (String)paramNames.nextElement();

      //Mostramos el nombre del parámetro 	
      out.print(paramName + " = ");

      //Cogemos los valores del parámetro
      String[] paramValues = request.getParameterValues(paramName);

      //Miramos si tiene más de un valor 
      if (paramValues.length == 1) {
 	//Si tiene un sólo valor, miramos si está vacío o no
        String paramValue = paramValues[0];
        if (paramValue.length() == 0)
          out.println("<i>Sin valor</i><br>");
        else
          out.println(paramValue + "<br>");
      }
      else {
	//Si tiene más de un sólo valor, los mostramos
        for(int i=0; i<paramValues.length; i++) 
          out.println(paramValues[i] + ", ");
	out.println("<br>");
      }
    }//end while

    //Escribimos el final de la página HTML
    out.println("</body>");
    out.println("</html>");
  }

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