Skip to content

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

En este capítulo se incluyen varios ejemplos de servlets y páginas JSP. Se han probado con el servidor Tomcat, que funciona en el puerto 8080. Siguiendo 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 proceden de los ejemplos de Tomcat. Para probarlos, basta con iniciar Tomcat, solicitar la dirección URL http://localhost:8080 con un navegador y seguir el enlace de los servlets.

3.1. Servlets 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 se incluye de serie con jdk. En ese caso, se puede descargar directamente desde el sitio web de Sun.

  • Un servlet deriva de la clase HttpServlet

public class HelloWorld extends HttpServlet {
  • Una solicitud GET realizada 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 realizada 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 objeto que nos da acceso a la solicitud realizada por el cliente web. La respuesta del servlet se realizará 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 se limita a llamar 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:

  • Son el objeto request y su método getHeaderNames los que nos permiten acceder a los encabezados http enviados por el navegador en forma de enumeración:
        Enumeration e = request.getHeaderNames();
  • El método request.getHeader("entête") permite obtener un encabezado http concreto. El ejemplo anterior nos ofrece 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 reproducen 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
formatos MIME aceptados por el navegador. Así, image/gif significa que el navegador sabe 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 sabe 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 dispone de un mismo documento en varios idiomas, enviará aquel cuyo idioma sea aceptado por el navegador.
Referer
la URL que ha sido solicitada 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 servido 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 pedirlas sin necesidad de crear una nueva conexión. Será el navegador el que tomará la iniciativa de cerrar la conexión cuando haya recibido todos los elementos de la página.

3.1.4. Recuperar 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 se obtiene aquí 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 incluye 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 utilizado por el navegador para realizar su solicitud
getRemoteHost()
el nombre del equipo cliente desde el que el navegador ha realizado la solicitud
getRemoteAddr()
la dirección IP de esta 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()
el version del protocolo http solicitado por el navegador
getRequestURI()
la URI solicitada por el navegador. Corresponde a la parte de URL situada después de la identificación hote:port en http://host:puerto/URI

3.1.5. Crear un servlet con JBuilder, implementarlo con Tomcat

A continuación describimos cómo crear y ejecutar un servlet Java. Utilizaremos dos herramientas: JBuilder para compilar el servlet y Tomcat para ejecutarlo. Tomcat podría bastar por sí solo. 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 en primer lugar 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);
    }

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

Image

En la sección «Configuración de JDK» anterior, normalmente aparece en el campo «Name» la versión 1.3.1 de JDK. Si tiene un JDK más reciente, utilice el botón Change para indicar el directorio de instalación de este último. En el ejemplo anterior, se ha designado 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 (Class, Source, Documentation) 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 añadir otras bibliotecas de clases, se utiliza el botón Add y se indican los archivos .jar adicionales que se desean 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, añadiremos estas bibliotecas, una a una, a la lista de bibliotecas exploradas por JBuilder:

Image

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

  • Ahora puede 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 .class de los servlets deben colocarse en <tomcat>\webapps\examples\WEB-INF\classes (Tomcat 4.x).
  • Compruebe que Tomcat está en ejecución y, con un navegador, solicite el URL 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 realidad, a menudo se toman 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 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 ha enviado. Se trata de un caso habitual. 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 envían 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 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);
            // combo
            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 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, contenido que en este caso proviene 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 realizada por 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 terminar, cabe señalar lo siguiente:

  • el navegador envía los valores del formulario al servlet que lo ha generado, ya que la etiqueta <form> no tiene el atributo <action>. En este caso, el navegador envía los datos introducidos 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 modificándolo de la siguiente manera. El formulario propuesto sigue siendo el mismo:

Image

¿Es diferente la respuesta?

Image

En la respuesta, se devuelve el formulario, indicando debajo el número elegido por el usuario. 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 hacer 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 posible elección del usuario
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

             // se 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);
            // combo
            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 elecció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 lo hace todo: elabora el formulario que envía al cliente y procesa los valores que este devuelve. Los puntos a tener en cuenta son los siguientes:

  • se comprueba si el parámetro cmbValeurs tiene un valor.
  • Si es así, al elaborar 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, con la contraseña mdpDbValeurs
  • la base tiene una única 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 MySQL dbValeurs ha sido habilitada por un controlador ODBC para MySQL. Su nombre DSN (Data Fuente Name) es odbc-valores. 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 indica al cliente el tipo de documento enviado
            response.setContentType("text/html");
             // flujo de salida
            PrintWriter out=response.getWriter();

            // ¿Se ha inicializado correctamente el servlet?
            if (msgErreur!=null){
                 // se ha producido 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;
            }//if

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

             // se envía el formulario
             // inicio
            out.println(HTML1);
            // combo
            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>");
            }//for
            out.println("</select>");
            // continuación del formulario
            out.println(HTML2);
            if(! choix.equals("")){
                // se muestra la elecció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(){
             // rellena la tabla de valores a partir de una base de datos ODBC
             // de nombre DSN: DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // conexión a la base ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // objeto Statement
                st=connexion.createStatement();
                // exécution requête select pour récupérer les valeurs
                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 guarda 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){}
            }//intentar
        }//init
    }//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 durante la carga inicial del 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 este modo, responde más rápidamente a las solicitudes de los clients.
  3. En nuestro servlet, hay que 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 este modo, el servlet solo accede a la base de datos una vez, en el momento de su carga inicial, y no en 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 comprueba 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 clásico 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.

Cuando se ejecuta el servlet y 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 se elige el número 6 y se «envía»:

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 gestionado 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 introducido es este:

Image

y se pulsa el botón Enviar (de 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 los valores introducidos 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
    {
      // se empieza por recuperar 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 ver 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 de nuestro ejemplo, en el que se han seleccionado los elementos liste1 y liste3. Los valores de lst2 han sido transmitidos por el navegador al servidor en forma de lst2=liste1&lst2=liste3. El servlet 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 multilínea. request.getParameter("areaSaisie") devuelve el contenido del campo en forma de una única 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");

Recupera 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 con JBuilder tal y como se ha explicado anteriormente
  • colocado la clase generada en <tomcat>\webapps\examples\WEB-INF\classes, donde <tomcat> es el directorio de instalación de Tomcat.
  • solicité el URL http://localhost:81/html/balises2.htm, cuyo código se ha presentado anteriormente
  • rellené el formulario y pulsé el botón Envoyer.

3.1.6.5. Recuperar los encabezados HTTP de un cliente web

Retomamos el mismo ejemplo que antes, pero en respuesta al cliente web que ha enviado 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 Java llamado headers ubicado en <tomcat>\webapps\examples\WEB-INF\classes. El servlet headers se ha creado y compilado 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 la naturaleza del documento
        response.setContentType("text/html");
       // se obtiene un flujo de escritura
        PrintWriter out = response.getWriter();
      // visualización de 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 ejecuta Envoyer sin modificar el formulario. Se obtiene la siguiente respuesta:

Image

Obsérvese el parámetro URL presente en el campo Address del navegador, que muestra la forma (GET) utilizada para transmitir los parámetros. Repetimos 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. Por otra parte, 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 escribir 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 los 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 son accesibles siguiendo el enlace JSP de la página de inicio de Tomcat:

3.2.1. Recuperar información del entorno

Retomamos aquí 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 sustituye 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 ya visto en el estudio de los servlets. Se trata, por tanto, de un objeto HttpServletRequest. De este modo, la etiqueta <%= request.getRemoteHost() %> se sustituirá en el código HTML por el nombre del equipo del cliente web que ha realizado la solicitud.
  • Se puede llegar al mismo resultado con un servlet, pero aquí la estructura de la página web es más evidente.

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 al 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 volvemos a encontrar la etiqueta <%= expresión %>, ya vista en el ejemplo anterior, aparece 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) será objeto de una conversión a servlet Java. Se encerrará en un único método, denominado método principal de la página JSP. Por eso, las variables Java declaradas al principio de la página JSP son conocidas en las demás partes del código JSP que salpican el código HTML: estas variables y partes del código formarán parte del mismo método 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, hay 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 con 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 muestra 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 de la página JSP. Por ejemplo:
import="java.util.*,java.sql.*" para especificar las bibliotecas necesarias para la página JSP
extends="unaClaseParent" para derivar la página JSP de otra clase

3.2.4. Los objetos implícitos JSP

En los ejemplos anteriores, nos hemos encontrado con dos objetos no declarados: request y out. Se trata de dos de los objetos que se definen automáticamente en el servlet en el que se convierte la página JSP. Se denominan 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 desde el que se accede 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 a partir 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 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 URL 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. Vemos 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 out. Este es el que permitirá enviar código HTML al cliente mediante 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 situado 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 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
%>  

<%
   // 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 acceden 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, conocida en esta
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 aquí. 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 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

    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 ver arriba, el código Java que se encontraba entre las etiquetas JSP <%! .. %> se ha incluido íntegramente y no forma parte del método principal _jspService del servlet. Las variables declaradas en esta parte son, por 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 duda de dónde ubicar la página en el árbol de directorios del servidor. Existen diferentes formas de hacerlo, sobre las que volveremos más adelante. Por el momento, 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, que luego es compilado por Tomcat cuando un navegador solicita la página URL de la página JSP. Pueden aparecer 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 erróneo (errores en las etiquetas jsp utilizadas, por ejemplo)
  2. el código Java incluido en la página JSP es erróneo

La primera causa se puede descartar comprobando el código JSP de la página. La segunda se puede eliminar comprobando 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 posibilidades de depuración más avanzadas que las de Tomcat.

3.2.8. Ejemplos

Retomamos el ejemplo ya tratado 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, hemos recuperado el código del servlet y lo hemos modificado de la siguiente manera:

  • se ha conservado 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 de código HTML y 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(){
             // rellena 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 ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // objeto Statement
                st=connexion.createStatement();
                // exécution requête select pour récupérer les valeurs
                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 guarda 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
        }//init
   
%>

<%
     // código de _jspService ejecutado en cada solicitud del cliente
   // ¿Se ha producido 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;
  }//if
  
     // se recupera la selección que haya realizado el usuario
    String choix=request.getParameter("cmbValeurs");
    if(choix==null) choix="";
%>
  
   <%-- sin errores: 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 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>");
                          }//for
            %>
          </select>
          <input type="submit" value="Envoyer">
        </form>
<%
         // ¿se había seleccionado algún valor?
                if(! choix.equals("")){
        // se muestra la elección del usuario
%>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
<%        
                }//if
%>        
      </body>
    </html>

Cabe destacar los siguientes puntos:

  • las instrucciones import del servlet han sido objeto 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 tienen la misma función. Por lo tanto, aquí se ha reproducido í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 reproducido tal cual. Se trata esencialmente de las variables title, valores y msgErreur, que se utilizan posteriormente en el código JSP.
  • Las etiquetas <% ... %> enmarcan el código Java que se incluirá en el método _jspService, ejecutado en el momento de una solicitud de un cliente.
  • Al igual que en el servlet, el método _jspService comenzará por comprobar el valor de la variable msgErreur para saber 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, comprueba si el usuario ha seleccionado un número, en cuyo caso muestra dicho 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 sigue habiendo 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, de modo que la página JSP solo conserve el código HTML y JSP. De este modo, 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. Aunque 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 describen 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. Entonces se podía acceder a ellas a través de URL http://localhost:8080/examples/servlet/nomServlet
  • las páginas JSP en el árbol <tomcat>\webapps\examples\jsp. Entonces 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 (eXtended Markup Language). 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. Así, 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 HTML <br> no es una etiqueta XML válida. Debería escribirse <br/> para que lo sea, a fin de cumplir con la regla que establece que toda etiqueta XML 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 son capaces de mostrar archivos XML. Así, si llamamos a personne.xml al documento XML presentado en el ejemplo anterior 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 a 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 reside en esta propiedad: es fácil recuperar la estructura y el contenido de un documento XML. Esto se hace con un programa llamado analizador sintáctico XML. Los documentos XML tienden a convertirse en el estándar en los intercambios 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 crea a partir de la información contenida en una base de datos DB-A. La máquina B 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 recupera tanto la estructura como los datos (tal y como lo hizo IE6 en nuestro ejemplo). A continuación, puede almacenar los datos recibidos en la base DB-B

No diremos nada más sobre el lenguaje XML, que merecería un libro por sí solo.

Aquí, pues, 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. De nuevo, tenemos un nombre relativo: webapps. 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. Se trata, por tanto, de la carpeta <tomcat>\webapps\examples. Veamos más de cerca esta carpeta:

Image

En ella 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. Nos limitaremos a fijarnos 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 el servlet servletToJsp al servidor Tomcat?

  • El navegador solicita el 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 esta aplicación. Como hemos visto anteriormente, es en la carpeta <tomcat>\webapps\examples.
  • Tomcat utiliza el resto de la ruta del servlet para localizarlo en 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 anterior). 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 ya estudiado que presentaba al usuario una lista de números entre los que elegía uno. A continuación, el servlet le confirmaba el número que había elegido:

Image

Como muestra 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 muestra 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 encuentra nada que haga referencia a un servlet gener3. ¿Cómo la ha encontrado entonces Tomcat? Tras haber 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 anterior:

Image

Configuramos la aplicación web «liste» añadiendo en el archivo server.xml las siguientes líneas 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 fuente gener3.java. Es posible que esta carpeta no exista. No tiene ninguna utilidad en la demostración actual. 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, solicite el URL 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 servlets utilizados hasta ahora. Podemos prescindir de él asociando, en el archivo web.xml de la aplicación, el servlet lstValeurs a un patrón 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 formada por un único servlet. Una aplicación web puede tener numerosos componentes: servlets, páginas JSP, archivos HTML, applets Java, etc. ¿Dónde se colocan 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 server.xml de configuración de Tomcat, hemos visto que los servlets se colocaban 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 «liste» desplegada 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 URL que comience por la ruta /liste se considera parte de la aplicación liste y se buscará en la carpeta indicada. Solicitemos el URL http://localhost:8080/liste/listvaleurs.jsp con un navegador:

Image

Hemos obtenido correctamente 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 ODBC de la que el servlet extrae los datos
admDbValeurs
nombre de un usuario con derechos de lectura sobre la base anterior
mdpDbvaleurs
su contraseña

Si el administrador de la base de datos DSNValeurs cambia la contraseña del usuario admDbValeurs, hay que modificar el código fuente del servlet y volver a compilarlo. Esto no resulta 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 que 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 colocado en <application>\WEB-INF\classes:

Image

El servlet lstValeurs2 tiene cuatro parámetros de inicialización: title, DSNValeurs, admDbValeurs, mdpDbValeurs. Además, se ha definido el alias /valeurs2 para el servlet mediante la etiqueta <servlet-mapping>. Por lo tanto, se podrá acceder al servlet lstValeurs2 de la aplicación liste a través de URL 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 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 rellena la tabla de valores a partir de una base de datos ODBC
             // con 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. Con un navegador, se solicita el URL del 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, necesitara 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, mdpDbValeurs en la sección de configuración del archivo web.xml del nuevo 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 ha asociado al 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 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 que 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 en el 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 rellena la tabla de valores a partir de una base de datos ODBC
             // denominada 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 tenga en cuenta el nuevo archivo web.xml de la aplicación y solicitamos el URL http://localhost:8080/liste/valeurs3:

Image

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

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 principio del código de la página listvaleurs.jsp que ya hemos estudiado:

<%@ 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";
.........

Encontramos las cuatro constantes title, DSNValeurs, admDbValeurs y mdpDbValeurs definidas en el archivo web.xml de la aplicación. Las constantes DSNValeurs, admDbValeurs y mdpDbValeurs se han definido ahora a nivel de 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á en un servlet. Este tendrá acceso al contexto a través del método getServletContext(). Más delicado 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 asimila a 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), que a su vez está asociado al alias /jspvaleurs (servlet-mapping). De este modo, nuestra página JSP será accesible a través de URL http://localhost:8080/liste/jspvaleurs.

La página JSP inicial 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 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;
            }

             // se rellena la tabla de valores a partir de una base de datos ODBC
             // con 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 envía una solicitud a un servidor web, la respuesta puede generarse mediante varios servlets y páginas JSP. Hasta ahora, la respuesta se generaba mediante un único servlet o página JSP. Hemos visto que la página JSP ofrecía 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 la situación colocando

  • 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 en respuesta al cliente

De este modo, 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 Java lstValeurs4 se encargará de leer al inicio los valores de la base de datos y, a continuación, de analizar las solicitudes de los clients. En función del resultado de este análisis, la solicitud del cliente se redirigirá a una página de error erreur.jsp o a la página de visualización de la lista de nombres liste.jsp. Por lo tanto, la aplicación liste estará formada por un servlet y dos páginas JSP.

¿Cómo puede un servlet pasar la solicitud que ha recibido 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 quiere transmitir la solicitud del cliente. Este paso 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 web de esta aplicación.
[RequestDispatcher].forward
(ServletRequest request,
 ServletResponse response)
método de la interfaz RequestDispatcher que transmite a la 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 trabajo sobre la solicitud. El método setAttribute de la clase ServletRequest permite añadir 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 ha reenviado una solicitud para obtener la información que se ha añadido 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 va a generar
JSPerreur
el URL de la página de error JSP
JSPliste
el URL de la página JSP que muestra la lista de nombres
URLservlet
la URL asociada al atributo action del formulario presentado por la página JSPliste. Esta URL será la del servlet lstValeurs4

El servlet tendrá el alias /valeurs4 (servlet-mapping) y, por lo tanto, será accesible 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 valores de 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 incluyen msgErreur y title en los atributos de la solicitud
            request.setAttribute("msgErreur",msgErreur);
            request.setAttribute("title",title);
            request.setAttribute("URLservlet",URLservlet);

             // ¿Se ha producido 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 ha producido ningún error
             // se coloca la lista de valores en los atributos de la solicitud
            request.setAttribute("valeurs",valeurs);

             // se recupera la posible elección del usuario
            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 rellena 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 ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // objeto Statement
                st=connexion.createStatement();
                // exécution requête select pour récupérer les valeurs
                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 guarda 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
        }//init
    }//clase

La novedad de 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 elabora la respuesta por sí misma. De ello se encargan las páginas JSP, JSPerreur y JSPliste. Anteriormente, el servlet añadía atributos (setAttribute) a la solicitud del cliente:

  • un mensaje de error msgErreur en caso de error para la página JSPerreur
  • los valores (valeurs)) que se van a mostrar, el valor elegido (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, se configura 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 ella a través de URL http://localhost:8080/liste/JSPerreur. Tiene 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, tendríamos /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");
   // ¿atributo válido?
  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: vuelta al servlet principal
%>
    <jsp:forward page="<%= servletListValeurs %>" />  
<%    
  }
%>  

Normalmente, esta página debe ser llamada por el servlet anterior, que debe pasarle el atributo msgErreur. Sin embargo, nada impide llamarla directamente si se conoce su URL. Además, si se detecta que el atributo msgErreur no está presente, se pasa la solicitud al servlet principal. Aquí se utiliza una etiqueta específica de 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 se configura 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 como alias /JSPliste, lo que lo hace accesible a través de URL http://localhost:8080/liste/JSPliste. Tiene 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 procedente 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 no válido: se pasa el control al servlet
   %>
   <jsp:forward page="<%= servletListValeurs %>" />
    <%
    }//if
  %>

  <%-- 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>");
                          }//for
            %>
          </select>
          <input type="submit" value="Envoyer">
        </form>
                <%
             // ¿se ha seleccionado algún valor?
                    if(! choix.equals("")){
                // se muestra la elección del usuario
                %>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
                <%        
                    }//if
                %>        
      </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, si ha elegido alguno.

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 ha sido 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 a crear directamente la respuesta HTML. De este modo, la estructura de los documentos generados resulta 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 centramos en el ciclo de vida de los servlets. El de las páginas JSP se deriva de él. Consideremos un servlet al que se llama por primera vez. El servidor web crea entonces una instancia de clase y la carga en memoria. A continuación, 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 descargará cuando haya transcurrido un tiempo suficiente long 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 subproceso por cada solicitud, y todos ellos utilizan la misma instancia del servlet:

Todos los subprocesos anteriores comparten las variables de la instancia del servlet. Puede ser necesario sincronizar los subprocesos para evitar la corrupción de los datos del servlet. Volveremos sobre esto 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(){
  }

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 ejecutado 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. Cualquier solicitud posterior al servlet incrementa y muestra el valor de este contador:

La primera vez:

Image

La segunda vez:

Image

Como se ve claramente arriba, entre las dos solicitudes, el servlet no se ha descargado; de lo contrario, el contador habría estado en 101 en la segunda solicitud. Cuando se descarga el servlet, 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. Entonces se crean dos subprocesos para ejecutarlas, subprocesos que utilizarán la misma instancia de servlet, es decir, 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 forma torpe. Supongamos que la ejecución de los dos subprocesos se desarrolla de la siguiente manera:

 
  1. en el momento T1, se ejecuta el subproceso TH1. Lee el contador (=145) en myCompteur, luego se interrumpe y pierde el procesador. Por lo tanto, no ha tenido tiempo de incrementar myCompteur y de copiar el nuevo valor en compteur.
  2. En el momento T2, se ejecuta el subproceso TH2. Lee el contador (=145) en myCompteur, luego se interrumpe y pierde el procesador. Cabe señalar que los dos subprocesos tienen variables myCompteur diferentes. Solo comparten las variables de instancia, aquellas que son globales a los métodos.
  3. En el tiempo T3, el subproceso TH1 recupera el control y finaliza. Por lo tanto, devuelve 146 a su cliente.
  4. En el momento T4, el subproceso 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 hiciera 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 ejecutado 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 procesador y otro hilo podría leer a su vez un contador que no se ha incrementado. Cuando realizamos consultas con un navegador, no vemos 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 suficientemente corto:

Image

Image

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

programa URL N

donde

URL es el URL del servlet de recuento

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

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


DOS>java clientCompteurJSP http://localhost:8080/examples/jsp/perso/compteur/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);
        }//if
         // 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 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-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 modelo 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
            }//while

             // se ha terminado
            clients[i].close();
        }//for
    }//getCompteurs

}//clase

Explicamos el código anterior:

  • el programa admite dos parámetros:
    • el URL de la página JSP del contador
    • el número de clients 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 es 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 sintácticamente válida como URL. 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á nbAppels y clients, que se conectarán todos al mismo tiempo (o casi) a URL y urlCompteur.
  • El puerto y la máquina a la que deben conectarse los clients se obtienen del URL urlCompteur: [URL].getHost() permite obtener el nombre del equipo 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, se pretende 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 (el mismo contador enviado a los cinco clients), debemos sincronizar los subprocesos del servicio de conteo en un mismo objeto antes de entrar en 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 ejecutado 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/examples/jsp/perso/compteur/compteur3.jsp 5
    Compteur= 104
    Compteur= 106
    Compteur= 105
    Compteur= 107
    Compteur= 108

La documentación indica que el servidor web puede crear en ocasiones varias instancias de un mismo servlet. En este caso, la sincronización anterior deja de funcionar, ya que la variable verrou es local a una instancia y, por lo tanto, las demás instancias no la conocen. 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.