Skip to content

3. Introducción a los servlets Java y las páginas JSP

En este capítulo se presentan diversos ejemplos de servlets y páginas JSP. Estos han sido probados con el servidor Tomcat, el cual opera en el puerto 8080. Al seguir los enlaces de la página de inicio, se puede acceder a ejemplos de servlets y páginas JSP. La mayoría de los ejemplos que se muestran a continuación provienen de los ejemplos de Tomcat. Para probarlos, basta con iniciar Tomcat, acceder a la página URL http://localhost:8080 con un navegador y seguir el enlace de los servlets.

3.1. Servlets de Java

3.1.1. Enviar contenido HTML a un cliente web

Analizamos el ejemplo «Hello World» anterior. El servlet es el siguiente:


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

public class HelloWorld extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello World!</h1>");
        out.println("</body>");
        out.println("</html>");
    }
}

Al ejecutar este servlet, se obtiene la siguiente visualización:

Image

Cabe destacar los siguientes puntos:

  • es necesario importar clases especiales para los servlets:

import javax.servlet.*;
import javax.servlet.http.*;

La biblioteca javax.servlet no siempre viene incluida de forma predeterminada con el JDK. En ese caso, se puede descargar directamente del sitio web de Sun.

  • Un servlet deriva de la clase HttpServlet

public class HelloWorld extends HttpServlet {
  • Una solicitud GET enviada al servlet es procesada por el método doGet

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  • Del mismo modo, una solicitud POST enviada al servlet es procesada por el método doPost

    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  • El objeto HttpServletRequest request es el que nos da acceso a la solicitud realizada por el cliente web. La respuesta del servlet se enviará a través del objeto HttpServletResponse response
  • El objeto response nos permite establecer los encabezados HTTP que se enviarán al cliente. Por ejemplo, el encabezado Content-type: text/html se establece aquí mediante:
        response.setContentType("text/html");
  • Para enviar la respuesta al cliente, el servlet utiliza un flujo de salida que le proporciona el objeto response:
        PrintWriter out = response.getWriter();
  • Una vez obtenido este flujo de salida, se escribe en él el código HTML y, por lo tanto, se envía al cliente:
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello World!</h1>");
        out.println("</body>");
        out.println("</html>");

3.1.2. Recuperar los parámetros enviados por un cliente web

El siguiente ejemplo muestra cómo un servlet puede recuperar los parámetros enviados por el cliente web. Un formulario de entrada:

Image

La respuesta enviada por el servlet:

Image

El código fuente del servlet es el siguiente:


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

public class myRequestParamExample extends HttpServlet {

    String title="Récupération des paramètres d'un formulaire";

    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>" + title + "</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");
        out.println("<h3>" + title + "</h3>");
        String firstName = request.getParameter("firstname");
        String lastName = request.getParameter("lastname");
        if (firstName != null || lastName != null) {
            out.println("firstname= " + firstName + "<br>");
            out.println("lastname= " + lastName);
        } else {
            out.println("pas de paramètres");
        }
        out.println("<P>");
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
        out.println("firstname= <input type=text size=20 name=firstname>");
        out.println("<br>");
        out.println("lastname= <input type=text size=20 name=lastname>");
        out.println("<br>");
        out.println("<input type=submit>");
        out.println("</form>");
        out.println("</body>");
        out.println("</html>");
    }

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

}

Cabe destacar las siguientes novedades con respecto al ejemplo anterior:

  • Los parámetros enviados por el navegador se recuperan de la siguiente manera:

        String firstName = request.getParameter("firstname");
        String lastName = request.getParameter("lastname");

El método request.getParameter("nomParamètre") devuelve el puntero null, si el parámetro nomParamètre no forma parte de los parámetros enviados por el cliente web.

  • El formulario especifica que el navegador debe enviar los parámetros mediante el método POST
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
  • Los parámetros recibidos serán procesados por el método doPost del servlet. En este caso, dicho método simplemente llama al método doGet. De este modo, este servlet procesa los valores del formulario, independientemente de si se envían mediante un GET o un POST.

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

3.1.3. Recuperar los encabezados HTTP enviados por un cliente web

El siguiente servlet muestra cómo recuperar los encabezados HTTP enviados por el cliente web:

Image

El código fuente del servlet es el siguiente:


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

public class RequestHeaderExample extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        Enumeration e = request.getHeaderNames();
        while (e.hasMoreElements()) {
            String name = (String)e.nextElement();
            String value = request.getHeader(name);
            out.println(name + " = " + value);
        }
    }
}

Puntos a tener en cuenta:

  • El objeto request y su método getHeaderNames nos permiten acceder a los encabezados HTTP enviados por el navegador en forma de una enumeración:
        Enumeration e = request.getHeaderNames();
  • El método request.getHeader("encabezado") permite obtener un encabezado HTTP específico. El ejemplo anterior nos muestra algunos de ellos. Hay que recordar que los encabezados que se presentan aquí son enviados por el navegador. El servidor también tiene sus propios encabezados HTTP, que a veces repiten los del navegador. Los encabezados HTTP enviados por el navegador tienen como objetivo informar al servidor sobre las capacidades del navegador.
Encabezado
Significado
User-Agent
identidad del navegador
Accept
Los formatos MIME que admite el navegador. Así, image/gif significa que el navegador puede procesar imágenes en formato GIF
Host
en formato hote:port. Indica con qué máquina y en qué puerto quiere conectarse el navegador.
Accept-Encoding
formato de codificación aceptado por el navegador para los documentos enviados por el servidor. Así, si un servidor tiene un documento en formato normal sin comprimir y otro en formato comprimido gzip, y el navegador ha indicado que puede procesar el formato gzip, entonces el servidor podrá enviar el documento en formato gzip para ahorrar ancho de banda.
Accept-language
Idiomas aceptados por el navegador. Si un servidor cuenta con el mismo documento en varios idiomas, enviará aquel cuyo idioma sea aceptado por el navegador.
Referer
el URL que fue solicitado por el navegador
Connection
el modo de conexión solicitado por el navegador. Keep-alive significa que el servidor no debe cortar la conexión después de haber enviado la página solicitada al navegador. Si este último descubre que la página recibida contiene enlaces a imágenes, por ejemplo, podrá realizar nuevas solicitudes al servidor para obtenerlas sin necesidad de establecer una nueva conexión. Será el navegador el que tomará la iniciativa de cerrar la conexión una vez que haya recibido todos los elementos de la página.

3.1.4. Obtener información del entorno

El siguiente servlet muestra cómo acceder a la información del entorno de ejecución del servlet. Parte de esta información es enviada en forma de encabezados HTTP por el navegador y, por lo tanto, puede recuperarse mediante el método anterior.

Image

El código del servlet es el siguiente:


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

public class RequestInfo extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>Request Information Example</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h3>Request Information Example</h3>");
        out.println("Method: " + request.getMethod());
        out.println("Request URI: " + request.getRequestURI());
        out.println("Protocol: " + request.getProtocol());
        out.println("PathInfo: " + request.getPathInfo());
        out.println("Remote Address: " + request.getRemoteAddr());
        out.println("</body>");
        out.println("</html>");
    }

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

La información aquí se obtiene mediante diversos métodos:

        out.println("Method: " + request.getMethod());
        out.println("Request URI: " + request.getRequestURI());
        out.println("Protocol: " + request.getProtocol());
        out.println("PathInfo: " + request.getPathInfo());
        out.println("Remote Address: " + request.getRemoteAddr());

A continuación se presenta una lista de algunos de los métodos disponibles y su significado:

método
significado
getServerName()
el nombre del servidor web
getServerPort()
el puerto de trabajo del servidor web
getMethod()
el método GET o POST que utiliza el navegador para realizar su solicitud
getRemoteHost()
el nombre de la máquina cliente desde la cual el navegador realizó su solicitud
getRemoteAddr()
la dirección IP de esa misma máquina
getContentType()
el tipo de contenido enviado por el navegador (encabezado HTTP Content-type)
getContentLength()
el número de caracteres enviados por el navegador (encabezado HTTP Content-length)
getProtocol()
la versión del protocolo HTTP solicitada por el navegador
getRequestURI()
el URI solicitado por el navegador. Corresponde a la parte del URL que se encuentra después de la identificación hote:port en http://hote:port/URI

3.1.5. Crear un servlet con JBuilder, implementarlo con Tomcat

A continuación describimos cómo crear y ejecutar un servlet de Java. Utilizaremos dos herramientas: JBuilder para compilar el servlet y Tomcat para ejecutarlo. Tomcat por sí solo podría ser suficiente. Sin embargo, ofrece capacidades de depuración limitadas. Retomamos el ejemplo desarrollado anteriormente que muestra los parámetros recibidos por el servidor. El servlet envía primero el siguiente formulario de entrada:

Image

La respuesta enviada por el servlet:

Image

El código fuente del servlet es el siguiente:


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

public class myRequestParamExample extends HttpServlet {

    String title="Récupération des paramètres d'un formulaire";

    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>" + title + "</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");
        out.println("<h3>" + title + "</h3>");
        String firstName = request.getParameter("firstname");
        String lastName = request.getParameter("lastname");
        if (firstName != null || lastName != null) {
            out.println("firstname= " + firstName + "<br>");
            out.println("lastname= " + lastName);
        } else {
            out.println("pas de paramètres");
        }
        out.println("<P>");
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
        out.println("firstname= <input type=text size=20 name=firstname>");
        out.println("<br>");
        out.println("lastname= <input type=text size=20 name=lastname>");
        out.println("<br>");
        out.println("<input type=submit>");
        out.println("</form>");
        out.println("</body>");
        out.println("</html>");
    }

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

}
  • Crea un proyecto myRequestParamExample con JBuilder e incluye en él el programa myRequestParamExample.java anterior.
  • Al compilar, puede surgir el siguiente problema: es posible que su JBuilder no cuente con la biblioteca javax.servlet necesaria para compilar los servlets. En ese caso, debe configurar JBuilder para que utilice bibliotecas de clases adicionales. El procedimiento se describe en los anexos de este documento para JBuilder 7. Lo resumimos parcialmente aquí:
  • active la opción Tools/Configure JDKs o (Options/Configurar JDK)

Image

En la sección «Configuración» de JDK mencionada anteriormente, normalmente aparece en el campo Name la versión 1.3.1 de JDK. Si tienes una versión más reciente de JDK, usa el botón Change para indicar el directorio de instalación de esta última. En el ejemplo anterior, se ha seleccionado el directorio E:\Program Files\jdk14, donde estaba instalado un JDK 1.4. A partir de ahora, JBuilder utilizará este JDK para sus compilaciones y ejecuciones. En la sección (Clase, Fuente, Documentación) se encuentra la lista de todas las bibliotecas de clases que JBuilder explorará; en este caso, las clases de JDK 1.4. Las clases de este último no son suficientes para desarrollar aplicaciones web en Java. Para agregar otras bibliotecas de clases, se utiliza el botón Add y se seleccionan los archivos .jar adicionales que se deseen utilizar. Los archivos .jar son bibliotecas de clases. Tomcat 4.x incluye todas las bibliotecas de clases necesarias para el desarrollo web. Se encuentran en <tomcat>\common\lib, donde <tomcat> es el directorio de instalación de Tomcat:

Image

Con el botón Add, agregaremos estas bibliotecas, una por una, a la lista de bibliotecas exploradas por JBuilder:

Image

A partir de ahora, podemos compilar programas Java que cumplan con el estándar J2EE, en particular los servlets Java. JBuilder solo sirve para la compilación; la ejecución la realiza posteriormente Tomcat.

  • Ahora puedes compilar el programa myRequestParamExample.java y generar el servlet myRequestParamExample.class. ¿Dónde colocar este servlet? Si no se ha modificado la configuración inicial de Tomcat, los archivos de los servlets deben colocarse en <tomcat>\webapps\examples\WEB-INF\classes (Tomcat 4.x).
  • Verifica que Tomcat esté en ejecución y, desde un navegador, accede a la página http://localhost:8080/examples/servlet/myRequestParamExample:

Image

3.1.6. Ejemplos

Para los siguientes ejemplos, hemos utilizado el método descrito anteriormente:

  • compilación del código fuente XX.java del servlet con JBuilder
  • implementación del servlet XX.class en <tomcat>\webapps\examples\WEB-INF\classes
  • Una vez iniciado Tomcat, acceda con un navegador a URL http://localhost:8080/examples/servlet/XX

3.1.6.1. Generación dinámica de formularios - 1

Tomamos como ejemplo la generación de un formulario que solo tiene un control: una lista. El contenido de esta lista se construye dinámicamente con valores tomados de una matriz. En la práctica, estos valores suelen obtenerse de una base de datos. El formulario es el siguiente:

Image

Si en el ejemplo anterior se ejecuta Envoyer, se obtiene la siguiente respuesta:

Image

Cabe destacar que el código URL que genera la respuesta es el mismo que el que muestra el formulario. En este caso, tenemos un servlet que procesa por sí mismo la respuesta al formulario que él mismo envió. Este es un caso común. El código HTML del formulario es el siguiente:

<html>
    <head><title>Génération de formulaire</title></head>
    <body>
    <h3>Choississez un nombre</h3><hr>
    <form method="POST">
      <select name="cmbValeurs" size="1">
        <option>zéro</option>
        <option>un</option>
        <option>deux</option>
        <option>trois</option>
        <option>quatre</option>
        <option>cinq</option>
        <option>six</option>
        <option>sept</option>
        <option>huit</option>
        <option>neuf</option>
      </select>
      <input type="submit" value="Envoyer">
    </form>
    </body>
</html>

Cabe señalar que los valores enviados por el formulario se transmiten mediante el método POST. El código HTML de la respuesta:

<html>
    <head><title>Voici ma réponse</title></head>
  <body>
      Vous avez choisi le nombre<h2>neuf</h2>
  </body>
</html>

El código del servlet que genera este formulario y esta respuesta es el siguiente:

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

public class gener1 extends HttpServlet{
    // variables de instancia
    private String title="Génération d'un formulaire";
    private final String[] valeurs={"zéro","un","deux","trois","quatre","cinq","six",
      "sept","huit","neuf"};
    private final String HTML1=
                "<html>" +
                  "<head>" +
                    "<title>Génération de formulaire</title>"+
                    "</head>" +
                  "<body>" +
                     "<h3>Choississez un nombre</h3>"+
                     "<hr>" +
                     "<form method=\"POST\">";
        private final String HTML2="<input type=\"submit\" value=\"Envoyer\">";
        private final String HTML3="</form>\n</body>\n</html>";

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

      // se le indica al cliente el tipo de documento enviado
      response.setContentType("text/html");
       // se envía el formulario
      PrintWriter out=response.getWriter();
            // inicio
      out.println(HTML1);
            // menú desplegable
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            for (int i=0;i<valeurs.length;i++){
                out.println("<option>"+valeurs[i]+"</option>");
            }//para
            out.println("</select>");
            // fin del formulario
            out.println(HTML2+HTML3);
    }//GET

         // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
          throws IOException, ServletException{

             // se recupera la elección del usuario
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) doGet(request,response);

             // se prepara la respuesta
            String réponse="<html><head><title>Voici ma réponse</title></head>";
            réponse+="<body>Vous avez choisi le nombre <h2>"+choix+"</h2></body></html>";
             // se le indica al cliente el tipo de documento enviado
            response.setContentType("text/html");
             // se envía el formulario
            PrintWriter out=response.getWriter();
            out.println(réponse);
    }//POST
    }//clasifica

El método doGet sirve para generar el formulario. Hay una parte dinámica que es el contenido de la lista, el cual proviene en este caso de una tabla. El método doPost sirve para generar la respuesta. Aquí, la única parte dinámica es el valor de la selección que realiza el usuario en la lista del formulario. Este valor se obtiene mediante request.getParameter("cmbValeurs"), donde cmbValeurs es el nombre de la lista:

      <select name="cmbValeurs" size="1">

Para concluir, cabe destacar los siguientes puntos:

  • el navegador envía los valores del formulario a la servlet que generó el formulario porque la etiqueta <form> no tiene el atributo <action>. En este caso, el navegador envía los datos ingresados en el formulario a la URL que lo proporcionó.
  • La etiqueta <form> especifica que los datos del formulario deben enviarse mediante el método POST. Por eso, estos valores son recuperados por el método doPost del servlet.

3.1.6.2. Generación dinámica de formularios - 2

Retomamos el ejemplo anterior y lo modificamos de la siguiente manera. El formulario propuesto sigue siendo el mismo:

Image

¿Es diferente la respuesta?:

Image

En la respuesta, se devuelve el formulario, con el número elegido por el usuario indicado debajo. Además, este número es el que aparece como seleccionado cuando se muestra la lista. El usuario puede entonces elegir otro número:

Image

y luego ejecutar Envoyer. Obtiene la siguiente respuesta:

Image

El código del servlet llamado gener2.java es el siguiente:

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

public class gener2 extends HttpServlet{
        // variables de instancia
        private String title="Génération d'un formulaire";
        private final String[] valeurs={"zéro","un","deux","trois","quatre","cinq","six",
            "sept","huit","neuf"};
        private final String HTML1=
                "<html>" +
                    "<head>" +
                        "<title>Génération de formulaire</title>"+
                    "</head>" +
                    "<body>" +
                         "<h3>Choisissez un nombre</h3>"+
                         "<hr>" +
                         "<form method=\"POST\">";
        private final String HTML2="<input type=\"submit\" value=\"Envoyer\"></form>\n";
        private final String HTML3="</body>\n</html>";

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

            // se recupera la selección del usuario, si la hay
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

             // se le indica al cliente el tipo de documento enviado
            response.setContentType("text/html");
             // se envía el formulario
            PrintWriter out=response.getWriter();
            // inicio
            out.println(HTML1);
            // menú desplegable
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            String selected="";
            for (int i=0;i<valeurs.length;i++){
                if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                out.println("<option "+selected+">"+valeurs[i]+"</option>");
            }//para
            out.println("</select>");
            // continuación del formulario
            out.println(HTML2);
            if(! choix.equals("")){
                // se muestra la selección del usuario
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//si
             // fin del formulario
            out.println(HTML3);
        }//GET

        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // se redirige a GET
            doGet(request,response);
        }//POST
    }//clase

El método doGet se encarga de todo: genera el formulario que envía al cliente y procesa los valores que este devuelve. Los puntos a tener en cuenta son los siguientes:

  • se verifica si el parámetro cmbValeurs tiene un valor.
  • Si es así, al generar el contenido de la lista, se compara cada elemento de la misma con la selección del usuario para asignar el atributo selected al elemento elegido por el usuario: <option selected>elemento</option>. Además, se muestra debajo del formulario el valor de la selección.

3.1.6.3. Generación dinámica de formularios - 3

Retomamos el mismo problema que antes, pero esta vez los valores se obtienen de una base de datos. En nuestro ejemplo, se trata de la base MySQL:

  • la base se llama dbValeurs
  • su propietario es admDbValeurs, cuya contraseña es mdpDbValeurs
  • La base de datos tiene una sola tabla llamada tvaleurs
  • esta tabla solo tiene un campo entero llamado valor
E:\Program Files\EasyPHP\mysql\bin>mysql --database=dbValeurs --user=admDbValeurs --password=mdpDbVa
leurs

mysql> show tables;
+---------------------+
| Tables_in_dbValeurs |
+---------------------+
| tvaleurs            |
+---------------------+
1 row in set (0.00 sec)

mysql> describe tvaleurs;
+--------+---------+------+-----+---------+-------+
| Field  | Type    | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+-------+
| valeur | int(11) |      |     | 0       |       |
+--------+---------+------+-----+---------+-------+

mysql> select * from tvaleurs;
+--------+
| valeur |
+--------+
|      0 |
|      1 |
|      2 |
|      3 |
|      4 |
|      6 |
|      5 |
|      7 |
|      8 |
|      9 |
+--------+
10 rows in set (0.00 sec)

La base de datos MySQL dbValeurs se ha hecho accesible mediante un controlador ODBC para MySQL. Su nombre DSN (nombre de la fuente de datos) es odbc-valeurs. El código del servlet es el siguiente:

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

public class gener3 extends HttpServlet{
        // el título de la página
        private final String title="Génération d'un formulaire";
         // la base de datos de valores de lista
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
         // valores de la lista
        private String[] valeurs=null;
         // mensaje de error
        private String msgErreur=null;
        // código HTML
        private final String HTML1=
                "<html>" +
                    "<head>" +
                        "<title>Génération de formulaire</title>"+
                    "</head>" +
                    "<body>" +
                         "<h3>Choisissez un nombre</h3>"+
                         "<hr>" +
                         "<form method=\"POST\">";
        private final String HTML2="<input type=\"submit\" value=\"Envoyer\"></form>\n";
        private final String HTML3="</body>\n</html>";

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

            // se le indica al cliente el tipo de documento enviado
            response.setContentType("text/html");
             // flujo de salida
            PrintWriter out=response.getWriter();

            // ¿Se ha realizado correctamente la inicialización del servlet?
            if (msgErreur!=null){
                 // Hubo un error: se genera una página de error
                out.println("<html><head><title>"+title+"</title></head>");
                out.println("<body><h3>Application indisponible ("+msgErreur+
                                        ")</h3></body></html>");
                return;
            }//si

             // se recupera la selección del usuario, si la hay
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

             // se envía el formulario
             // inicio
            out.println(HTML1);
            // lista desplegable
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            String selected="";
            for (int i=0;i<valeurs.length;i++){
                if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                out.println("<option "+selected+">"+valeurs[i]+"</option>");
            }//para
            out.println("</select>");
            // continuación del formulario
            out.println(HTML2);
            if(! choix.equals("")){
                // se muestra la selección del usuario
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//si
             // fin del formulario
            out.println(HTML3);
        }//GET

        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // se redirige a GET
            doGet(request,response);
        }//POST

        // inicialización del servlet
        public void init(){
             // llena la tabla con valores de una base de datos ODBC
             // con nombre DSN: DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // conexión a la base de datos ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // objeto Statement
                st=connexion.createStatement();
                 // ejecución de la consulta SELECT para recuperar los valores
                rs=st.executeQuery("select valeur from Tvaleurs");
                // los valores se recuperan y se colocan en una tabla dinámica
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // se registra el valor en la lista
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                 // transformación de lista a matriz
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                 // problema
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//iniciar
    }//clase

Los puntos importantes a tener en cuenta son los siguientes:

  1. Un servlet puede inicializarse mediante un método cuya firma debe ser public void init(). Este método solo se ejecuta al cargarse inicialmente el servlet
  2. Una vez cargada, una servlet permanece en memoria todo el tiempo. Esto significa que, una vez que ha atendido a un cliente, no se descarga. De esta manera, responde más rápido a las solicitudes de los clientes.
  3. En nuestro servlet, se debe buscar una lista de valores en una base de datos. Dado que esta lista no cambia con el tiempo, el método init es el momento ideal para recuperarla. De esta manera, el servlet solo accede a la base de datos una vez, al momento de su carga inicial, y no con cada solicitud de un cliente.
  4. El acceso a una base de datos puede fallar. El método init de nuestro servlet genera un mensaje de error msgErreur en caso de fallo. Este mensaje se verifica en el método doGet y, si se ha producido un error, doGet genera una página que lo indica.
  5. La implementación del método init utiliza un acceso convencional a una base de datos con los controladores ODBC-JDBC. Si es necesario, se invita al lector a revisar los métodos de acceso a las bases de datos JDBC.

Al ejecutar el servlet y si el servidor MySQL no se ha iniciado, aparece la siguiente página de error:

Image

Si ahora se inicia el servidor MySQL, se obtiene la página:

Image

Si elegimos el número 6 y hacemos clic en «Enviar»:

Image

3.1.6.4. Recuperar los valores de un formulario

Retomamos un ejemplo que ya hemos visto, el del siguiente formulario web:

Image

El código HTML del formulario balises2.htm es el siguiente:

<html>

  <head>
      <title>balises</title>
    <script language="JavaScript">
        function effacer(){
          alert("Vous avez cliqué sur le bouton Effacer");
      }//borrar
        </script>
  </head>

  <body background="/images/standard.jpg">
...

    <form method="POST" action="http://localhost:8080/examples/servlet/parameters">

      <table border="0">
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
              <input type="radio" value="Oui" name="R1">Oui
              <input type="radio" name="R1" value="non" checked>Non
          </td>
        </tr>
        <tr>
          <td>Cases à cocher</td>
          <td>
              <input type="checkbox" name="C1" value="un">1
              <input type="checkbox" name="C2" value="deux" checked>2
              <input type="checkbox" name="C3" value="trois">3
          </td>
        </tr>
        <tr>
          <td>Champ de saisie</td>
          <td>
              <input type="text" name="txtSaisie" size="20" value="qqs mots">
          </td>
        </tr>
        <tr>
          <td>Mot de passe</td>
          <td>
              <input type="password" name="txtMdp" size="20" value="unMotDePasse">
          </td>
        </tr>
        <tr>
          <td>Boîte de saisie</td>
          <td>
               <textarea rows="2" name="areaSaisie" cols="20">
ligne1
ligne2
ligne3
</textarea>
          </td>
        </tr>
        <tr>
          <td>combo</td>
          <td>
              <select size="1" name="cmbValeurs">
                <option>choix1</option>
                <option selected>choix2</option>
                <option>choix3</option>
              </select>
          </td>
        </tr>
        <tr>
          <td>liste à choix simple</td>
          <td>
              <select size="3" name="lst1">
                <option selected>liste1</option>
                <option>liste2</option>
                <option>liste3</option>
                <option>liste4</option>
                <option>liste5</option>
              </select>
          </td>
        </tr>
        <tr>
          <td>liste à choix multiple</td>
          <td>
              <select size="3" name="lst2" multiple>
                <option selected>liste1</option>
                <option>liste2</option>
                <option selected>liste3</option>
                <option>liste4</option>
                <option>liste5</option>
              </select>
          </td>
        </tr>
        <tr>
          <td>bouton</td>
          <td>
              <input type="button" value="Effacer" name="cmdEffacer" onclick="effacer()">
          </td>
        </tr>
        <tr>
          <td>envoyer</td>
          <td>
              <input type="submit" value="Envoyer" name="cmdRenvoyer">
          </td>
        </tr>
        <tr>
          <td>rétablir</td>
          <td>
              <input type="reset" value="Rétablir" name="cmdRétablir">
          </td>
        </tr>
      </table>
      <input type="hidden" name="secret" value="uneValeur">
    </form>
  </body>
</html>

La etiqueta <form> del formulario se ha definido de la siguiente manera:

    <form method="POST" action="http://localhost:8080/examples/servlet/parameters">

El navegador «enviará» los valores del formulario a URL http://localhost:8080/examples/servlet/parameters, que es el URL de un servlet administrado por Tomcat y que muestra los valores del formulario anterior. Si se llama directamente al servlet parameters, se obtienen los siguientes resultados:

Image

Si el formulario balises2.htm que se ingresa es este:

Image

y se presiona el botón Enviar (del tipo submit), esta vez se invoca el servlet parameters con parámetros. A continuación, devuelve la siguiente respuesta:

Image

En esta respuesta se pueden ver claramente los valores ingresados en el formulario. El código del servlet es el siguiente:

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

public class parameters extends HttpServlet{
    // variables de instancia
    String title="Récupération des paramètres d'un formulaire";

    private String getParameter(HttpServletRequest request, String contrôle){
      // devuelve el valor request.getParameter (control) o "" si no existe
      String valeur=request.getParameter(contrôle);
      if(valeur==null) return ""; else return valeur;
    }//getParameter

    // GET
    public void doGet(HttpServletRequest request,HttpServletResponse response)
        throws IOException, ServletException
    {
      // primero se recuperan los parámetros del formulario
      String R1=getParameter(request,"R1");
      String C1=getParameter(request,"C1");
      String C2=getParameter(request,"C2");
      String C3=getParameter(request,"C3");
      String txtSaisie=getParameter(request,"txtSaisie");
      String txtMdp=getParameter(request,"txtMdp");
      String areaSaisie=getParameter(request,"areaSaisie");
      String[] lignes=areaSaisie.split("\\r\\n");
      String cmbValeurs=getParameter(request,"cmbValeurs");
      String lst1=getParameter(request,"lst1");
      String[] lst2=request.getParameterValues("lst2");
      String secret=getParameter(request,"secret");

      // se indica el contenido del documento
        response.setContentType("text/html");
       // se envía el documento
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>" + title + "</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");
        out.println("<h3>" + title + "</h3>");
        out.println("<hr>");
        out.println("<table border=\"1\">");
        out.println("<tr><td>R1</td><td>"+R1+"</td></tr>");
        out.println("<tr><td>C1</td><td>"+C1+"</td></tr>");
        out.println("<tr><td>C2</td><td>"+C2+"</td></tr>");
        out.println("<tr><td>C3</td><td>"+C3+"</td></tr>");
        out.println("<tr><td>txtSaisie</td><td>"+txtSaisie+"</td></tr>");
        out.println("<tr><td>txtMdp</td><td>"+txtMdp+"</td></tr>");
        for(int i=0;i<lignes.length;i++)
          out.println("<tr><td>areaSaisie["+i+"]</td><td>"+lignes[i]+"</td></tr>");
        out.println("<tr><td>cmbValeurs</td><td>"+cmbValeurs+"</td></tr>");
        out.println("<tr><td>lst1</td><td>"+lst1+"</td></tr>");
        if(lst2==null)
          out.println("<tr><td>lst2</td><td></td></tr>");
        else
          for(int i=0;i<lst2.length;i++)
            out.println("<tr><td>lst2</td><td>"+lst2[i]+"</td></tr>");
        out.println("<tr><td>secret</td><td>"+secret+"</td></tr>");
        out.println("</body>");
        out.println("</html>");
    }

     // POST
    public void doPost(HttpServletRequest request,HttpServletResponse response)
        throws IOException, ServletException
    {
       // redirige a GET
      doGet(request,response);
    }
}

En este código se pueden observar las técnicas presentadas anteriormente en otro ejemplo. Cabe destacar dos puntos:

  1. el control lst2 es una lista de selección múltiple, por lo que se pueden seleccionar varios elementos. Este es el caso en nuestro ejemplo, donde se han seleccionado los elementos liste1 y liste3. Los valores de lst2 fueron enviados por el navegador al servidor en el formato lst2=liste1&lst2=liste3. El servlet de Java puede recuperar estos valores en una matriz con el método getParameterValues: aquí, request.getParameterValues("lst2") proporciona una matriz de 2 cadenas de caracteres ["liste1","liste3"].
  2. El control areaSaisie es un campo de entrada de varias líneas. request.getParameter("areaSaisie") devuelve el contenido del campo en forma de una sola cadena de caracteres. Si en esta cadena se desea recuperar las diferentes líneas que la componen, se puede utilizar la función split de la clase String. El siguiente código
      String areaSaisie=getParameter(request,"areaSaisie");
      String[] lignes=areaSaisie.split("\\r\\n");

extrae las líneas del campo de entrada. Estas líneas terminan con los caracteres \r\n (0D0A).

Para realizar las pruebas, hemos:

  • construido y compilado el servlet parameters junto con JBuilder, tal como se explicó anteriormente
  • colocado la clase generada en <tomcat>\webapps\examples\WEB-INF\classes, donde <tomcat> es el directorio de instalación de Tomcat.
  • Accedí a URL http://localhost:81/html/balises2.htm, cuyo código se presentó anteriormente
  • llené el formulario y presioné el botón Envoyer.

3.1.6.5. Recuperar los encabezados de un cliente web

Retomamos el mismo ejemplo que antes, pero en respuesta al cliente web que envió los valores del formulario, le enviamos los encabezados HTTP que él mismo envió al mismo tiempo. Introducimos un único cambio en nuestro formulario:

    <form method="GET" action="http://localhost:8080/examples/servlet/headers">

Los valores del formulario se enviarán mediante el método GET a un servlet de Java llamado headers ubicado en <tomcat>\webapps\examples\WEB-INF\classes. El servlet headers se ha creado y compilado junto con JBuilder:

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

public class headers extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
       // se establece el tipo de documento
        response.setContentType("text/html");
       // se obtiene un flujo de escritura
        PrintWriter out = response.getWriter();
      // se muestra la lista de encabezados HTTP
        Enumeration e = request.getHeaderNames();
        while (e.hasMoreElements()) {
            String name = (String)e.nextElement();
            String value = request.getHeader(name);
            out.println("<b>"+name + "</b> = " + value + "<br>");
        }
    }//GET

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

Se solicita el URL http://localhost:81/html/balises2.htm y se genera el Envoyer sin modificar el formulario. Se obtiene la siguiente respuesta:

Image

Obsérvese que el parámetro URL aparece en el campo Address del navegador, lo que muestra la forma (GET) utilizada para transmitir los parámetros. Tomamos el mismo ejemplo, pero modificando la forma de enviar los parámetros (POST):

    <form method="POST" action="http://localhost:8080/examples/servlet/headers">

Se obtiene la siguiente respuesta nueva:

Image

Cabe destacar los encabezados HTTP, content-type y content-length, característicos de un envío mediante POST. Además, cabe señalar que en el campo Address del navegador ya no aparecen los valores del formulario.

3.2. Páginas JSP

Las páginas JSP (Java Server Pages) son otra forma de desarrollar aplicaciones de servidor web. De hecho, estas páginas JSP se traducen a servlets antes de ejecutarse, por lo que se recurre a la tecnología de servlets. Las páginas JSP permiten resaltar mejor la estructura de las páginas HTML generadas. A continuación presentamos algunos ejemplos, algunos de los cuales se pueden consultar siguiendo el enlace JSP de la página de inicio de Tomcat:

3.2.1. Obtener información del entorno

Aquí retomamos un ejemplo ya tratado con un servlet: mostrar las variables de entorno de un servlet. Se trata del ejemplo snoop de los ejemplos JSP:

Image

El código fuente de la página JSP se encuentra en <tomcat>\jakarta-tomcat\examples\jsp\snp\snoop.jsp (Tomcat 3.x) o en <tomcat>\examples\jsp\snp\snoop.jsp (Tomcat 4.x)


<html>
<!--
  Copyright (c) 1999 The Apache Software Foundation.  All rights 
  reserved.
-->

  <body bgcolor="white">
    <h1> Request Information </h1>
    <font size="4">
      JSP Request Method: <%= request.getMethod() %>
      <br>
      Request URI: <%= request.getRequestURI() %>
      <br>
      Request Protocol: <%= request.getProtocol() %>
      <br>
      Servlet path: <%= request.getServletPath() %>
      <br>
      Path info: <%= request.getPathInfo() %>
      <br>
      Path translated: <%= request.getPathTranslated() %>
      <br>
      Query string: <%= request.getQueryString() %>
      <br>
      Content length: <%= request.getContentLength() %>
      <br>
      Content type: <%= request.getContentType() %>
      <br>
      Server name: <%= request.getServerName() %>
      <br>
      Server port: <%= request.getServerPort() %>
      <br>
      Remote user: <%= request.getRemoteUser() %>
      <br>
      Remote address: <%= request.getRemoteAddr() %>
      <br>
      Remote host: <%= request.getRemoteHost() %>
      <br>
      Authorization scheme: <%= request.getAuthType() %> 
      <hr>
      The browser you are using is <%= request.getHeader("User-Agent") %>
      <hr>
    </font>
  </body>
</html>

Cabe destacar lo siguiente:

  • aquí tenemos un código que se parece mucho al de HTML. Sin embargo, contiene etiquetas <%= expresión %> que son propias del lenguaje JSP. El compilador JSP reemplaza en el texto HTML la etiqueta completa por el valor de expression.
  • Este ejemplo utiliza los métodos del objeto Java request, que es el objeto request que ya se ha visto en el estudio de los servlets. Por lo tanto, se trata de un objeto HttpServletRequest. De este modo, la etiqueta <%= request.getRemoteHost() %> se reemplazará en el código HTML por el nombre de la máquina del cliente web que realizó la solicitud.
  • Se puede lograr el mismo resultado con un servlet, pero aquí la estructura de la página web es más clara.

3.2.2. Recuperar los parámetros enviados por el cliente web

Retomamos aquí el ejemplo ya estudiado con un servlet. Se presenta un formulario en el navegador:

Image

En respuesta a la solicitud anterior, el navegador recibe la siguiente página:

Image

El código de la página JSP es el siguiente:


<%
   // variables locales del procedimiento principal
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

<!-- código HTML -->
<html>
  <head>
    <title><%= title %></title>
  </head>
  <body bgcolor="white">
    <h3><%= title %></h3>
    <%
      if (firstName != null || lastName != null) {
        out.println("firstname= " + firstName + "<br>");
        out.println("lastname= " + lastName);
      } else {
        out.println("pas de paramètres");
      }
    %>
    <P>
    <form method="POST">
      firstname= <input type="text" size="20" name="firstname">
      <br>
      lastname= <input type="text" size="20" name="lastname">
      <br>
      <input type="submit">
    </form>
  </body>
</html>
  • Si bien se vuelve a encontrar la etiqueta <%= expresión %>, que ya apareció en el ejemplo anterior, surge una nueva etiqueta: <% instrucciones Java; %>. La etiqueta <% introduce código Java. Este código termina al encontrar la etiqueta de cierre de código %>.
  • Todo el código anterior (HTML + JSP) se convertirá en un servlet de Java. Se incluirá en un único método, denominado método principal de la página JSP. Por eso, las variables de Java declaradas al inicio de la página JSP son reconocidas en las otras secciones de código JSP que se encuentran dispersas en el código HTML: estas variables y secciones de código formarán parte del mismo método de Java. Pero si nuestro código JSP contuviera métodos, las variables title, firstname y lastname no serían reconocidas en él debido al aislamiento entre métodos. Habría que convertirlas en variables globales o pasarlas como parámetros a los métodos. Volveremos sobre esto más adelante.
  • Para incluir partes dinámicas en el código HTML, existen dos métodos posibles: <%= expresión %> o out.println(expresión). El objeto out es un flujo de salida similar al del mismo nombre que aparece en los ejemplos de servlets, pero no es del mismo tipo: es un objeto JspWriter y no un PrintWriter. Permite escribir en el flujo HTML mediante los métodos print y println.
  • La página JSP refleja mejor la estructura de la página HTML generada que el servlet equivalente.

3.2.3. Las etiquetas JSP

A continuación se presenta una lista de las etiquetas que pueden aparecer en una página JSP y su significado.

etiqueta
significado
<!-- comentario -->
comentario HTML. Se envía al cliente.
<%-- comentario --%>
comentario JSP. No se envía al cliente.
<%! déclarations, méthodes %>
Declara variables globales y métodos. Las variables serán visibles en todos los métodos
<%= expression %>
El valor de la expresión se insertará en la página HTML en lugar de la etiqueta
<% code Java %>
contiene código Java que formará parte del método principal de la página JSP
<%@ page attribut1=valeur1
attribut2=valeur2 … %>
establece los atributos para la página JSP. Por ejemplo:
import="java.util.*,java.sql.*" para especificar las bibliotecas necesarias para la página JSP
extends="unaClaseParent" para que la página JSP se derive de otra clase

3.2.4. Los objetos implícitos JSP

En los ejemplos anteriores, nos encontramos con dos objetos no declarados: request y out. Estos son dos de los objetos que se definen automáticamente en el servlet en el que se convierte la página JSP. Se les conoce como objetos implícitos o predefinidos. Existen otros, pero estos son los más utilizados junto con el objeto response:

objeto
significado
HttpServletRequest request
el objeto a través del cual se tiene acceso a la solicitud del cliente web (getParameter, getParameterNames, getParameterValues)
HttpServletResponse response
el objeto con el que se puede construir la respuesta del servidor web a su cliente. Permite establecer los encabezados HTTP que se enviarán al cliente web.
JspWriter out
el flujo de salida que nos permite enviar código HTML al cliente (print, println)

3.2.5. La transformación de una página JSP en un servlet

Retomemos el código JSP de myRequestParamExample.jsp:


<%
   // variables locales del procedimiento principal
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

<!-- código HTML -->
<html>
  <head>
    <title><%= title %></title>
  </head>
  <body bgcolor="white">
    <h3><%= title %></h3>
    <%
      if (firstName != null || lastName != null) {
        out.println("firstname= " + firstName + "<br>");
        out.println("lastname= " + lastName);
      } else {
        out.println("pas de paramètres");
      }
    %>
    <P>
    <form method="POST">
      firstname= <input type="text" size="20" name="firstname">
      <br>
      lastname= <input type="text" size="20" name="lastname">
      <br>
      <input type="submit">
    </form>
  </body>
</html>

Cuando el navegador solicita esta página JSP al servidor Tomcat, este la transformará en un servlet. Si la página URL solicitada es

http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp, Tomcat 4.x colocará el servlet generado en el directorio <tomcat>\work\localhost\examples\jsp\perso\intro:

Image

En este nombre se encuentra el http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp de la página JSP. Como se ve arriba, tenemos acceso al código Java del servlet generado para la página JSP. En nuestro ejemplo, es el siguiente:

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;


public class myRequestParamExample$jsp extends HttpJspBase {


    static {
    }
    public myRequestParamExample$jsp( ) {
    }

    private static boolean _jspx_inited = false;

    public final void _jspx_init() throws org.apache.jasper.runtime.JspException {
    }

    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

        JspFactory _jspxFactory = null;
        PageContext pageContext = null;
        HttpSession session = null;
        ServletContext application = null;
        ServletConfig config = null;
        JspWriter out = null;
        Object page = this;
        String  _value = null;
        try {

            if (_jspx_inited == false) {
                synchronized (this) {
                    if (_jspx_inited == false) {
                        _jspx_init();
                        _jspx_inited = true;
                    }
                }
            }
            _jspxFactory = JspFactory.getDefaultFactory();
            response.setContentType("text/html;charset=ISO-8859-1");
            pageContext = _jspxFactory.getPageContext(this, request, response,
                        "", true, 8192, true);

            application = pageContext.getServletContext();
            config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();


                   // variables locales del procedimiento principal
                  String title="Récupération des paramètres d'un formulaire";
                  String firstName = request.getParameter("firstname");
                  String lastName = request.getParameter("lastname");

                out.write("\r\n\r\n<!-- code HTML -->\r\n<html>\r\n  <head>\r\n    <title>");
                out.print( title );
                out.write("</title>\r\n  </head>\r\n  <body bgcolor=\"white\">\r\n    <h3>");
                out.print( title );
                out.write("</h3>\r\n    ");

                      if (firstName != null || lastName != null) {
                        out.println("firstname= " + firstName + "<br>");
                        out.println("lastname= " + lastName);
                      } else {
                        out.println("pas de paramètres");
                      }

                out.write("\r\n    <P>\r\n    <form method=\"POST\">\r\n      firstname= <input type=\"text\" size=\"20\" name=\"firstname\">\r\n      <br>\r\n      lastname= <input type=\"text\" size=\"20\" name=\"lastname\">\r\n      <br>\r\n      <input type=\"submit\">\r\n    </form>\r\n  </body>\r\n</html>\r\n");

        } catch (Throwable t) {
            if (out != null && out.getBufferSize() != 0)
                out.clearBuffer();
            if (pageContext != null) pageContext.handlePageException(t);
        } finally {
            if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
        }
    }
}

El código generado es bastante complejo. Nos limitaremos a destacar los siguientes puntos:

  • El método principal del servlet es el siguiente:
    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

Este es el método que se ejecuta al inicio del servlet. Se observa que recibe dos parámetros: la solicitud request del cliente y un objeto response para generar su respuesta al cliente web.

  • En el método principal se declara y se inicializa un objeto JspWriter denominado «out». Este es el que permitirá enviar código HTML al cliente mediante las instrucciones out.print("codeHTML").
        JspWriter out = null;
...
            out = pageContext.getOut();
  • El código Java

<%
   // variables locales del procedimiento principal
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

se ha incorporado íntegramente en el método principal _jspService del servlet. Lo mismo ocurre con todo el código ubicado entre las etiquetas <%… %>

  • El código HTML de la página JSP es objeto de las instrucciones out.print("codeHTML") o out.write(...). Por ejemplo

                out.write("</title>\r\n  </head>\r\n  <body bgcolor=\"white\">\r\n    <h3>");
  • En este ejemplo, no hay otros métodos aparte del método principal _jspService.

3.2.6. Los métodos y variables globales de una página JSP

Consideremos la siguiente página JSP:


<%!
   // la etiqueta anterior inicia la sección de variables y métodos globales
   // esta sección se incorporará sin modificaciones en el servlet
  
   // una variable global
  String prenom="inconnu";

   // un método  
  private String sonChien(){
    return "milou";
  }//sonChien

   // otro método
  private void afficheAmi(JspWriter out) throws Exception{
    out.println("<p>Son ami s'appelle Haddock</p>");
  }//afficheAmi

   // fin de la parte global del servlet
%>  

<%
   // la etiqueta anterior indica que el código que sigue se guardará
   // en el método principal del servlet
  
   // variable local del método principal
  String nom="tintin";
%>


<%-- código HTML --%>
<html>
  <head>
    <title>Page JSP</title>
  </head>
  <body>
    <center>
      <h2>Page JSP</h2>
      <p>Son nom est <%= nom %></p>
      <p>Son prénom est <%= prenom %></p>
      <p>Son chien s'appelle <%= sonChien() %></p>
      <%
         // el nombre de su amigo
        afficheAmi(out);
      %>
    </center>
  </body>
</html>

Esta página JSP genera la siguiente página web:

Image

Veamos cómo se generan las cuatro líneas anteriores:


      <p>Son nom est <%= nom %></p>
      <p>Son prénom est <%= prenom %></p>
      <p>Son chien s'appelle <%= sonChien() %></p>
      <%
         // el nombre de su amigo
        afficheAmi(out);
      %>

Las líneas anteriores se encuentran dentro de una etiqueta <%..%> y, por lo tanto, formarán parte del método principal _jspService del servlet que se generará. ¿Cómo tienen acceso a las variables nom, prenom y a los métodos sonChien y afficheAmi?

nom (tintin)
es una variable local del método principal de la página JSP y, por lo tanto, es conocida en este
prenom (inconnu)
es una variable global de la página JSP y, por lo tanto, es conocida en el método principal
sonChien (milou)
es un método público de la página JSP y, por lo tanto, accesible desde el método principal
afficheAmi (Haddock)
es un método público de la página JSP y, por lo tanto, accesible desde el método principal. Cabe destacar que se pasa el objeto out como parámetro al método. Esto es obligatorio en este caso. De hecho, el objeto out se declara e inicializa en el método principal del servlet y no es una variable global.

Veamos ahora el código del servlet Java generado a partir de esta página JSP, una vez eliminado el código innecesario:

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;


public class tintin$jsp extends HttpJspBase {

          // la etiqueta anterior inicia la sección de variables y métodos globales
           // esta sección se incorporará sin modificaciones en el servlet

           // una variable global
          String prenom="inconnu";

           // un método  
          private String sonChien(){
            return "milou";
          }//sonChien

           // otro método
          private void afficheAmi(JspWriter out) throws Exception{
            out.println("<p>Son ami s'appelle Haddock</p>");
          }//afficheAmi

           // fin de la parte global del servlet

    static {
    }
    public tintin$jsp( ) {
    }

    private static boolean _jspx_inited = false;

    public final void _jspx_init() throws org.apache.jasper.runtime.JspException {
    }

    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

        JspFactory _jspxFactory = null;
        PageContext pageContext = null;
        HttpSession session = null;
        ServletContext application = null;
        ServletConfig config = null;
        JspWriter out = null;
        Object page = this;
        String  _value = null;
        try {

            if (_jspx_inited == false) {
                synchronized (this) {
                    if (_jspx_inited == false) {
                        _jspx_init();
                        _jspx_inited = true;
                    }
                }
            }
            _jspxFactory = JspFactory.getDefaultFactory();
            response.setContentType("text/html;charset=ISO-8859-1");
            pageContext = _jspxFactory.getPageContext(this, request, response,
                        "", true, 8192, true);

            application = pageContext.getServletContext();
            config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();

                out.write("  \r\n\r\n");
                  // la etiqueta anterior indica que el código que sigue se guardará
                   // en el método principal del servlet

                   // variable local del método principal
                  String nom="tintin";

                out.write("\r\n\r\n\r\n");
                out.write("\r\n<html>\r\n  <head>\r\n    <title>Page JSP</title>\r\n  </head>\r\n  <body>\r\n    <center>\r\n      <h2>Page JSP</h2>\r\n      <p>Son nom est ");
                out.print( nom );
                out.write("</p>\r\n      <p>Son prénom est ");
                out.print( prenom );
                out.write("</p>\r\n      <p>Son chien s'appelle ");
                out.print( sonChien() );
                out.write("</p>\r\n      ");

                        // el nombre de su amigo
                        afficheAmi(out);

                out.write("\r\n    </center>\r\n  </body>\r\n</html>\r\n");
        } catch (Throwable t) {
            if (out != null && out.getBufferSize() != 0)
                out.clearBuffer();
            if (pageContext != null) pageContext.handlePageException(t);
        } finally {
            if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
        }
    }
}

Como se puede observar arriba, el código Java que se encontraba entre las etiquetas JSP <%! .. %> se ha incluido en su totalidad y no forma parte del método principal _jspService del servlet. Las variables declaradas en esta sección son, por lo tanto, variables de instancia, es decir, globales para los métodos, y es también aquí donde se pueden definir métodos distintos de _jspService.


  // esta parte se incluirá sin modificaciones en el servlet

   // una variable global
  String prenom="inconnu";

   // un método
  private String sonChien(){
    return "milou";
  }//sonChien

   // otro método
  private void afficheAmi(JspWriter out) throws Exception{
    out.println("<p>Son ami s'appelle Haddock</p>");
  }//afficheAmi

   // fin de la parte global del servlet

3.2.7. Implementación y depuración de las páginas JSP en el servidor Tomcat

Cuando se desea crear una página JSP y utilizarla con el servidor Tomcat, surge la pregunta de dónde ubicar la página en la estructura de directorios del servidor. Existen diferentes formas de hacerlo, las cuales abordaremos más adelante. Por ahora, la más sencilla es colocar la página JSP en una carpeta del árbol de directorios <tomcat>\webapps\examples\jsp (Tomcat 4.x), donde <tomcat> es el directorio de instalación de Tomcat. Así, el URL del ejemplo anterior era http://localhost:8080/examples/jsp/perso/tintin/tintin.jsp. Esto significa que la página tintin.jsp se encontraba en la carpeta <tomcat>\webapps\examples\jsp\perso\tintin.

Una página JSP se traduce a un archivo fuente Java, el cual luego es compilado por Tomcat cuando un navegador solicita el archivo URL de la página JSP. Pueden surgir errores de compilación. Tomcat 4.x los señala en su respuesta al navegador. En particular, indica las líneas del archivo .java que contienen errores. Los errores pueden tener diversas causas:

  1. el código JSP de la página es incorrecto (por ejemplo, errores en las etiquetas JSP utilizadas)
  2. el código Java incluido en la página JSP es incorrecto

La primera causa se puede descartar al verificar el código JSP de la página. La segunda puede eliminarse al verificar el código Java. Esto se puede hacer compilando directamente el archivo .java generado para la página JSP con una herramienta como JBuilder, que ofrece capacidades de depuración más avanzadas que las de Tomcat.

3.2.8. Ejemplos

Retomamos el ejemplo ya visto con un servlet en el que un usuario elige un número de una lista y el servidor le indica qué número ha elegido, al tiempo que le devuelve la misma lista con el elemento seleccionado por el usuario:

Image

Para crear esta página, tomamos el código del servlet y lo modificamos de la siguiente manera:

  • se mantuvo tal cual el código Java que no generaba código HTML
  • el código Java que generaba el código HTML se transformó en una combinación del código HTML y el código JSP

De este modo, se obtiene la siguiente página JSP:



<%@ page import="java.sql.*, java.util.*" %>

<%!

         // variables globales de la aplicación
         // el título de la página
        private final String title="Génération d'un formulaire";
         // la base de datos de los valores de la lista
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
         // valores de la lista
        private String[] valeurs=null;
         // mensaje de error
        private String msgErreur=null;
    
         // inicialización de la página JSP: solo se ejecuta una vez
        public void jspInit(){
             // llena la tabla de valores a partir de una base de datos ODBC
             // con nombre DSN: DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // conexión a la base de datos ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // objeto Statement
                st=connexion.createStatement();
                 // ejecución de la consulta SELECT para recuperar los valores
                rs=st.executeQuery("select valeur from Tvaleurs");
                // los valores se recuperan y se colocan en una tabla dinámica
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // se registra el valor en la lista
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                 // transformación de lista a matriz
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                 // problema
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//inicializar
   
%>

<%
     // código de _jspService ejecutado en cada solicitud del cliente
   // ¿Hubo un error al inicializar la página JSP?
  if(msgErreur!=null){
%>
       <!-- código HTML -->
    <html>
        <head>
          <title>Erreur</title>
      </head>
      <body>
          <h3>Application indisponible (<%= msgErreur %></h3>
      </body>
    </html>
<%
       // fin de jspService
    return;
  }//si
  
     // se recupera la elección del usuario, si la hay
    String choix=request.getParameter("cmbValeurs");
    if(choix==null) choix="";
%>
  
   <%-- sin error: código HTML de la página normal --%>
      <html>
        <head>
          <title><%= title %></title>
      </head>
      <body>
          <h3>Choisissez une valeur</h3>
          <form method="POST">
            <select name="cmbValeurs">
              <%
                 // visualización dinámica de valores
                          String selected="";
                          for (int i=0;i<valeurs.length;i++){
                              if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                              out.println("<option "+selected+">"+valeurs[i]+"</option>");
                          }//para
            %>
          </select>
          <input type="submit" value="Envoyer">
        </form>
<%
         // ¿Se seleccionó algún valor?
                if(! choix.equals("")){
        // se muestra la elección del usuario
%>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
<%        
                }//si
%>        
      </body>
    </html>

Cabe destacar los siguientes puntos:

  • las instrucciones import del servlet se han incluido dentro de una directiva <% page import="..." %>
  • la etiqueta <%! ... %> enmarca las variables globales y los métodos Java de la aplicación
  • el método init del servlet, que se ejecuta una sola vez al cargarse el servlet, se invoca para una página JSP: jspInit. Estos dos métodos cumplen la misma función. Por lo tanto, aquí se ha copiado íntegramente el código del método init del servlet.
  • Las variables de instancia del servlet, aquellas que deben ser conocidas en varios métodos, se han copiado tal cual. Se trata esencialmente de las variables title, valores y msgErreur, que luego se utilizan en el código JSP.
  • Las etiquetas <% ... %> enmarcan el código Java que se incluirá en el método _jspService, el cual se ejecutará al recibir una solicitud de un cliente.
  • Al igual que en el servlet, el método _jspService comenzará por verificar el valor de la variable msgErreur para determinar si debe generar una página de error. Si hay un error, genera la página de error y se detiene (return).
  • Si no hay error, genera el formulario con la lista de valores
  • una vez hecho esto, verifica si el usuario seleccionó un número; de ser así, muestra ese número en la página generada

¿Qué hemos ganado con respecto al servlet? Sin duda, una mejor visión del código HTML generado. Pero aún queda mucho código Java que «contamina» esta vista. Más adelante veremos otro método llamado delegación, en el que podremos colocar la mayor parte del código Java en un servlet, mientras que la página JSP conservará únicamente el código HTML y JSP. De esta manera, se separa claramente la parte de procesamiento de la parte de presentación.

3.3. Implementación de una aplicación web en el servidor Tomcat

A continuación, explicamos cómo implementar aplicaciones web Java con el servidor Tomcat. Si bien lo que se va a explicar es específico de este servidor, la implementación de una aplicación web Java en otro contenedor J2EE presentará características similares a las que se describirán a continuación.

3.3.1. Los archivos de configuración server.xml y web.xml

Hasta ahora, para probar nuestros servlets y páginas JSP, hemos colocado

  • los servlets en la carpeta <tomcat>\webapps\examples\WEB-INF\classes. Así, se podía acceder a ellos a través de la dirección http://localhost:8080/examples/servlet/nomServlet
  • las páginas JSP en la estructura de directorios <tomcat>\webapps\examples\jsp. En ese momento, se podía acceder a ellas a través de URL http://localhost:8080/examples/jsp/nomPageJSP

Nunca hemos explicado por qué era así. La configuración del servidor Tomcat se realiza en un archivo de texto llamado server.xml, que se encuentra en la carpeta <tomcat>\conf:

Image

Este archivo de texto es, de hecho, un archivo XML (lenguaje de marcado eXtended). Un documento XML es un documento de texto que contiene etiquetas, al igual que un documento HTML. Sin embargo, mientras que las etiquetas del lenguaje HTML están bien definidas, las del lenguaje XML no lo están. Por lo tanto, el siguiente documento es un documento XML:

<personne>
    <prenom>Pierre</prenom>
  <nom>Lucas</nom>
  <age>28</age>
</personne>

Un documento XML es simplemente un documento «con etiquetas» que sigue ciertas reglas de marcado:

  • un texto marcado con el formato <xx att1="val1" att2="val2" ....>texto</xx>
  • una etiqueta puede aparecer sola y tener la forma <xx att1="val1" att2="val2" ..../>

Los campos «atti» se denominan atributos de la etiqueta «xx» y los campos «vali» son los valores asociados a dichos atributos. Algunos documentos HTML no son documentos XML válidos. Por ejemplo, la etiqueta <br> no es una etiqueta válida. Debería escribirse <br/> para que lo sea, a fin de cumplir con la regla que establece que toda etiqueta debe cerrarse. Se ha creado una variante de HTML llamada XHTML con el fin de convertir cualquier documento XHTML en un documento XML válido. Algunos de los navegadores más recientes pueden mostrar archivos XML. Así, si llamamos personne.xml al documento XML presentado como ejemplo anteriormente y lo visualizamos con IE6, obtenemos la siguiente visualización:

Image

IE6 reconoce las etiquetas y las colorea. También reconoce la estructura del documento gracias a las etiquetas. Así, si llamamos personne2.xml al siguiente documento:

<personne><prenom>Pierre</prenom><nom>Lucas</nom><age>28</age></personne>

y lo visualizamos con IE6, obtenemos la misma visualización:

Image

IE6 ha reconocido correctamente la estructura y el contenido del documento. Todo el interés del documento XML radica en esta propiedad: es fácil recuperar la estructura y el contenido de un documento XML. Esto se logra con un programa llamado analizador sintáctico XML. Los documentos XML tienden a convertirse en el estándar en el intercambio de documentos en la web. Tomemos como ejemplo una máquina A que debe enviar un documento DOC a una máquina B. El documento DOC se genera a partir de la información contenida en una base de datos DB-A. La máquina B, por su parte, debe almacenar el documento DOC en una base de datos DB-B. El intercambio podrá realizarse de la siguiente manera:

  • la máquina A recupera los datos de la base DB-A y los encapsula en un documento de texto XML
  • el documento XML se envía a la máquina B a través de la red
  • la máquina B analiza el documento recibido con un analizador sintáctico XML y extrae tanto la estructura como los datos (tal como lo hizo IE6 en nuestro ejemplo). A continuación, puede almacenar los datos recibidos en la base de datos DB-B

No diremos más sobre el lenguaje XML, que merecería un libro completo dedicado a él.

Aquí, entonces, Tomcat está configurado mediante el archivo XML server.xml. Si lo visualizamos con IE6, obtenemos un documento complejo. Nos detendremos simplemente en las siguientes líneas:

Image

Lo que nos interesa aquí es la etiqueta <Context ...>. Sirve para definir aplicaciones web. Cabe destacar dos de sus atributos:

  • path: es el nombre de la aplicación web
  • docBase: es la carpeta en la que se encuentra. Aquí se trata de un nombre relativo: examples. ¿Relativo a qué carpeta? La respuesta también se encuentra en el archivo server.xml, en la línea siguiente:

Image

La línea anterior define el servidor web:

  • name: nombre del servidor web
  • appBase: raíz del árbol de documentos que distribuye. Una vez más, tenemos un nombre relativo: webapps. Este es relativo al directorio de instalación del servidor Tomcat <tomcat>. Por lo tanto, se trata de la carpeta <tomcat>\webapps.

La aplicación web «examples» tiene sus documentos en la carpeta examples (véase docBase más arriba). Este nombre es relativo a la raíz del árbol web del servidor, c.a.d. <tomcat>\webapps. Por lo tanto, se trata de la carpeta <tomcat>\webapps\examples. Veamos más de cerca esta carpeta:

Image

Aquí encontramos la carpeta WEB-INF\classes, en la que hemos guardado nuestros servlets para probarlos. La carpeta WEB-INF contiene un archivo llamado web.xml:

Image

Este archivo sirve para configurar la aplicación web examples. Por el momento, no entraremos en detalles sobre este archivo, ya que es demasiado complejo. Simplemente nos detendremos en las siguientes líneas:

    <servlet>
      <servlet-name>
          servletToJsp
      </servlet-name>
      <servlet-class>
          servletToJsp
      </servlet-class>
    </servlet>

La etiqueta <servlet> sirve para definir un servlet dentro de una aplicación web. Recordemos aquí que la aplicación web en cuestión es examples. La etiqueta servlet contiene aquí otras dos etiquetas:

  • <servlet-name>servletToJsp</servlet-name>: define el nombre del servlet
  • <servlet-name>servletToJsp</servlet-name>: define el nombre de la clase que se ejecutará cuando se solicite el servlet. En este ejemplo, el servlet y su clase tienen el mismo nombre. Esto no es obligatorio.

¿Cómo solicita un navegador la servlet servletToJsp al servidor Tomcat?

  • El navegador solicita el servlet URL http://localhost:8080/examples/servlet/servletToJsp
  • Tomcat analiza la ruta del servlet /examples/servlet/servletToJsp. Interpreta la primera parte de la ruta, /examples, como el nombre de una aplicación web y busca en su archivo de configuración, server.xml, dónde se han almacenado los documentos de esa aplicación. Como vimos anteriormente, se encuentran en la carpeta <tomcat>\webapps\examples.
  • Tomcat utiliza el resto de la ruta del servlet para localizarlo dentro de la aplicación web examples. Esta ruta /servlet/servletToJsp indica que debe ejecutar el servlet con el nombre servletToJsp. A continuación, Tomcat leerá el archivo de configuración web.xml de la aplicación examples, que encontrará en <tomcat>\webapps\examples\WEB-INF. En este archivo encontrará que el servlet servletToJsp está vinculado a la clase Java servletToJsp (véase el archivo web.xml más arriba). A continuación, buscará esta clase en la carpeta WEB-INF\classes de la aplicación web examples, c.a.d. en <tomcat>\webapps\examples\WEB-INF\classes y la ejecutará.

Image

3.3.2. Ejemplo: implementación de la aplicación web «lista»

Retomamos un servlet que ya hemos estudiado y que presentaba al usuario una lista de números entre los cuales él elegía uno. El servlet luego le confirmaba el número que había elegido:

Image

Como se muestra en el campo Address del navegador anterior, el archivo de clase del servlet se llamaba gener3. Según las explicaciones dadas anteriormente:

  • el URL /examples/servlet/gener3 indica que se trata de un servlet llamado gener3 de la aplicación web examples
  • en el archivo web.xml de la aplicación examples, no se encontrará nada que haga referencia a un servlet gener3. ¿Cómo la encontró entonces Tomcat? Habiendo revisado todo el archivo web.xml, no puedo responder con certeza... La pregunta sigue sin respuesta...

Decidimos implementar el servlet gener3.class con el nombre lstValeurs en una aplicación web llamada «lista», ubicada en la carpeta E:\data\serge\Servlets\lstValeurs:

Image

Colocamos el archivo gener3.class en la carpeta WEB-INF\classes mencionada anteriormente:

Image

Configuramos la aplicación web «lista» agregando en el archivo server.xml las siguientes líneas, por encima de las que definen la aplicación web manager:

                 <!-- Personal: lstValeurs -->
                <Context path="/liste" docBase="e:/data/serge/servlets/lstValeurs" />

                <!-- Contexto de Tomcat Manager -->
                <Context path="/manager" docBase="manager" debug="0" privileged="true" />
                <!-- Contexto de ejemplos de Tomcat -->
                <Context path="/examples" docBase="examples" debug="0" reloadable="true" crossContext="true">
........

La línea que define la aplicación indica que se encuentra en la carpeta e:/data/serge/servlets/lstValeurs. Ahora debemos definir el archivo web.xml de esta aplicación. Este archivo definirá el único servlet de la aplicación:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
</web-app>

El archivo anterior indica que el servlet denominado lstValeurs está asociado al archivo de clase gener3.class. Este archivo web.xml debe crearse y guardarse en la carpeta WEB-INF de la aplicación de la lista:

Image

La captura de pantalla anterior muestra una carpeta src en la que se ha colocado el archivo de origen gener3.java. Es posible que esta carpeta no exista. No tiene ninguna utilidad en esta demostración. Ya estamos listos para realizar las pruebas:

  • detenga y reinicie Tomcat para que vuelva a leer su archivo de configuración server.xml. Aquí estamos en Windows. En Unix, se puede forzar a Tomcat a volver a leer su archivo de configuración sin necesidad de detenerlo.
  • Con un navegador, acceda a URL en http://localhost:8080/liste/servlet/lstValeurs

Image

Se observa que el URL anterior contiene la palabra clave servlet, al igual que todos los URL de los servlets utilizados hasta ahora. Podemos prescindir de ella asociando, en el archivo web.xml de la aplicación «lista», el servlet lstValeurs a un modelo de URL (url-pattern):

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
</web-app>

En la etiqueta <servlet-mapping>, asociamos la ruta /valores al servlet lstValeurs definido en las líneas anteriores. Guardamos el nuevo archivo web.xml y solicitamos el URL http://localhost:8080/liste/valeurs:

Image

3.3.3. Implementación de las páginas públicas de una aplicación web

Acabamos de ver la implementación de una aplicación web compuesta por un único servlet. Una aplicación web puede tener numerosos componentes: servlets, páginas JSP, archivos HTML, applets de Java, etc. ¿Dónde se ubican estos elementos de la aplicación? Si <application> es la carpeta de la aplicación web definida por el atributo docBase de la aplicación en el archivo de configuración de Tomcat server.xml, hemos visto que los servlets se ubican en <application>\WEB-INF\classes. Los demás elementos de la aplicación pueden colocarse en cualquier lugar del árbol de carpetas de <application>, excepto en la carpeta WEB-INF. Consideremos la aplicación JSP listvaleurs.jsp que ya hemos estudiado:

Image

Esta página JSP se había almacenado en la carpeta <tomcat>\webapps\examples\jsp\perso\listvaleurs. Esta página podría ser un componente de la aplicación «lista» implementada anteriormente. Coloquemos el archivo listvaleurs.jsp directamente en la carpeta de esta aplicación:

Image

Recordemos la configuración de la aplicación liste en el archivo server.xml:

                <Context path="/liste" docBase="e:/data/serge/servlets/lstValeurs" />

Cualquier archivo URL que comience con la ruta /liste se considera parte de la aplicación liste y se buscará en la carpeta indicada. Busquemos el archivo URL http://localhost:8080/liste/listvaleurs.jsp con un navegador:

Image

Efectivamente, obtuvimos la página JSP esperada.

3.3.4. Parámetros de inicialización de un servlet

Hemos visto que un servlet se configura mediante el archivo <application>\WEB-INF\web.xml, donde <application> es la carpeta de la aplicación web a la que pertenece. Es posible incluir en este archivo parámetros de inicialización del servlet. Volvamos a nuestro servlet lstValeurs de la aplicación web liste, cuyo archivo de configuración era el siguiente:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
</web-app>

La clase asociada al servlet es la clase gener3. En el código fuente de esta se encuentra la definición de algunas constantes:

public class gener3 extends HttpServlet{
        // el título de la página
        private final String title="Génération d'un formulaire";
         // la base de datos de los valores de la lista
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";

Recordemos el significado de las cuatro constantes definidas anteriormente:

title
título del documento HTML generado por el servlet
DSNValeurs
nombre de la base de datos DSN donde el servlet busca datos
admDbValeurs
nombre de un usuario con derecho de lectura en la base anterior
mdpDbvaleurs
su contraseña

Si el administrador de la base de datos DSNValeurs cambia la contraseña del usuario admDbValeurs, se debe modificar y recompilar el código fuente del servlet. Esto no es muy práctico. El archivo de configuración del servlet web.xml nos ofrece una alternativa al permitir la definición de parámetros de inicialización del servlet con la etiqueta <init-param>:

    <init-param>
        <param-name>...</param-name>
        <param-value>...</param-value>
    </init-param>
<param-name>
permite definir el nombre del parámetro
<param-value>
define el valor asociado al parámetro anterior

El servlet tiene acceso a sus parámetros de inicialización mediante los siguientes métodos:

[Servlet].getServletConfig()
Método de la clase Servlet, de la cual deriva la clase HttpServlet utilizada para la programación web. Devuelve un objeto ServletConfig que da acceso a los parámetros de configuración del servlet.
[ServletConfig].getInitParameter("paramètre")
Método de la clase ServletConfig que devuelve el valor del parámetro de inicialización «paramètre»

Configuramos la aplicación liste con el siguiente nuevo archivo web.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
    <servlet>
      <servlet-name>lstValeurs2</servlet-name>
    <servlet-class>gener5</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
    <init-param>
        <param-name>DSNValeurs</param-name>
      <param-value>odbc-valeurs</param-value>
    </init-param>
    <init-param>
        <param-name>admDbValeurs</param-name>
      <param-value>admDbValeurs</param-value>
    </init-param>
    <init-param>
        <param-name>mdpDbValeurs</param-name>
      <param-value>mdpDbValeurs</param-value>
    </init-param>   
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
      <servlet-name>lstValeurs2</servlet-name>
    <url-pattern>/valeurs2</url-pattern>
  </servlet-mapping>
</web-app>

En la aplicación liste, definimos un segundo servlet llamado lstValeurs2 vinculado al archivo de clase gener5. Este último se ha ubicado en <application>\WEB-INF\classes:

Image

El servlet lstValeurs2 tiene cuatro parámetros de inicialización: title, DSNValeurs, admDbValeurs y mdpDbValeurs. Además, se ha definido el alias /valeurs2 para el servlet mediante la etiqueta <servlet-mapping>. De este modo, se podrá acceder al servlet lstValeurs2 de la aplicación liste a través de URL y http://localhost:8080/liste/valeurs2.

El código fuente del servlet se ha modificado de la siguiente manera para recuperar los parámetros de inicialización del servlet:

public class gener5 extends HttpServlet{
    // el título de la página
    private String title=null;
    // la base de datos de los valores de la lista
    private String DSNValeurs=null;
    private String admDbValeurs=null;
    private String mdpDbValeurs=null;
...............

         // inicialización del servlet
        public void init(){
             // se recuperan los parámetros de inicialización del servlet
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");
            DSNValeurs=config.getInitParameter("DSNValeurs");
            admDbValeurs=config.getInitParameter("admDbValeurs");
            mdpDbValeurs=config.getInitParameter("mdpDbValeurs");

             //¿Se han recuperado todos los parámetros?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // se llena la tabla de valores a partir de una base de datos ODBC
             // con el nombre DSN: DSNvaleurs
...............

Para probar el servlet, hay que reiniciar Tomcat para que tenga en cuenta el nuevo archivo de configuración web.xml de la aplicación liste. Desde un navegador, se solicita el URL de la servlet http://localhost:8080/liste/valeurs2:

Image

Si falta alguno de los parámetros de inicialización necesarios para el servlet en el archivo web.xml, se obtiene la siguiente página:

Image

3.3.5. Parámetros de inicialización de una aplicación web

En el ejemplo anterior, solo el servlet lstValeurs2 tiene acceso a los parámetros title, DSNValeurs, admDbValeurs y mdpDbValeurs. Podríamos imaginar que otro servlet de la misma aplicación, liste, necesite los datos de la misma base de datos que utiliza el servlet lstValeurs2. En ese caso, habría que redefinir los parámetros DSNValeurs, admDbValeurs y mdpDbValeurs en la sección de configuración del archivo web.xml de la nueva servlet. Otra solución es definir los parámetros comunes a varios servlets a nivel de la aplicación y no a nivel de los servlets. El nuevo archivo web.xml de la aplicación queda así:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <context-param>
      <param-name>DSNValeurs</param-name>
    <param-value>odbc-valeurs</param-value>
  </context-param>
  <context-param>
      <param-name>admDbValeurs</param-name>
    <param-value>admDbValeurs</param-value>
  </context-param>
  <context-param>
      <param-name>mdpDbValeurs</param-name>
    <param-value>mdpDbValeurs</param-value>
  </context-param>   

    <servlet>
      <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
    <servlet>
      <servlet-name>lstValeurs3</servlet-name>
    <servlet-class>gener6</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
      <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
      <servlet-name>lstValeurs3</servlet-name>
    <url-pattern>/valeurs3</url-pattern>
  </servlet-mapping>
</web-app>

El nuevo servlet se llama lstValeurs3, está vinculado al archivo de clase gener6 y se le ha asignado el alias /valeurs3 (servlet-mapping). El parámetro title es el único que se ha conservado en la definición del servlet. Los demás se han colocado en la configuración de la aplicación dentro de las etiquetas <context-param>. Esta etiqueta sirve para definir información específica de la aplicación y no de un servlet o una página JSP en particular. ¿Cómo accede el servlet de Java a estos parámetros, a menudo denominados parámetros de contexto? Los métodos disponibles para obtener la información de contexto son muy similares a los que se utilizan para obtener los parámetros de inicialización específicos de un servlet:

[Servlet].getServletContext()
método de la clase Servlet, de la cual deriva la clase HttpServlet utilizada para la programación web. Devuelve un objeto ServletContext que da acceso a los parámetros de configuración de la aplicación
[ServletContext].getInitParameter("paramètre")
método de la clase ServletContext que devuelve el valor del parámetro de inicialización «paramètre»

La clase gener6.java introduce únicamente las siguientes modificaciones al código Java de gener5.java utilizado anteriormente:

             // se recuperan los parámetros de inicialización del servlet
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");

            ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");

             //¿Se han recuperado todos los parámetros?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // se llena la tabla de valores a partir de una base de datos ODBC
             // con nombre DSN: DSNvaleurs
...............

El parámetro title específico del servlet se obtiene a través de un objeto ServletConfig. Los otros tres parámetros definidos a nivel de aplicación se obtienen a través de un objeto ServletContext. Compilamos esta clase y la colocamos, al igual que las demás, en <application>\WEB-INF\classes:

Image

Reiniciamos Tomcat para que reconozca el nuevo archivo web.xml de la aplicación y solicitamos el URL y el http://localhost:8080/liste/valeurs3:

Image

3.3.6. Parámetros de inicialización de una página JSP

Ya hemos visto cómo definir parámetros de inicialización para un servlet o una aplicación web. ¿Se puede hacer lo mismo para una página JSP? Volvamos al inicio del código de la página listvaleurs.jsp que ya hemos analizado:

<%@ page import="java.sql.*, java.util.*" %>

<%!
         // variables globales de la aplicación
         // el título de la página
        private final String title="Génération d'un formulaire";
         // la base de datos de los valores de la lista
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
.........

Las cuatro constantes title, DSNValeurs, admDbValeurs y mdpDbValeurs se encuentran definidas en el archivo web.xml de la aplicación. Las constantes DSNValeurs, admDbValeurs y mdpDbValeurs ya se han definido a nivel de la aplicación, por lo que podemos suponer que una página JSP que forme parte de esta aplicación tendrá acceso a ellas. Así es. Sabemos que la página JSP se traducirá a un servlet. Este tendrá acceso al contexto a través del método getServletContext(). Más complicado es el caso de la constante title. De hecho, la hemos definido a nivel de servlet y no a nivel de aplicación de la siguiente manera:

    <servlet>
      <servlet-name>lstValeurs3</servlet-name>
    <servlet-class>gener6</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
  </servlet>

Para la página JSP, la sintaxis anterior ya no es válida, ya que ya no existe el concepto de archivo de clase. Sin embargo, la sintaxis de configuración de una página JSP es muy similar a la de un servlet. Es la siguiente:

    <servlet>
      <servlet-name>JSPlstValeurs</servlet-name>
    <jsp-file>/listvaleurs2.jsp</jsp-file>
...
  </servlet>

De hecho, una página JSP se considera un servlet al que se le asigna un nombre (servlet-name). En lugar de asociar un archivo de clase a este servlet, se asocia el archivo fuente de la página JSP que se va a ejecutar (jsp-file). Así, las líneas anteriores definen un servlet llamado JSPlstvaleurs asociado a la página JSP /listvaleurs2.jsp. La ruta /listvaleurs2.jsp se mide con respecto a la raíz de la aplicación. Así, en el caso de nuestra aplicación liste, el archivo listvaleurs2.jsp se encontraría en la carpeta docBase (véase server.xml) de la aplicación liste:

Image

La configuración de la página JSP será la siguiente en el archivo web.xml de la aplicación:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <context-param>
      <param-name>DSNValeurs</param-name>
    <param-value>odbc-valeurs</param-value>
  </context-param>
  <context-param>
      <param-name>admDbValeurs</param-name>
    <param-value>admDbValeurs</param-value>
  </context-param>
  <context-param>
      <param-name>mdpDbValeurs</param-name>
    <param-value>mdpDbValeurs</param-value>
  </context-param>   
.......      
    <servlet>
      <servlet-name>JSPlstValeurs</servlet-name>
    <jsp-file>/listvaleurs2.jsp</jsp-file>
    <init-param>
        <param-name>JSPtitle</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
  </servlet>
..........
  <servlet-mapping>
      <servlet-name>JSPlstValeurs</servlet-name>
    <url-pattern>/jspvaleurs</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
........
</web-app>

La página JSP listvaleurs2.jsp se encuentra en la raíz de la aplicación liste y está asociada al nombre de servlet JSPlstValeurs (servlet-name), el cual a su vez está asociado al alias /jspvaleurs (servlet-mapping). De este modo, se podrá acceder a nuestra página JSP a través de URL y http://localhost:8080/liste/jspvaleurs.

La página inicial JSP, listvaleurs.jsp, se modifica a listvaleurs2.jsp y recupera sus cuatro parámetros de inicialización en el método jspInit():

<%!
         // variables globales de la aplicación
         // el título de la página
        private String title=null;
        // la base de datos de valores de lista
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
         // valores de la lista
        private String[] valeurs=null;
         // mensaje de error
        private String msgErreur=null;

         // inicialización de la página JSP: solo se ejecuta una vez
        public void jspInit(){

             // se recuperan los parámetros de inicialización del servlet
      ServletConfig config=getServletConfig();
            title=config.getInitParameter("JSPtitle");
      ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");

             //¿Se han recuperado todos los parámetros?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // Llena la tabla de valores a partir de una base de datos ODBC
             // con el nombre DSN: DSNvaleurs
..............

La página JSP recupera sus parámetros de inicialización de la misma manera que los servlets. El archivo anterior se guarda en la raíz de la aplicación web liste:

Image

Se reinicia el servidor Tomcat para obligarlo a volver a leer el nuevo archivo de configuración web.xml de la aplicación. A continuación, se puede solicitar el URL http://localhost:8080/liste/jspvaleurs:

Image

3.3.7. Colaboración entre servlets y páginas JSP dentro de una aplicación web

Cuando un cliente realiza una solicitud a un servidor web, la respuesta puede ser generada por varios servlets y páginas JSP. Hasta ahora, la respuesta había sido generada por un único servlet o una única página JSP. Hemos visto que la página JSP ofrece una mejor legibilidad de la estructura del documento HTML generado. Sin embargo, por lo general también contiene mucho código Java. Se puede mejorar esto al colocar

  • en uno o varios servlets el código Java que no genera el código HTML de la respuesta
  • en páginas JSP, el código de generación de los distintos documentos HTML enviados como respuesta al cliente

De esta manera, se espera mejorar la separación entre el código Java y el código HTML. Vamos a aplicar esta nueva estructura a nuestra aplicación liste: un servlet de Java lstValeurs4 se encargará de leer, al inicio, los valores de la base de datos y, posteriormente, de analizar las solicitudes de los clientes. Según el resultado de este análisis, la solicitud del cliente se redirigirá a una página de error erreur.jsp o a la página que muestra la lista de números liste.jsp. Por lo tanto, la aplicación liste estará compuesta por un servlet y dos páginas JSP.

¿Cómo puede un servlet pasar la solicitud que recibió de un cliente a otro servlet o a una página JSP? Utilizaremos los siguientes métodos:

[ServletContext].getRequestDispatcher(
String url)
método de la clase ServletContext que devuelve un objeto RequestDispatcher. El parámetro url es el nombre del URL al que se desea transmitir la solicitud del cliente. Este reenvío de la solicitud solo puede realizarse dentro de una misma aplicación. Por lo tanto, el parámetro url es una ruta relativa al árbol de directorios web de dicha aplicación.
[RequestDispatcher].forward
(ServletRequest request,
 ServletResponse response)
Método de la interfaz RequestDispatcher que transmite al método anterior URL la solicitud request del cliente y el objeto response que debe utilizarse para elaborar la respuesta.
[ServletRequest].setAttribute(String nom, Object obj)
Cuando un servlet o una página JSP pasa una solicitud a otro servlet o página JSP, por lo general necesita pasarle a este otra información además de la mera solicitud del cliente, información derivada de su propio procesamiento de la solicitud. El método setAttribute de la clase ServletRequest permite agregar atributos al objeto request del cliente en un formato similar a un diccionario de pares (atributo, valor), donde attribut es el nombre del atributo y valeur es cualquier objeto que represente el valor de este.
[ServletRequest].getAttribute(
String attribut)
permite recuperar los valores de los atributos de una solicitud. Este método será utilizado por el servlet o la página JSP a la que se le ha enviado una solicitud para obtener la información que se ha agregado en ella.

El servlet encargado de procesar el formulario se configurará de la siguiente manera en el archivo web.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <context-param>
      <param-name>DSNValeurs</param-name>
    <param-value>odbc-valeurs</param-value>
  </context-param>
  <context-param>
      <param-name>admDbValeurs</param-name>
    <param-value>admDbValeurs</param-value>
  </context-param>
  <context-param>
      <param-name>mdpDbValeurs</param-name>
    <param-value>mdpDbValeurs</param-value>
  </context-param>   

............
    <servlet>
      <servlet-name>lstValeurs4</servlet-name>
    <servlet-class>gener7</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
    <init-param>    
        <param-name>JSPerreur</param-name>
      <param-value>/erreur.jsp</param-value>
    </init-param>      
    <init-param>      
        <param-name>JSPliste</param-name>
      <param-value>/liste.jsp</param-value>
    </init-param>      
    <init-param>      
        <param-name>URLservlet</param-name>
      <param-value>/liste/valeurs4</param-value>
    </init-param>      
  </servlet>
...........
  <servlet-mapping>
      <servlet-name>lstValeurs4</servlet-name>
    <url-pattern>/valeurs4</url-pattern>
  </servlet-mapping>
.......
</web-app>

El servlet lstValeurs4 tendrá cuatro parámetros de inicialización propios:

title
el título del documento HTML que se generará
JSPerreur
el código de error de la página
JSPliste
el URL de la página JSP que presenta la lista de nombres
URLservlet
la URL asociada al atributo «action» del formulario que presenta la página JSPliste. Esta URL será la del servlet lstValeurs4

El servlet tendrá el alias /valeurs4 (servlet-mapping) y, por lo tanto, se podrá acceder a él a través de URL http://localhost:8080/liste/valeurs4. Está vinculada al archivo de clase gener7.java, cuyo código fuente completo es el siguiente:

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

public class gener7 extends HttpServlet{
        // el título de la página
        private String title=null;
        // la base de datos de los valores de la lista
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
         // las páginas de visualización JSP
        private String JSPerreur=null;
        private String JSPliste=null;
        // el URL del servlet
        private String URLservlet=null;
        // valores de la lista
        private String[] valeurs=null;
         // mensaje de error
        private String msgErreur=null;

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

            // se incluye «msgErreur,title» en los atributos de la solicitud
            request.setAttribute("msgErreur",msgErreur);
            request.setAttribute("title",title);
            request.setAttribute("URLservlet",URLservlet);

             // ¿Hubo un error al cargar el servlet?
            if(msgErreur!=null){
                 // se redirige a una página de error JSP
                getServletContext().getRequestDispatcher(JSPerreur).forward(request,response);
                 // fin
                return;
            }

             // No se produjo ningún error
             // se coloca la lista de valores en los atributos de la solicitud
            request.setAttribute("valeurs",valeurs);

             // se recupera la selección del usuario, si la hay
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";
            request.setAttribute("choix",choix);

             // se pasa el control a la página JSP de presentación de la lista
            getServletContext().getRequestDispatcher(JSPliste).forward(request,response);
            // fin
            return;
        }//GET

        // -----------------------------------------------------------------
        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

             // se redirige a GET
            doGet(request,response);
        }//POST

        // -----------------------------------------------------------------
         // inicialización del servlet
        public void init(){

             // se recuperan los parámetros de inicialización del servlet
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");
            JSPerreur=config.getInitParameter("JSPerreur");
            JSPliste=config.getInitParameter("JSPliste");
            URLservlet=config.getInitParameter("URLservlet");

            ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");


             //¿Se han recuperado todos los parámetros?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null || JSPerreur==null || JSPliste==null || URLservlet==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // se llena la tabla de valores a partir de una base de datos ODBC
             // con el nombre DSN: DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // conexión a la base de datos ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // objeto Statement
                st=connexion.createStatement();
                 // ejecución de la consulta SELECT para recuperar los valores
                rs=st.executeQuery("select valeur from Tvaleurs");
                // los valores se recuperan y se colocan en una tabla dinámica
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // se registra el valor en la lista
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                 // transformación de lista a matriz
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                 // problema
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//iniciar
    }//clase

La novedad en esta clase es que, en caso de error, la solicitud del cliente se reenvía a la página JSPerreur, y en caso contrario, a la página JSPliste. La clase no genera la respuesta por sí misma. De ello se encargan las páginas JSP, JSPerreur y JSPliste. Anteriormente, el servlet agregaba atributos (setAttribute) a la solicitud del cliente:

  • un mensaje de error msgErreur en caso de error en la página JSPerreur
  • los valores (valeurs)) que se mostrarán, el valor seleccionado (choix) por el usuario, el título (title) del formulario, el URL (URLservlet) del atributo «action» del formulario para la página JSPliste

Esta clase se compila y se incluye en las clases de la aplicación:

Image

La página JSP, que muestra un mensaje de error, está configurada de la siguiente manera:

    <servlet>
      <servlet-name>JSPerreur</servlet-name>
    <jsp-file>/erreur.jsp</jsp-file>
    <init-param>
        <param-name>mainServlet</param-name>
      <param-value>/valeurs4</param-value>
    </init-param>
  </servlet>
.........
<servlet-mapping>
      <servlet-name>JSPerreur</servlet-name>
    <url-pattern>/JSPerreur</url-pattern>
  </servlet-mapping>

El archivo JSP asociado a la página de error se llama erreur.jsp y se encuentra en la raíz de la aplicación:

Image

Su alias es /JSPerreur, lo que permite acceder a él a través de la ruta http://localhost:8080/liste/JSPerreur. Cuenta con un parámetro de inicialización llamado mainServlet, cuyo valor es el alias del servlet principal descrito anteriormente. Cabe señalar que este alias es relativo a la raíz de la aplicación liste,; de lo contrario, sería /liste/valeurs4.. El código de la página erreur.jsp es el siguiente:

<%
     // código de _jspService
   // se recupera el parámetro de inicialización mainServlet
  String servletListValeurs=config.getInitParameter("mainServlet");
   // se recupera el atributo msgErreur
  String msgErreur=(String)request.getAttribute("msgErreur");
   // ¿Es válido el atributo?
  if(msgErreur!=null){
%>
       <!-- código HTML -->
    <html>
        <head>
          <title>Erreur</title>
      </head>
      <body>
          <h3>Application indisponible (<%= msgErreur %>)</h3>
      </body>
    </html>
<%
    } else { // atributo msgErreur no válido: regreso al servlet principal
%>
    <jsp:forward page="<%= servletListValeurs %>" />  
<%    
  }
%>  

Normalmente, esta página debe ser llamada por el servlet anterior, el cual debe pasarle el atributo msgErreur. Sin embargo, nada impide llamarla directamente si se conoce su URL. Además, si se detecta que falta el atributo msgErreur, se pasa la solicitud al servlet principal. Aquí se utiliza una etiqueta específica para las páginas JSP, cuya sintaxis es:

<jsp:forward page="URL" />

donde URL es el URL del servlet al que se transfiere la solicitud del cliente. Si el atributo msgErreur está presente, se muestra la página de error.

La página JSP, que muestra la lista de números, está configurada de la siguiente manera:

    <servlet>
      <servlet-name>JSPliste</servlet-name>
    <jsp-file>/liste.jsp</jsp-file>
    <init-param>
        <param-name>mainServlet</param-name>
      <param-value>/valeurs4</param-value>
    </init-param>
.........
  <servlet-mapping>
      <servlet-name>JSPliste</servlet-name>
    <url-pattern>/JSPliste</url-pattern>
  </servlet-mapping>

El archivo JSP asociado a la página de error se llama liste.jsp y se encuentra en la raíz de la aplicación:

Image

El servlet tiene el alias /JSPliste, lo que permite acceder a él a través de la dirección http://localhost:8080/liste/JSPliste. Cuenta con un parámetro de inicialización llamado mainServlet, cuyo valor es el alias del servlet principal. El código de la página liste.jsp es el siguiente:

   <%-- página de visualización de la lista de valores --%>
  <%
       // código de jspService
       // se recupera el parámetro de inicialización
      String servletListValeurs=config.getInitParameter("mainServlet");

     // se recuperan los atributos de la solicitud proveniente del servlet principal
    String title=(String) request.getAttribute("title");
    String[] valeurs=(String[]) request.getAttribute("valeurs");
    String choix=(String) request.getAttribute("choix");
    String URLservlet=(String) request.getAttribute("URLservlet");

     // ¿Atributos válidos?
    if(title==null || valeurs==null || choix==null){
         // hay un atributo inválido; se pasa el control al servlet
   %>
   <jsp:forward page="<%= servletListValeurs %>" />
    <%
    }//si
  %>

  <%-- código HTML --%>  
      <html>
        <head>
          <title><%= title %></title>
      </head>
      <body>
          <h3>Choisissez une valeur</h3>
          <form method="POST" action="<%= URLservlet %>">
            <select name="cmbValeurs">
              <%
                 // visualización dinámica de los valores
                          String selected="";
                          for (int i=0;i<valeurs.length;i++){
                              if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                              out.println("<option "+selected+">"+valeurs[i]+"</option>");
                          }//para
            %>
          </select>
          <input type="submit" value="Envoyer">
        </form>
                <%
             // ¿Se seleccionó algún valor?
                    if(! choix.equals("")){
                // se muestra la elección del usuario
                %>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
                <%        
                    }//si
                %>        
      </body>
    </html>

Esta página funciona igual que la página erreur.jsp. Normalmente debe ser llamada por el servlet /liste/valeurs4 y recibir los atributos title, valeurs y choix. Si falta alguno de estos parámetros, se pasa el control al servlet URLservlet (/liste/valeurs4). Si todos los parámetros están presentes, se muestra la lista de números, así como el número elegido por el usuario, en caso de que haya elegido uno.

Si se solicita el URL del servlet principal, se obtiene el siguiente resultado:

Image

con el siguiente código fuente (View/Source):

<html>
    <head>
      <title>Génération d'un formulaire</title>
  </head>
  <body>
      <h3>Choisissez une valeur</h3>
      <form method="POST" action="/liste/valeurs4">
        <select name="cmbValeurs">
          <option >0</option>
        <option >1</option>
        <option >2</option>
        <option >3</option>
        <option >4</option>
        <option >6</option>
        <option >5</option>
        <option >7</option>
        <option >8</option>
        <option >9</option>
      </select>
      <input type="submit" value="Envoyer">
    </form>

  </body>
</html>

Este documento HTMl fue generado por la página JSP liste.jsp. Se observa que los atributos title, valeurs y URLservlet se han recuperado correctamente.

Para concluir sobre la colaboración entre servlets y páginas JSP, observamos que las páginas JSP son aquí muy breves y carecen del código Java que no contribuye directamente a crear la respuesta HTML. De este modo, la estructura de los documentos generados es más visible.

3.4. Ciclo de vida de los servlets y las páginas JSP

3.4.1. El ciclo de vida

Aquí nos enfocamos en el ciclo de vida de los servlets. El de las páginas JSP se deriva de este. Consideremos un servlet al que se le llama por primera vez. El servidor web crea entonces una instancia de la clase y la carga en memoria. A continuación, esta atenderá la solicitud. Una vez hecho esto, el servlet no se descarga de la memoria. Permanece allí para atender otras solicitudes con el fin de optimizar los tiempos de respuesta del servidor. Se liberará cuando haya transcurrido un tiempo suficientemente largo sin que haya atendido nuevas solicitudes. Este tiempo suele ser configurable en el servidor web.

Mientras permanece en memoria, el servlet puede atender varias solicitudes simultáneamente. El servidor web crea un hilo por cada solicitud, y todos ellos utilizan la misma instancia del servlet:

Todos los hilos anteriores comparten las variables de la instancia del servlet. Puede ser necesario sincronizar los hilos para evitar la corrupción de los datos del servlet. Volveremos sobre este tema más adelante.

Al cargar un servlet, se ejecuta un método específico del servlet:

public void init() throws ServletException{
}

Para una página JSP, es el método


  public void jspInit(){
  }

el que se ejecuta. A continuación se muestra un ejemplo de la página JSP que utiliza el método jspInit:

<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>

<%!
   // variables y métodos globales de la página JSP

   // variable de instancia
  int compteur;

   // método para incrementar el contador  
  public int getCompteur(){
     // se incrementa el contador
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
     // se restablece
    return compteur;
  }

   // el método que se ejecuta al cargar la página por primera vez
  public void jspInit(){
     // inicializar contador
    compteur=100;
  }
%>

La página anterior JSP inicializa un contador en jspInit con el valor 100. Cada solicitud posterior al servlet incrementa y luego muestra el valor de este contador:

La primera vez:

Image

La segunda vez:

Image

Como se puede ver arriba, entre las dos solicitudes, el servlet no se descargó; de lo contrario, el contador habría estado en 101 en la segunda solicitud. Cuando el servlet se descarga, el método

public void destroy(){
}

se ejecuta si existe. Para las páginas JSP, es el método


  public void jspDestroy(){
  }

En estos métodos, se podrán, por ejemplo, cerrar conexiones a bases de datos, conexiones que se habrán abierto en los métodos init correspondientes.

3.4.2. Sincronización de las solicitudes con un servlet

Volvamos a la página JSP anterior, que incrementa un contador y lo devuelve al cliente web. Supongamos que hay dos solicitudes simultáneas. En ese caso, se crean dos subprocesos para ejecutarlas, los cuales utilizarán la misma instancia de servlet y, por lo tanto, en este caso, el mismo contador. Recordemos el código que incrementa el contador:


  public int getCompteur(){
     // se incrementa el contador
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
     // se devuelve
    return compteur;
  }

El incremento del contador se ha escrito deliberadamente de manera incorrecta. Supongamos que la ejecución de los dos hilos se lleva a cabo de la siguiente manera:

 
  1. En el momento T1, se ejecuta el hilo TH1. Lee el contador (=145) en myCompteur, luego se interrumpe y pierde el control del procesador. Por lo tanto, no tuvo tiempo de incrementar myCompteur ni de copiar el nuevo valor en compteur.
  2. En el momento de T2, se ejecuta el hilo TH2. Lee el contador (=145) en myCompteur, luego se interrumpe y pierde el procesador. Cabe señalar que los dos hilos tienen variables myCompteur diferentes. Solo comparten las variables de instancia, es decir, aquellas que son globales a los métodos.
  3. En el momento T3, el hilo TH1 recupera el control y finaliza. Por lo tanto, devuelve 146 a su cliente.
  4. En el momento T4, el hilo TH2 recupera el control y finaliza. También devuelve 146 a su cliente, cuando debería haber devuelto 147.

Aquí tenemos un problema de sincronización de subprocesos. Cuando TH1 quiere incrementar el contador, habría que impedir que cualquier otro subproceso lo haga también. Para poner de manifiesto este problema, reescribimos la página JSP de la siguiente manera:

<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>

<%!
   // variables y métodos globales de la página JSP

   // variable de instancia
  int compteur;

   // método para incrementar el contador  
  public int getCompteur(){
     // se lee el contador
    int myCompteur=compteur;
     // se detiene durante 10 segundos
    try{
      Thread.sleep(10000);
    }catch (Exception ignored){}
     // se incrementa el contador
    compteur=myCompteur+1;
     // se devuelve
    return compteur;
  }

   // el método que se ejecuta al cargar la página por primera vez
  public void jspInit(){
     // inicializar contador
    compteur=100;
  }
%>

Aquí hemos forzado que el hilo se detenga 10 segundos después de leer el contador. Por lo tanto, debería perder el control del procesador y otro hilo podría leer a su vez un contador que no se ha incrementado. Cuando realizamos consultas con un navegador, no notamos ninguna diferencia, salvo la espera de 10 segundos antes de obtener el resultado.

Image

Ahora bien, si abrimos dos ventanas del navegador y realizamos dos solicitudes con un intervalo de tiempo lo suficientemente corto:

Image

Image

Obtenemos el mismo valor del contador. Podemos resaltar mejor el problema con un cliente programado en lugar de uno manual, como lo es el navegador. A continuación, se muestra un cliente en Perl que se invoca de la siguiente manera:

programa URL N

donde

URL es el URL del servlet de conteo

N el número de solicitudes que se deben realizar a esta servlet

Estos son los resultados obtenidos para 5 solicitudes, que muestran claramente el problema de la mala sincronización de los hilos: todas obtienen el mismo valor del contador.


DOS>java clientCompteurJSP http://localhost:8080/examples/jsp/perso/contador/compteur2.jsp 5
Compteur=121
Compteur=121
Compteur=121
Compteur=121
Compteur=121

El código del cliente Java es el siguiente.

import java.net.*;
import java.util.regex.*;
import java.io.*;

public class clientCompteurJSP {

    public static void main(String[] params){

         // datos
        String syntaxe="Syntaxe : pg URL nbAppels";

         // verificación de parámetros
        if(params.length!=2){
            System.err.println(syntaxe);
            System.exit(1);
        }//si
         // URL
        URL urlCompteur=null;
        try{
            urlCompteur=new URL(params[0]);
            String query=urlCompteur.getQuery();
            if(query!=null) throw new Exception();
        }catch (Exception ex){
            System.err.println(syntaxe);
            System.err.println("URL ["+params[0]+" incorrecte");
            System.exit(2);
        }//try-catch
         // número de llamadas
        int nbAppels=0;
        try{
            nbAppels=Integer.parseInt(params[1]);
            if(nbAppels<=0) throw new Exception();
        }catch(Exception ex){
            System.err.println(syntaxe);
            System.err.println("Nombre d'appels ["+params[1]+" incorrect");
            System.exit(3);
        }//try-catch

         // los parámetros son correctos; se pueden establecer las conexiones con el URL
        try{
            getCompteurs(urlCompteur,nbAppels);
        }catch(Exception ex){
            System.err.println(syntaxe);
            System.err.println("L'erreur suivante s'est produite : "+ex.getMessage());
            System.exit(4);
        }//try-catch
    }//main

    private static void getCompteurs (URL urlCompteur, int nbAppels)
            throws Exception {
         // realiza nbAppels en el URL urlCompteur
         // muestra cada vez el valor del contador devuelto por el servidor web


         // se extrae de urlCompteur la información necesaria para conectarse al servidor de impuestos
        String path=urlCompteur.getPath();
        if(path.equals("")) path="/";
        String host=urlCompteur.getHost();
        int port=urlCompteur.getPort();
        if(port==-1) port=urlCompteur.getDefaultPort();

         // se realizan las llamadas a URL
        Socket[] clients=new Socket[nbAppels];
        for(int i=0;i<nbAppels;i++){
             // se conecta al servidor
            clients[i]=new Socket(host,port);
             // se crea un flujo de escritura hacia el servidor
            PrintWriter OUT=new PrintWriter(clients[i].getOutputStream(),true);
             // se solicita el URL - envío de los encabezados HTTP
            OUT.println("GET " + path + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            OUT.println("Connection: close");
            OUT.println("");
        }//para

         // datos locales
        String réponse=null;                        // respuesta del servidor
         //: el patrón buscado en la respuesta HTML del servidor
        Pattern modèleCompteur=Pattern.compile("^\\s*Compteur= (\\d+)");
         // el patrón de una respuesta correcta
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // el resultado de la comparación con el modelo
        Matcher résultat=null;

        for(int i=0;i<nbAppels;i++){
             // cada cliente lee la respuesta que le envía el servidor

             // se crean los flujos de entrada y salida del cliente TCP
            BufferedReader IN=new BufferedReader(new InputStreamReader(clients[i].getInputStream()));

             // se lee la primera línea de la respuesta
            réponse=IN.readLine();
             // se compara la línea HTTP con el patrón de la respuesta correcta
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                 // hay un problema con URL
                throw new Exception("Client n° " + i + " - Le serveur a répondu : URL ["+ urlCompteur + "] inconnue");
            }//si

             // se lee la respuesta hasta el final de los encabezados
            while((réponse=IN.readLine())!=null && ! réponse.equals("")){
            }//mientras

             // se han terminado los encabezados HTTP: pasamos al código HTML
             // para recuperar el valor del contador
            boolean compteurTrouvé=false;
            while((réponse=IN.readLine())!=null){
                 // comparamos la línea con el modelo del contador
                if(! compteurTrouvé){
                    résultat=modèleCompteur.matcher(réponse);
                    if(résultat.find()){
                         // contador encontrado
                        System.out.println("Compteur="+résultat.group(1));
                        compteurTrouvé=true;
                    }//si
                }//si
            }//mientras

             // se terminó
            clients[i].close();
        }//for
    }//getCompteurs

}//clase

Explicemos el código anterior:

  • el programa admite dos parámetros:
    • el URL de la página JSP del contador
    • el número de clientes que se deben crear para este URL
  • por lo tanto, el programa comienza por verificar la validez de los parámetros: que haya dos, que el primero se parezca sintácticamente a un URL y que el segundo sea un número entero >0. Para verificar que el URL sea sintácticamente correcto, se utiliza la clase URL y su constructor URL (String), que crea un objeto URL a partir de una cadena de caracteres como http://istia.univ-angers.fr. Se lanza una excepción si la cadena de caracteres no es un URL sintácticamente válido. Esto nos permite verificar la validez del primer parámetro.
  • Una vez verificados los parámetros, se pasa el control al procedimiento getCompteurs. Este creará clientes nbAppels, los cuales se conectarán todos al mismo tiempo (o casi) al URL urlCompteur.
  • El puerto y la máquina a la que deben conectarse los clientes se obtienen de URL, urlCompteur y [URL].getHost() permite obtener el nombre de la máquina y [URL].getPort() permite obtener el puerto.
  • Un primer bucle permite a cada cliente:
    • conectarse al servidor web
    • solicitarle el URL urlCompteur

En este bucle, el cliente no espera la respuesta del servidor. De hecho, el objetivo es hacer que el servidor reciba solicitudes casi simultáneas.

  • Un segundo bucle permite a cada cliente recibir y procesar la respuesta que le envía el servidor. El procesamiento consiste en encontrar en la respuesta la línea que contiene el valor del contador y mostrarla.

Para resolver el problema señalado anteriormente (se envía el mismo contador a los cinco clientes), debemos sincronizar los subprocesos del servicio de conteo en un mismo objeto antes de ingresar a la sección crítica de lectura y actualización del contador. La nueva página JSP es la siguiente:


<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>

<%!
   // variables y métodos globales de la página JSP
  
   // variables de instancia
  int compteur;
  Object verrou=new Object();
  
   // método para incrementar el contador  
  public int getCompteur(){
  
     // se sincroniza la sección crítica
    synchronized(verrou){
       // se lee el contador
      int myCompteur=compteur;
       // se detiene durante 10 segundos
      try{
        Thread.sleep(10000);
      }catch (Exception ignored){}
       // se incrementa el contador
      compteur=myCompteur+1;
    }//sincronizado
     // se devuelve
    return compteur;
  }//getCompteur

   // el método que se ejecuta al cargar la página por primera vez
  public void jspInit(){
     // inicializar contador
    compteur=100;
  }
%>

Al ejecutarlo, se obtienen los siguientes resultados:

dos>c:\perl\bin\perl.exe client2.pl http://localhost:8080/ejemplos/jsp/perso/contador/compteur3.jsp 5
    Compteur= 104
    Compteur= 106
    Compteur= 105
    Compteur= 107
    Compteur= 108

La documentación indica que el servidor web a veces puede crear varias instancias de un mismo servlet. En este caso, la sincronización anterior ya no funciona porque la variable verrou es local a una instancia y, por lo tanto, las demás instancias no la reconocen. Lo mismo ocurre con la variable compteur. Para que sean globales para todas las instancias, se escribirá:


// variable de clase
  static int compteur;
  static Object verrou=new Object();

El resto del código no cambia.