Skip to content

3. Introdução aos servlets Java e às páginas JSP

Este capítulo contém vários exemplos de servlets e páginas JSP. Foram testados com o servidor Tomcat, que funciona na porta 8080. Seguindo as ligações na página inicial, pode aceder a exemplos de servlets e páginas JSP. A maioria dos exemplos abaixo foi retirada dos exemplos do Tomcat. Para os testar, basta iniciar o Tomcat, introduzir o URL http://localhost:8080 num navegador e seguir o link do servlet.

3.1. Servlets Java

3.1.1. Envio de conteúdo HTML para um cliente Web

Vamos analisar o exemplo «Hello World» acima. O servlet é o seguinte:


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>");
    }
}

Quando este servlet é executado, é exibido o seguinte:

Image

Tenha em atenção os seguintes pontos:

  • É necessário importar classes especiais para servlets:

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

A biblioteca javax.servlet nem sempre está incluída por predefinição no JDK. Nesse caso, pode descarregá-la diretamente do site da Sun.

  • Um servlet estende a classe HttpServlet

public class HelloWorld extends HttpServlet {
  • Uma solicitação GET feita ao servlet é tratada pelo método doGet

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

  • Da mesma forma, uma solicitação POST feita ao servlet é tratada pelo método doPost

    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  • O objeto de solicitação HttpServletRequest é o objeto que nos dá acesso à solicitação feita pelo cliente web. A resposta do servlet será enviada através do objeto de resposta HttpServletResponse
  • O objeto response permite-nos definir os cabeçalhos HTTP que serão enviados ao cliente. Por exemplo, o cabeçalho Content-Type: text/html é definido aqui da seguinte forma:
        response.setContentType("text/html");
  • Para enviar a resposta ao cliente, o servlet utiliza um fluxo de saída fornecido pelo objeto response:
        PrintWriter out = response.getWriter();
  • Assim que este fluxo de saída é obtido, o código HTML é gravado nele e, assim, enviado ao 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. Recuperação de parâmetros enviados por um cliente web

O exemplo seguinte mostra como um servlet pode recuperar parâmetros enviados pelo cliente web. Um formulário de entrada:

Image

A resposta enviada pelo servlet:

Image

O código-fonte do servlet é o seguinte:


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);
    }
 
}

Observe as seguintes alterações em relação ao exemplo anterior:

  • Os parâmetros enviados pelo navegador são recuperados da seguinte forma:

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

O método request.getParameter("parameterName") devolve um ponteiro nulo se o parâmetro "parameterName" não estiver entre os parâmetros enviados pelo cliente web.

  • O formulário especifica que o navegador deve enviar os parâmetros utilizando o método POST
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
  • Os parâmetros recebidos serão processados pelo método doPost do servlet. Aqui, este método simplesmente chama o método doGet. Assim, este servlet processa os valores do formulário independentemente de serem enviados via GET ou POST.

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

3.1.3. Recuperação de cabeçalhos HTTP enviados por um cliente web

O servlet a seguir demonstra como recuperar os cabeçalhos HTTP enviados pelo cliente web:

Image

O código-fonte do servlet é o seguinte:


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);
        }
    }
}

Pontos a ter em conta:

  • É o objeto `request` e o seu método `getHeaderNames` que nos dão acesso aos cabeçalhos HTTP enviados pelo navegador na forma de uma enumeração:
        Enumeration e = request.getHeaderNames();
  • O método request.getHeader("header") permite-lhe recuperar um cabeçalho HTTP específico. O exemplo acima apresenta alguns deles. Tenha em mente que os cabeçalhos aqui apresentados são enviados pelo navegador. O servidor também tem os seus próprios cabeçalhos HTTP, que por vezes espelham os do navegador. Os cabeçalhos HTTP enviados pelo navegador destinam-se a informar o servidor sobre as capacidades do navegador.
Cabeçalho
Significado
User-Agent
Identidade do navegador
Accept
Tipos MIME aceites pelo navegador. Por exemplo, image/gif significa que o navegador consegue processar imagens no formato GIF
Host
no formato host:porta. Indica a que máquina e porta o navegador pretende estabelecer contacto.
Accept-Encoding
formato de codificação aceite pelo navegador para documentos enviados pelo servidor. Assim, se um servidor tiver um documento num formato normal, não comprimido, e outro no formato gzip, e o navegador tiver indicado que consegue lidar com o formato gzip, então o servidor pode enviar o documento no formato gzip para poupar largura de banda.
Accept-language
Idiomas aceites pelo navegador. Se um servidor tiver o mesmo documento em vários idiomas, enviará a versão no idioma aceite pelo navegador.
Referer
O URL solicitado pelo navegador
Conexão
O modo de ligação solicitado pelo navegador. Keep-alive significa que o servidor não deve encerrar a ligação após enviar a página solicitada ao navegador. Se o navegador detetar que a página recebida contém ligações para imagens, por exemplo, pode efetuar novos pedidos ao servidor para as recuperar sem necessidade de estabelecer uma nova ligação. O navegador tomará então a iniciativa de encerrar a ligação assim que tiver recebido todos os elementos da página.

3.1.4. Recuperação de informações do ambiente

O servlet a seguir demonstra como aceder às informações do ambiente de execução do servlet. Algumas dessas informações são enviadas como cabeçalhos HTTP pelo navegador e, portanto, podem ser recuperadas utilizando o método anterior.

Image

O código do servlet é o seguinte:


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);
    }
}

A informação é obtida aqui através de vários 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());

Aqui está uma lista de alguns dos métodos disponíveis e os seus significados:

método
significado
getServerName()
o nome do servidor web
getServerPort()
a porta de trabalho do servidor web
getMethod()
o método GET ou POST utilizado pelo navegador para efetuar o seu pedido
getRemoteHost()
o nome do computador do cliente a partir do qual o navegador efetuou o pedido
getRemoteAddr()
o endereço IP dessa mesma máquina
getContentType()
o tipo de conteúdo enviado pelo navegador (cabeçalho HTTP Content-Type)
getContentLength()
o número de caracteres enviados pelo navegador (cabeçalho HTTP Content-Length)
getProtocol()
a versão do protocolo HTTP solicitada pelo navegador
getRequestURI()
O URI solicitado pelo navegador. Corresponde à parte do URL que se segue ao identificador host:port em http://hote:port/URI

3.1.5. Crie um servlet com o JBuilder e implemente-o com o Tomcat

Vamos agora descrever como criar e executar um servlet Java. Utilizaremos duas ferramentas: o JBuilder para compilar o servlet e o Tomcat para o executar. O Tomcat por si só pode ser suficiente. No entanto, oferece capacidades de depuração limitadas. Vamos revisitar o exemplo desenvolvido anteriormente que apresenta os parâmetros recebidos pelo servidor. O servlet envia primeiro o seguinte formulário de entrada:

Image

A resposta enviada pelo servlet:

Image

O código-fonte do servlet é o seguinte:


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);
    }
 
}
  • Crie um projeto chamado myRequestParamExample utilizando o JBuilder e inclua nele o programa myRequestParamExample.java anterior.
  • Durante a compilação, poderá deparar-se com o seguinte problema: o seu JBuilder poderá não ter a biblioteca javax.servlet necessária para compilar servlets. Neste caso, terá de configurar o JBuilder para utilizar bibliotecas de classes adicionais. O procedimento está descrito nos apêndices deste documento para o JBuilder 7. Reproduzimos-o parcialmente aqui:
  • Ative a opção Ferramentas/Configurar JDKs

Image

Na secção «Configurações do JDK» acima, o campo «Nome» apresenta normalmente «JDK 1.3.1». Se tiver um JDK mais recente, utilize o botão «Alterar» para especificar o seu diretório de instalação. Acima, especificámos o diretório E:\Program Files\jdk14, onde foi instalado um JDK 1.4. A partir de agora, o JBuilder utilizará este JDK para as suas compilações e execuções. Na secção (Class, Source, Documentation), verá uma lista de todas as bibliotecas de classes que o JBuilder irá analisar — neste caso, as classes do JDK 1.4. As classes incluídas neste JDK não são suficientes para o desenvolvimento web em Java. Para adicionar bibliotecas de classes adicionais, utilize o botão Add e selecione os ficheiros .jar extra que pretende utilizar. Os ficheiros .jar são bibliotecas de classes. O Tomcat 4.x inclui todas as bibliotecas de classes necessárias para o desenvolvimento web. Estas encontram-se em <tomcat>\common\lib, onde <tomcat> é o diretório de instalação do Tomcat:

Image

Usando o botão Adicionar, vamos adicionar estas bibliotecas, uma a uma, à lista de bibliotecas analisadas pelo JBuilder:

Image

A partir de agora, pode compilar programas Java em conformidade com a norma J2EE, incluindo servlets Java. O JBuilder é utilizado apenas para a compilação; a execução é posteriormente tratada pelo Tomcat.

  • Agora pode compilar o programa myRequestParamExample.java e gerar o servlet myRequestParamExample.class. Onde deve este servlet ser colocado? Se a configuração inicial do Tomcat não tiver sido alterada, os ficheiros .class do servlet devem ser colocados em <tomcat>\webapps\examples\WEB-INF\classes (Tomcat 4.x).
  • Verifique se o Tomcat está a funcionar e utilize um navegador para aceder ao URL http://localhost:8080/examples/servlet/myRequestParamExample:

Image

3.1.6. Exemplos

Para os exemplos a seguir, utilizámos o método descrito acima:

  • compilar o ficheiro fonte XX.java do servlet com o JBuilder
  • implementar o servlet XX.class em <tomcat>\webapps\examples\WEB-INF\classes
  • Com o Tomcat em execução, introduza o URL http://localhost:8080/examples/servlet/XX num navegador

3.1.6.1. Geração dinâmica de formulários - 1

Usaremos como exemplo a geração de um formulário com apenas um controlo: uma lista. O conteúdo desta lista é construído dinamicamente utilizando valores retirados de uma matriz. Na realidade, estes valores são frequentemente recuperados de uma base de dados. O formulário é o seguinte:

Image

Se clicar em Enviar no exemplo acima, obtém a seguinte resposta:

Image

Note que o URL que devolve a resposta é o mesmo que o que apresenta o formulário. Aqui, temos um servlet que processa a resposta ao formulário que enviou. Este é um cenário comum. O código HTML do formulário é o seguinte:

<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>

Note que os valores enviados pelo formulário são enviados utilizando o método POST. O código HTML da resposta:

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

O código do servlet que gera este formulário e esta resposta é o seguinte:

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

public class gener1 extends HttpServlet{
    // variables d'instance
    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{

      // on indique au client le type de document envoyé
      response.setContentType("text/html");
      // on envoie le formulaire
      PrintWriter out=response.getWriter();
            // début
      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>");
            }//for
            out.println("</select>");
            // fin formulaire
            out.println(HTML2+HTML3);
    }//GET

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

            // on récupère le choix de l'utilisateur
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) doGet(request,response);

            // on prépare la réponse
            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>";
            // on indique au client le type de document envoyé
            response.setContentType("text/html");
            // on envoie le formulaire
            PrintWriter out=response.getWriter();
            out.println(réponse);
    }//POST
    }//classe

O método doGet é utilizado para gerar o formulário. Existe uma parte dinâmica, que é o conteúdo da lista, obtido aqui a partir de um array. O método doPost é utilizado para gerar a resposta. Aqui, a única parte dinâmica é o valor da seleção do utilizador na lista do formulário. Este valor é obtido através de request.getParameter("cmbValeurs"), onde cmbValeurs é o nome HTML da lista:

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

Por fim, tenha em atenção os seguintes pontos:

  • O navegador envia os valores do formulário para o servlet que o gerou, uma vez que a tag <form> não possui o atributo <action>. Neste caso, o navegador envia os dados introduzidos no formulário para o URL que o forneceu.
  • A tag <form> especifica que os dados do formulário devem ser enviados utilizando o método POST. É por isso que estes valores são recuperados pelo método doPost do servlet.

3.1.6.2. Geração Dinâmica de Formulários - 2

Vamos revisitar o exemplo anterior e modificá-lo da seguinte forma. O formulário permanece o mesmo:

Image

Mas a resposta é diferente:

Image

Na resposta, o formulário é devolvido, com o número escolhido pelo utilizador indicado abaixo dele. Além disso, este número é o que aparece como selecionado quando a lista é apresentada. O utilizador pode então escolher outro número:

Image

e clicar em Enviar. Recebe a seguinte resposta:

Image

O código para o servlet denominado gener2.java é o seguinte:

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

public class gener2 extends HttpServlet{
        // variables d'instance
        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{

            // on récupère l'éventuel choix de l'utilisateur
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

            // on indique au client le type de document envoyé
            response.setContentType("text/html");
            // on envoie le formulaire
            PrintWriter out=response.getWriter();
            // début
            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>");
            // suite formulaire
            out.println(HTML2);
            if(! choix.equals("")){
                // on affiche le choix de l'utilisateur
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//if
            // fin du formulaire
            out.println(HTML3);
        }//GET

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

            // on renvoie sur GET
            doGet(request,response);
        }//POST
    }//classe

O método doGet faz tudo: constrói o formulário que envia ao cliente e processa os valores que o cliente devolve. Vale a pena destacar os seguintes pontos:

  • Verificamos se o parâmetro cmbValeurs tem um valor.
  • Se for o caso, ao criar o conteúdo da lista, cada item é comparado com a seleção do utilizador para definir o atributo selected no item escolhido pelo utilizador: <option selected>item</option>. Além disso, o valor selecionado é apresentado abaixo do formulário.

3.1.6.3. Geração Dinâmica de Formulários - 3

Estamos a abordar o mesmo problema de antes, mas desta vez os valores são recuperados de uma base de dados. No nosso exemplo, trata-se de uma base de dados MySQL:

  • a base de dados chama-se dbValues
  • o seu proprietário é admDbValeurs com a palavra-passe mdpDbValeurs
  • a base de dados tem uma única tabela chamada tvaleurs
  • esta tabela tem apenas um campo inteiro chamado value
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)

A base de dados MySQL dbValeurs foi disponibilizada através de um controlador ODBC para MySQL. O seu DSN (Data Source Name) é odbc-valeurs. O código do servlet é o seguinte:

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

public class gener3 extends HttpServlet{
        // le titre de la page
        private final String title="Génération d'un formulaire";
        // la base de données des valeurs de liste
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
        // valeurs de liste
        private String[] valeurs=null;
        // msg d'erreur
        private String msgErreur=null;
        // code 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{

            // on indique au client le type de document envoyé
            response.setContentType("text/html");
            // flux de sortie
            PrintWriter out=response.getWriter();

            // l'initialisation de la servlet s'est-elle bien passée ?
            if (msgErreur!=null){
                // il y a eu une erreur - on génère une page d'erreur
                out.println("<html><head><title>"+title+"</title></head>");
                out.println("<body><h3>Application indisponible ("+msgErreur+
                                        ")</h3></body></html>");
                return;
            }//if

            // on récupère l'éventuel choix de l'utilisateur
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

            // on envoie le formulaire
            // début
            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>");
            // suite formulaire
            out.println(HTML2);
            if(! choix.equals("")){
                // on affiche le choix de l'utilisateur
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//if
            // fin du formulaire
            out.println(HTML3);
        }//GET

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

            // on renvoie sur GET
            doGet(request,response);
        }//POST

        // initialisation de la servlet
        public void init(){
            // remplit le tableau des valeurs à partir d'une base de données ODBC
            // de nom DSN : DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                // connexion à la base ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                // objet Statement
                st=connexion.createStatement();
                // exécution requête select pour récupérer les valeurs
                rs=st.executeQuery("select valeur from Tvaleurs");
                // les valeurs sont récupérées et mises dans un tableau dynamique
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // on enregistre la valeur dans la liste
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                // transformation liste --> tableau
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                // problème
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//init
    }//classe

Os pontos-chave a ter em conta são os seguintes:

  1. Um servlet pode ser inicializado por um método cuja assinatura deve ser public void init(). Este método é executado apenas quando o servlet é carregado inicialmente
  2. Uma vez carregado, um servlet permanece na memória em todos os momentos. Isto significa que, após atender um cliente, ele não é descarregado. Assim, responde mais rapidamente às solicitações dos clientes.
  3. No nosso servlet, é necessário recuperar uma lista de valores de uma base de dados. Uma vez que esta lista não se altera ao longo do tempo, o método init é o momento ideal para a recuperar. A base de dados é, assim, acedida apenas uma vez pelo servlet, quando este é carregado inicialmente, em vez de ser acedida a cada pedido do cliente.
  4. O acesso à base de dados pode falhar. O método init do nosso servlet define uma mensagem de erro, msgErreur, em caso de falha. Esta mensagem é verificada no método doGet e, se ocorrer um erro, o doGet gera uma página a indicá-lo.
  5. A implementação do método init utiliza o acesso padrão à base de dados com controladores ODBC-JDBC. Se necessário, o leitor é encorajado a rever os métodos para aceder a bases de dados JDBC.

Quando o servlet é executado e o servidor MySQL não foi iniciado, aparece a seguinte página de erro:

Image

Se iniciar agora o servidor MySQL, verá a página:

Image

Se selecionar o número 6 e clicar em «Submit»:

Image

3.1.6.4. Recuperar valores de um formulário

Vamos revisitar um exemplo que já vimos, o do seguinte formulário web:

Image

O código HTML do formulário balises2.htm é o seguinte:

<html>

  <head>
      <title>balises</title>
    <script language="JavaScript">
        function effacer(){
          alert("Vous avez cliqué sur le bouton Effacer");
      }//effacer
        </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>

A tag <form> no formulário foi definida da seguinte forma:

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

O navegador irá «enviar» os valores do formulário para o URL http://localhost:8080/examples/servlet/parameters, que é o URL de um servlet gerido pelo Tomcat e que apresenta os valores do formulário anterior. Se chamarmos o servlet de parâmetros diretamente, obtemos os seguintes resultados:

Image

Se o formulário balises2.htm introduzido for este:

Image

e clicar no botão «Submit», o servlet «parameters» é chamado com os parâmetros desta vez. Em seguida, devolve a seguinte resposta:

Image

Esta resposta contém claramente os valores introduzidos no formulário. O código do servlet é o seguinte:

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

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

    private String getParameter(HttpServletRequest request, String contrôle){
      // rend la valeur request.getParameter(contrôle) ou "" si elle n'existe pas
      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
    {
      // on commence par récupérer les paramètres du formulaire
      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");

      // on indique le contenu du document
        response.setContentType("text/html");
      // on envoie le document
        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
    {
      // renvoie sur GET
      doGet(request,response);
    }
}

Este código utiliza as técnicas apresentadas anteriormente noutro exemplo. Tenha em atenção os dois pontos seguintes:

  1. O controlo lst2 é uma lista de seleção múltipla, pelo que é possível selecionar vários itens. É o que acontece no nosso exemplo, em que os itens list1 e list3 foram selecionados. Os valores de lst2 foram enviados pelo navegador para o servidor na forma lst2=liste1&lst2=liste3. O servlet Java pode recuperar esses valores numa matriz utilizando o método getParameterValues: aqui, request.getParameterValues("lst2") devolve uma matriz de duas cadeias de caracteres ["liste1", "liste3"].
  2. O controlo areaSaisie é um campo de entrada multilinha. request.getParameter("areaSaisie") devolve o conteúdo do campo como uma única string. Se pretender extrair as linhas individuais dentro dele, pode utilizar o método split da classe String. O código seguinte
      String areaSaisie=getParameter(request,"areaSaisie");
      String[] lignes=areaSaisie.split("\\r\\n");

recupera as linhas do campo de entrada. Estas linhas são terminadas pelos caracteres \r\n (0D0A).

Para realizar os testes, temos:

  • criámos e compilámos os parâmetros do servlet utilizando o JBuilder, conforme explicado anteriormente
  • colocado a classe gerada em <tomcat>\webapps\examples\WEB-INF\classes, onde <tomcat> é o diretório de instalação do Tomcat.
  • solicitámos o URL http://localhost:81/html/balises2.htm, cujo código foi apresentado acima
  • preenchemos o formulário e clicámos no botão Enviar.

3.1.6.5. Recuperação de cabeçalhos HTTP de um cliente web

Vamos usar o mesmo exemplo de antes, mas, em resposta ao cliente web que enviou os valores do formulário, vamos reenviar os cabeçalhos HTTP que ele enviou ao mesmo tempo. Vamos fazer uma única alteração no nosso formulário:

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

Os valores do formulário serão enviados utilizando o método GET para um servlet Java chamado headers, localizado em <tomcat>\webapps\examples\WEB-INF\classes. O servlet headers foi criado e compilado com o 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
    {
       // determine the nature of the document
        response.setContentType("text/html");
       // we obtain a writing flow
        PrintWriter out = response.getWriter();
      // display header list 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
}

Acedemos à URL http://localhost:81/html/balises2.htm e clicamos em «Submeter» sem alterar o formulário. Recebemos a seguinte resposta:

Image

Repare na URL parametrizada na barra de endereços do navegador, que mostra o método (GET) utilizado para transmitir os parâmetros. Vamos utilizar o mesmo exemplo, mas alterar o método de envio dos parâmetros (POST):

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

Recebemos a seguinte resposta nova:

Image

Repare nos cabeçalhos HTTP content-type e content-length, que são característicos de um pedido POST. Além disso, repare que os valores do formulário já não aparecem no campo de endereço do navegador.

3.2. Páginas JSP

As JSP (Java Server Pages) são outra forma de escrever aplicações para servidores web. Na verdade, estas páginas JSP são traduzidas em servlets antes de serem executadas, pelo que estamos essencialmente a lidar com tecnologia de servlets. As páginas JSP permitem destacar de forma mais clara a estrutura das páginas HTML geradas. Seguem-se alguns exemplos, alguns dos quais podem ser acedidos seguindo a ligação JSP na página inicial do Tomcat:

3.2.1. Recuperação de informações do ambiente

Aqui, retomamos um exemplo anteriormente abordado com um servlet: a exibição das variáveis de ambiente de um servlet. Este é o exemplo “snoop” dos exemplos de JSP:

Image

O código-fonte da página JSP encontra-se em <tomcat>\jakarta-tomcat\examples\jsp\snp\snoop.jsp (Tomcat 3.x) ou <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>

Tenha em atenção os seguintes pontos:

  • Este código assemelha-se muito ao HTML. No entanto, contém tags <%= expressão %> que são específicas da linguagem JSP. O compilador JSP substitui a tag inteira no texto HTML pelo valor da expressão.
  • Este exemplo utiliza métodos do objeto Java `request`, que é o objeto `request` já encontrado no estudo dos servlets. Trata-se, portanto, de um objeto `HttpServletRequest`. Assim, a tag `<%= request.getRemoteHost() %>` será substituída no código HTML pelo nome da máquina do cliente web que efetuou o pedido.
  • É possível obter o mesmo resultado com um servlet, mas aqui a estrutura da página web é mais evidente.

3.2.2. Recuperação de parâmetros enviados pelo cliente web

Aqui, revisamos o exemplo que estudámos anteriormente com um servlet. É apresentado um formulário ao navegador:

Image

Em resposta ao pedido acima, o navegador recebe a seguinte página:

Image

O código da página JSP é o seguinte:


<%
  // variables locales à la procédure principale
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>
 
<!-- code 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>
  • Embora voltemos a ver a tag <%= expressão %>, tal como no exemplo anterior, surge uma nova tag: <% instruções Java; %>. A tag <% introduz código Java. Este código termina quando encontra a tag de encerramento %>.
  • Todo o código anterior (HTML + JSP) será convertido num servlet Java. Será incluído num único método, conhecido como o método principal da página JSP. É por isso que as variáveis Java declaradas no início da página JSP são acessíveis nas outras partes do código JSP intercaladas ao longo do HTML: estas variáveis e segmentos de código farão parte do mesmo método Java. No entanto, se o nosso código JSP contivesse métodos, as variáveis `title`, `firstname` e `lastname` não estariam acessíveis dentro deles devido ao escopo dos métodos. Teríamos de torná-las variáveis globais ou passá-las como parâmetros para os métodos. Voltaremos a este assunto mais tarde.
  • Para incluir partes dinâmicas no código HTML, existem dois métodos possíveis: <%= expressão %> ou out.println(expressão). O objeto out é um fluxo de saída semelhante ao de mesmo nome encontrado nos exemplos de servlet, mas não do mesmo tipo: é um objeto JspWriter, não um PrintWriter. Permite escrever no fluxo HTML utilizando os métodos print e println.
  • A página JSP reflete melhor a estrutura da página HTML gerada do que o servlet equivalente.

3.2.3. Etiquetas JSP

Aqui está uma lista de tags que poderá encontrar numa página JSP e os seus significados.

tag
Significado
<!-- comentário -->
Comentário HTML. Enviado para o cliente.
<%-- comentário --%>
Comentário JSP. Não enviado ao cliente.
<%! declarações, métodos %>
Declara variáveis e métodos globais. As variáveis estarão disponíveis em todos os métodos
<%= expressão %>
O valor da expressão será inserido na página HTML no lugar da tag
<% código Java %>
contém código Java que fará parte do método principal da página JSP
<%@ page atributo1=valor1
atributo2=valor2 … %>
define atributos para a página JSP. Por exemplo:
import="java.util.*,java.sql.*" para especificar as bibliotecas necessárias à página JSP
extends="aParentClass" para que a página JSP herde de outra classe

3.2.4. Objetos implícitos do JSP

Nos exemplos anteriores, deparámo-nos com dois objetos não declarados: request e out. Estes são dois dos objetos que são automaticamente definidos no servlet para o qual a página JSP é convertida. São chamados de objetos implícitos ou predefinidos. Existem outros, mas estes são os mais utilizados juntamente com o objeto response:

object
significado
HttpServletRequest request
o objeto através do qual pode aceder ao pedido do cliente web (getParameter, getParameterNames, getParameterValues)
HttpServletResponse resposta
O objeto utilizado para construir a resposta do servidor web ao cliente. Permite definir os cabeçalhos HTTP a serem enviados ao cliente web.
JspWriter out
o fluxo de saída que nos permite enviar código HTML para o cliente (print, println)

3.2.5. Converter uma página JSP num servlet

Vamos rever o código JSP do myRequestParamExample.jsp:


<%
  // variables locales à la procédure principale
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>
 
<!-- code 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>

Quando o navegador solicita esta página JSP ao servidor Tomcat, o servidor irá convertê-la num servlet. Se o URL solicitado for

http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp, o Tomcat 4.x colocará o servlet gerado no diretório <tomcat>\work\localhost\examples\jsp\perso\intro:

Image

Este nome reflete a URL da página JSP, http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp. Conforme mostrado acima, podemos aceder ao código Java do servlet gerado para a página JSP. No nosso exemplo, é o seguinte:

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 local to the main procedure
                  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);
        }
    }
}

O código gerado é bastante complexo. Vamos concentrar-nos apenas nos seguintes pontos:

  • O método main do servlet é o seguinte:
    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

Este método é chamado quando o servlet é iniciado. Podemos ver que ele recebe dois parâmetros: o pedido do cliente e um objeto de resposta para gerar a sua resposta ao cliente web.

  • No método main, é declarado e, em seguida, inicializado um objeto JspWriter denominado out. Este objeto é utilizado para enviar código HTML ao cliente através da instrução out.print("código HTML").
        JspWriter out = null;
...
            out = pageContext.getOut();
  • O código Java


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

foi incluído na íntegra no método principal _jspService do servlet. O mesmo se aplica a qualquer código localizado dentro das tags <%… %>

  • O código HTML da página JSP é gerado utilizando as instruções out.print("codeHTML") ou out.write(...). Por exemplo

                out.write("</title>\r\n  </head>\r\n  <body bgcolor=\"white\">\r\n    <h3>");
  • Neste exemplo, não existem outros métodos além do método principal _jspService.

3.2.6. Métodos e variáveis globais de uma página JSP

Considere a seguinte página JSP:


<%!
  // la balise précédente démarre la partie variables et méthodes globales
  // cette partie sera reprise sans modification dans la servlet
 
  // une variabe globale
  String prenom="inconnu";
 
  // une méthode  
  private String sonChien(){
    return "milou";
  }//sonChien
 
  // une autre méthode
  private void afficheAmi(JspWriter out) throws Exception{
    out.println("<p>Son ami s'appelle Haddock</p>");
  }//afficheAmi
 
  // fin de la partie globale de la servlet
%>  
 
<%
  // la balise précédente indique que le code qui suit sera enregistré
  // dans la méthode principale de la servlet
 
  // variable locale à la méthode principale
  String nom="tintin";
%>
 
 
<%-- code 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>
      <%
        // le nom de son ami
        afficheAmi(out);
      %>
    </center>
  </body>
</html>

Esta página JSP gera a seguinte página web:

Image

Vamos ver como as quatro linhas acima são geradas:


      <p>Son nom est <%= nom %></p>
      <p>Son prénom est <%= prenom %></p>
      <p>Son chien s'appelle <%= sonChien() %></p>
      <%
        // le nom de son ami
        afficheAmi(out);
      %>

As linhas acima estão dentro de uma tag <%..%> e, portanto, farão parte do método principal _jspService do servlet que será gerado. Como é que elas acedem às variáveis lastName, firstName e aos métodos hisDog e displayFriend?

name (tintin)
é uma variável local do método principal da página JSP e, portanto, é reconhecida dentro dela
firstName (desconhecido)
é uma variável global da página JSP e, portanto, é reconhecida no método principal
sonChien (milou)
é um método público da página JSP e, portanto, acessível a partir do método principal
displayFriend (Haddock)
é um método público da página JSP e, portanto, acessível a partir do método principal. Note-se que o objeto out é passado como parâmetro para o método. Isto é obrigatório aqui. Na verdade, o objeto out é declarado e inicializado no método principal do servlet e não é uma variável global.

Vejamos agora o código do servlet Java gerado a partir desta página JSP, depois de removido o código desnecessário:

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 {

          // the preceding tag starts the global variables and methods section
           // this part will be adopted unchanged in the servlet

           // global variability
          String prenom="inconnu";

           // a method  
          private String sonChien(){
            return "milou";
          }//sonChien

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

           // end of the global part of the 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");
                  // the preceding tag indicates that the following code will be saved
                   // in the servlet's main method

                   // variable local to the main method
                  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      ");

                        // his friend's name
                        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 mostrado acima, o código Java que se encontrava entre as tags JSP <%! .. %> foi incluído na íntegra e não faz parte do método principal do servlet, _jspService. As variáveis declaradas nesta secção são variáveis de instância e, portanto, globais para os métodos; é também aqui que podem ser definidos outros métodos além do _jspService.


  // this part will be adopted unchanged in the servlet
 
   // global variability
  String prenom="inconnu";
 
   // a method
  private String sonChien(){
    return "milou";
  }//sonChien
 
   // another method
  private void afficheAmi(JspWriter out) throws Exception{
    out.println("<p>Son ami s'appelle Haddock</p>");
  }//afficheAmi
 
   // end of the global part of the servlet

3.2.7. Implantação e depuração de páginas JSP no servidor Tomcat

Quando se pretende criar uma página JSP e utilizá-la com o servidor Tomcat, surge a questão de onde colocar a página na estrutura de diretórios do servidor. Existem diferentes formas de o fazer, às quais voltaremos mais tarde. Por enquanto, a abordagem mais simples é colocar a página JSP numa pasta dentro da árvore de diretórios <tomcat>\webapps\examples\jsp (Tomcat 4.x), onde <tomcat> é o diretório de instalação do Tomcat. Assim, o URL no exemplo anterior era http://localhost:8080/examples/jsp/perso/tintin/tintin.jsp. Isto significa que a página tintin.jsp estava localizada na pasta <tomcat>\webapps\examples\jsp\perso\tintin.

Uma página JSP é traduzida num ficheiro fonte Java, que é então compilado pelo Tomcat quando a URL da página JSP é solicitada por um navegador. Podem ocorrer erros de compilação. O Tomcat 4.x reporta-os na sua resposta ao navegador. Indica especificamente quais as linhas do ficheiro .java que contêm erros. Os erros podem ter várias causas:

  1. o código JSP na página está incorreto (por exemplo, erros nas tags JSP utilizadas)
  2. o código Java incluído na página JSP está incorreto

A primeira causa pode ser eliminada verificando o código JSP da página. A segunda pode ser eliminada verificando o código Java. Isto pode ser feito compilando diretamente o ficheiro .java gerado para a página JSP utilizando uma ferramenta como o JBuilder, que oferece capacidades de depuração mais avançadas do que o Tomcat.

3.2.8. Exemplos

Vamos retomar o exemplo que abordámos anteriormente, que envolve um servlet em que um utilizador seleciona um número de uma lista e o servidor indica ao utilizador qual o número que selecionou, devolvendo a mesma lista com o elemento escolhido pelo utilizador destacado:

Image

Para criar esta página, recuperámos o código do servlet e modificámo-lo da seguinte forma:

  • mantivemos o código Java que não gerava código HTML tal como estava
  • O código Java que gerava código HTML foi convertido numa mistura de código HTML e JSP

Isto resulta na seguinte página JSP:


 
<%@ page import="java.sql.*, java.util.*" %>
 
<%!
 
        // variables globales de l'application
        // le titre de la page
        private final String title="Génération d'un formulaire";
        // la base de données des valeurs de liste
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
        // valeurs de liste
        private String[] valeurs=null;
        // msg d'erreur
        private String msgErreur=null;
 
        // initialisation de la page JSP - n'est exécutée qu'une seule fois
        public void jspInit(){
            // remplit le tableau des valeurs à partir d'une base de données ODBC
            // de nom DSN : DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                // connexion à la base ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                // objet Statement
                st=connexion.createStatement();
                // exécution requête select pour récupérer les valeurs
                rs=st.executeQuery("select valeur from Tvaleurs");
                // les valeurs sont récupérées et mises dans un tableau dynamique
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // on enregistre la valeur dans la liste
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                // transformation liste --> tableau
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                // problème
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//init
 
%>
 
<%
    // code de _jspService exécuté à chaque requête cliente
  // y-at-il eu une erreur lors de l'initialisation de la page JSP ?
  if(msgErreur!=null){
%>
       <!-- code HTML -->
    <html>
        <head>
          <title>Erreur</title>
      </head>
      <body>
          <h3>Application indisponible (<%= msgErreur %></h3>
      </body>
    </html>
<%
      // fin de jspService
    return;
  }//if
 
    // on récupère l'éventuel choix de l'utilisateur
    String choix=request.getParameter("cmbValeurs");
    if(choix==null) choix="";
%>
 
  <%-- pas d'erreur - code HTML de la page normale --%>
      <html>
        <head>
          <title><%= title %></title>
      </head>
      <body>
          <h3>Choisissez une valeur</h3>
          <form method="POST">
            <select name="cmbValeurs">
              <%
                // affichage dynamique des valeurs
                          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>
<%
        // y-avait-il une valeur choisie ?
                if(! choix.equals("")){
        // on affiche le choix de l'utilisateur
%>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
<%        
                }//if
%>        
      </body>
    </html>

Tenha em atenção os seguintes pontos:

  • as instruções de importação do servlet estão incluídas numa diretiva <% page import="..." %>
  • A tag <%! ... %> engloba as variáveis globais da aplicação e os métodos Java
  • O método init do servlet, que é executado apenas uma vez quando o servlet é carregado, é chamado jspInit para uma página JSP. Estes dois métodos têm a mesma finalidade. Por conseguinte, o código do método init do servlet foi incluído aqui na íntegra.
  • As variáveis de instância do servlet — aquelas que devem estar acessíveis em vários métodos — foram incluídas na íntegra. Estas são principalmente as variáveis title, values e msgError, que são posteriormente utilizadas no código JSP.
  • As tags <% ... %> delimitam o código Java que será incluído no método _jspService, executado quando um cliente faz uma solicitação.
  • Tal como no servlet, o método _jspService verificará primeiro o valor da variável msgError para determinar se precisa de gerar uma página de erro. Se houver um erro, gera a página de erro e sai (return).
  • Se não houver erro, gera o formulário com a lista de valores
  • Depois de fazer isso, verifica se o utilizador selecionou um número; se sim, exibe esse número na página gerada

Quais são as vantagens em relação a um servlet? Sem dúvida, uma visão mais clara do código HTML gerado. Mas ainda há muito código Java que «sobrecarrega» essa visão. Veremos mais tarde outro método chamado delegação, onde podemos colocar a maior parte do código Java num servlet, com a página JSP a reter apenas o código HTML e JSP. Isto separa claramente a parte de processamento da parte de apresentação.

3.3. Implementação de uma aplicação Web no servidor Tomcat

Vamos agora explicar como implementar aplicações Web Java utilizando o servidor Tomcat. Embora as instruções seguintes sejam específicas para este servidor, a implementação de uma aplicação Web Java noutro contentor J2EE envolverá passos semelhantes aos aqui descritos.

3.3.1. Os ficheiros de configuração server.xml e web.xml

Até agora, para testar os nossos servlets e páginas JSP, colocámos

  • os servlets na pasta <tomcat>\webapps\examples\WEB-INF\classes. Estavam então acessíveis através do URL http://localhost:8080/examples/servlet/nomServlet
  • as páginas JSP na árvore de diretórios <tomcat>\webapps\examples\jsp. Elas ficavam então acessíveis através da URL http://localhost:8080/examples/jsp/nomPageJSP

Nunca explicámos por que razão isto acontecia. O servidor Tomcat é configurado num ficheiro de texto chamado server.xml, localizado no diretório <tomcat>\conf:

Image

Este ficheiro de texto é, na verdade, um ficheiro XML (eXtended Markup Language). Um documento XML é um documento de texto que contém tags, tal como um documento HTML. No entanto, enquanto as tags HTML são bem definidas, as tags XML não o são. Assim, o documento seguinte é um documento XML:

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

Um documento XML é simplesmente um documento «marcado» que segue certas regras de marcação:

  • um texto marcado na forma <xx att1="val1" att2="val2" ....>texto</xx>
  • uma tag pode ser independente e assumir a forma <xx att1="val1" att2="val2" ..../>

Os campos «att» são chamados atributos da tag «xx», e os campos «val» são os valores associados a esses atributos. Alguns documentos HTML não são documentos XML válidos. Por exemplo, a tag HTML <br> não é uma tag XML válida. Deve ser escrita como <br/> para ser válida, a fim de cumprir a regra de que todas as tags XML devem ser fechadas. Foi criada uma variante do HTML chamada XHTML para garantir que todos os documentos XHTML sejam documentos XML válidos. Alguns navegadores modernos são capazes de exibir ficheiros XML. Assim, se chamarmos ao documento XML apresentado no exemplo acima «person.xml» e o visualizarmos com o IE6, obtemos a seguinte exibição:

Image

O IE6 reconhece as tags e destaca-as. Também reconhece a estrutura do documento com base nas tags. Assim, se chamarmos ao documento seguinte “person2.xml”:

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

e o visualizarmos com o IE6, obtemos a mesma apresentação:

Image

O IE6 reconheceu corretamente a estrutura e o conteúdo do documento. O objetivo principal de um documento XML reside nesta propriedade: é fácil recuperar a estrutura e o conteúdo de um documento XML. Isto é feito utilizando um programa chamado analisador XML. Os documentos XML estão a tornar-se o padrão para a troca de documentos na Web. Consideremos a máquina A, que precisa de enviar um documento DOC para a máquina B. O documento DOC é construído a partir de informações contidas na base de dados DB-A. A máquina B deve armazenar o documento DOC na base de dados DB-B. A troca pode ocorrer da seguinte forma:

  • A máquina A recupera os dados da base de dados DB-A e encapsula-os num documento de texto XML
  • o documento XML é enviado para a máquina B através da rede
  • A máquina B analisa o documento recebido com um analisador XML e extrai tanto a estrutura como os dados (tal como o IE6 fez no nosso exemplo). Pode então armazenar os dados recebidos na base de dados DB-B

Não falaremos mais sobre a linguagem XML, que merece um livro inteiro só para ela.

Aqui, então, o Tomcat é configurado pelo ficheiro XML server.xml. Se visualizarmos este ficheiro com o IE6, obtemos um documento complexo. Vamos concentrar-nos simplesmente nas seguintes linhas:

Image

É a tag <Context ...> que nos interessa aqui. É utilizada para definir aplicações web. Dois dos seus atributos merecem destaque:

  • path: este é o nome da aplicação web
  • docBase: esta é a pasta na qual se encontra. Aqui, trata-se de um nome relativo: examples. Relativo a que pasta? A resposta também se encontra no ficheiro server.xml, na seguinte linha:

Image

A linha acima define o servidor web:

  • name: nome do servidor web
  • appBase: raiz da árvore de documentos que ele serve. Mais uma vez, temos um nome relativo: webapps. É relativo ao diretório de instalação do servidor Tomcat <tomcat>. Assim, isto refere-se à pasta <tomcat>\webapps.

A aplicação web de exemplos armazena os seus ficheiros na pasta «examples» (ver «docBase» acima). Este nome é relativo à raiz da árvore de diretórios web do servidor, ou seja, <tomcat>\webapps. Trata-se, portanto, da pasta <tomcat>\webapps\examples. Vamos analisar esta pasta mais detalhadamente:

Image

Aí encontramos a pasta WEB-INF\classes, onde armazenámos os nossos servlets para teste. A pasta WEB-INF contém um ficheiro chamado web.xml:

Image

Este ficheiro é utilizado para configurar a aplicação web de exemplo. Não vamos entrar em detalhes sobre este ficheiro, que é demasiado complexo para o momento. Vamos simplesmente concentrar-nos nas seguintes linhas:

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

A tag <servlet> é utilizada para definir um servlet dentro de uma aplicação web. Note-se que a aplicação web em questão é a examples. A tag servlet contém duas outras tags:

  • <servlet-name>servletToJsp</servlet-name>: define o nome do servlet
  • <servlet-name>servletToJsp</servlet-name>: define o nome da classe a ser executada quando o servlet for solicitado. Neste exemplo, o servlet e a sua classe têm o mesmo nome. Isto não é obrigatório.

Como é que o servlet servletToJsp é solicitado por um navegador ao servidor Tomcat?

  • O navegador solicita a URL http://localhost:8080/examples/servlet/servletToJsp
  • O Tomcat analisa o caminho do servlet /examples/servlet/servletToJsp. Ele interpreta a primeira parte do caminho /examples como o nome de uma aplicação web e consulta o seu ficheiro de configuração server.xml para verificar onde os documentos desta aplicação estão armazenados. Como vimos anteriormente, isto encontra-se na pasta <tomcat>\webapps\examples.
  • O Tomcat utiliza o resto do caminho do servlet para o localizar dentro da aplicação web examples. Este caminho /servlet/servletToJsp indica que deve executar o servlet denominado servletToJsp. O Tomcat irá então ler o ficheiro de configuração web.xml da aplicação examples, que encontrará em <tomcat>\webapps\examples\WEB-INF. Encontrará neste ficheiro que o servlet servletToJsp está ligado à classe Java servletToJsp (ver o ficheiro web.xml acima). Em seguida, procurará esta classe na pasta WEB-INF\classes da aplicação web de exemplos, ou seja, em <tomcat>\webapps\examples\WEB-INF\classes, e executá-la-á.

Image

3.3.2. Exemplo: Implementação da aplicação web "list"

Vamos revisitar um servlet que já estudámos, que apresentava ao utilizador uma lista de números a partir da qual este selecionava um. O servlet confirmava então o número que tinha sido escolhido:

Image

Conforme mostrado no campo de endereço do navegador acima, o ficheiro de classe do servlet foi denominado gener3. Com base nas explicações fornecidas anteriormente:

  • a URL /examples/servlet/gener3 indica que se trata de um servlet chamado gener3 da aplicação web de exemplos
  • no ficheiro web.xml da aplicação de exemplos, não há nada que mencione um servlet chamado gener3. Como é que o Tomcat o encontrou, então? Depois de ter revisto todo o ficheiro web.xml, não consigo responder com certeza... A questão permanece...

Optamos por implementar o servlet gener3.class com o nome lstValeurs numa aplicação web chamada liste, localizada na pasta E:\data\serge\Servlets\lstValeurs:

Image

Colocamos o ficheiro gener3.class na pasta WEB-INF\classes acima mencionada:

Image

Configuramos a aplicação web liste adicionando as seguintes linhas ao ficheiro server.xml, acima das que definem a aplicação web manager:

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

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

A linha que define a aplicação lstValeurs indica que esta se encontra na pasta e:/data/serge/servlets/lstValeurs. Temos agora de definir o ficheiro web.xml para esta aplicação. Este ficheiro irá definir o único servlet da aplicação:

<?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>

O ficheiro acima indica que o servlet denominado lstValeurs está associado ao ficheiro de classe gener3.class. Este ficheiro web.xml deve ser criado e guardado na pasta WEB-INF da aplicação liste:

Image

A captura de ecrã acima mostra uma pasta src na qual foi colocado o ficheiro fonte gener3.java. Esta pasta pode não existir. Não tem qualquer utilidade nesta demonstração. Estamos prontos para executar os testes:

  • Pare e reinicie o Tomcat para que ele volte a ler o seu ficheiro de configuração server.xml. Aqui estamos a utilizar o Windows. No Unix, pode forçar o Tomcat a voltar a ler o seu ficheiro de configuração sem o parar.
  • Utilizando um navegador, aceda ao URL http://localhost:8080/liste/servlet/lstValeurs

Image

Podemos ver que a URL anterior contém a palavra-chave *servlet*, tal como todas as URLs de servlet utilizadas até agora. Podemos eliminar isto associando o servlet lstValeurs a um padrão de URL (*url-pattern*) no ficheiro web.xml da aplicação:

<?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>

Na tag <servlet-mapping>, associamos o caminho /values ao servlet lstValeurs definido nas linhas anteriores. Guardamos o novo ficheiro web.xml e acedemos à URL http://localhost:8080/liste/valeurs:

Image

3.3.3. Implantação das páginas públicas de uma aplicação web

Acabámos de ver como implementar uma aplicação web composta por um único servlet. Uma aplicação web pode ter muitos componentes: servlets, páginas JSP, ficheiros HTML, applets Java, etc. Onde colocamos estes elementos da aplicação? Se <application> é o diretório da aplicação web definido pelo atributo docBase da aplicação no ficheiro server.xml da configuração do Tomcat ( ), vimos que os servlets são colocados em <application>\WEB-INF\classes. Os outros componentes da aplicação podem ser colocados em qualquer local dentro da árvore de diretórios <application>, exceto no diretório WEB-INF. Considere a aplicação JSP listvaleurs.jsp que estudámos anteriormente:

Image

Esta página JSP estava armazenada na pasta <tomcat>\webapps\examples\jsp\perso\listvaleurs. Esta página poderia ser um componente da aplicação list previamente implementada. Vamos colocar o ficheiro listvaleurs.jsp diretamente na pasta desta aplicação:

Image

Lembre-se da configuração da aplicação liste no ficheiro server.xml:

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

Qualquer URL que comece com o caminho /liste é considerada parte da aplicação liste e será procurada na pasta especificada. Vamos aceder à URL http://localhost:8080/liste/listvaleurs.jsp utilizando um navegador:

Image

De facto, obtivemos a página JSP esperada.

3.3.4. Parâmetros de inicialização do Servlet

Vimos que um servlet é configurado pelo ficheiro <application>\WEB-INF\web.xml, em que <application> é a pasta da aplicação web à qual pertence. É possível incluir parâmetros de inicialização do servlet neste ficheiro. Voltemos ao nosso servlet lstValeurs da aplicação web liste, cujo ficheiro de configuração era o seguinte:

<?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>

A classe associada ao servlet é a classe gener3. O código-fonte desta classe contém a definição de várias constantes:

public class gener3 extends HttpServlet{
        // page title
        private final String title="Génération d'un formulaire";
         // the list values database
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";

Vamos rever o significado das quatro constantes definidas acima:

título
título do documento HTML gerado pelo servlet
DSNValues
nome DSN da base de dados ODBC da qual o servlet recupera dados
admDbValues
nome de utilizador de um utilizador com acesso de leitura à base de dados acima mencionada
dbPasswordValues
a sua palavra-passe

Se o administrador da base de dados DSNValeurs alterar a palavra-passe do utilizador admDbValeurs, o código-fonte do servlet terá de ser modificado e recompilado. Isto não é muito prático. O ficheiro de configuração web.xml do servlet oferece uma alternativa, permitindo a definição de parâmetros de inicialização do servlet através da tag <init-param>:

    <init-param>
        <param-name>...</param-name>
        <param-value>...</param-value>
    </init-param>
<param-name>
permite definir o nome do parâmetro
<param-value>
define o valor associado ao parâmetro anterior

O servlet pode aceder aos seus parâmetros de inicialização utilizando os seguintes métodos:

[Servlet].getServletConfig()
Um método da classe Servlet, da qual deriva a classe HttpServlet utilizada na programação web. Devolve um objeto ServletConfig que permite aceder aos parâmetros de configuração do servlet.
[ServletConfig].getInitParameter("parameter")
Um método da classe ServletConfig que devolve o valor do parâmetro de inicialização "parameter"

Configuramos a aplicação "liste" com o seguinte novo ficheiro 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>

Na aplicação de lista, definimos um segundo servlet chamado lstValeurs2 associado ao ficheiro de classe gener5. Este ficheiro foi colocado em <application>\WEB-INF\classes:

Image

O servlet lstValeurs2 tem quatro parâmetros de inicialização: title, DSNValeurs, admDbValeurs e mdpDbValeurs. Além disso, foi definido o alias /values2 para o servlet através da tag <servlet-mapping>. Assim, o servlet lstValeurs2 na aplicação de lista estará acessível através do URL http://localhost:8080/liste/valeurs2.

O código-fonte do servlet foi modificado da seguinte forma para recuperar os parâmetros de inicialização do servlet:

public class gener5 extends HttpServlet{
    // page title
    private String title=null;
    // the list values database
    private String DSNValeurs=null;
    private String admDbValeurs=null;
    private String mdpDbValeurs=null;
...............

         // servlet initialization
        public void init(){
             // retrieve servlet initialization parameters
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");
            DSNValeurs=config.getInitParameter("DSNValeurs");
            admDbValeurs=config.getInitParameter("admDbValeurs");
            mdpDbValeurs=config.getInitParameter("mdpDbValeurs");

             //have all the parameters been recovered?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // table of values is filled from a ODBC database
             // name DSN : DSNvaleurs
...............

Para testar o servlet, é necessário reiniciar o Tomcat para que este possa carregar o novo ficheiro de configuração web.xml para a aplicação «liste». Utilizando um navegador, aceda ao URL do servlet: http://localhost:8080/liste/valeurs2:

Image

Se algum dos parâmetros de inicialização exigidos pelo servlet estiver em falta no ficheiro web.xml, é apresentada a seguinte página:

Image

3.3.5. Parâmetros de inicialização para uma aplicação Web

No exemplo anterior, apenas o servlet lstValeurs2 tem acesso aos parâmetros title, DSNValeurs, admDbValeurs e mdpDbValeurs. É possível que outro servlet na mesma aplicação liste necessite de dados da mesma base de dados utilizada pelo servlet lstValeurs2. Nesse caso, os parâmetros DSNValeurs, admDbValeurs e mdpDbValeurs teriam de ser redefinidos na secção de configuração do ficheiro web.xml do novo servlet. Outra solução consiste em definir parâmetros comuns a vários servlets ao nível da aplicação, em vez de ao nível do servlet. O novo ficheiro web.xml para a aplicação fica da seguinte forma:

<?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>

O novo servlet chama-se lstValeurs3, está ligado ao ficheiro de classe gener6 e foi associado ao alias /valeurs3 (mapeamento de servlet). O parâmetro title é o único parâmetro que foi mantido na definição do servlet. Os outros foram colocados na configuração da aplicação dentro de tags <context-param>. Esta tag é utilizada para definir informações específicas da aplicação, e não de um servlet ou página JSP em particular. Como é que o servlet Java acede a estes parâmetros, frequentemente chamados de parâmetros de contexto? Os métodos disponíveis para obter informações de contexto são muito semelhantes aos utilizados para obter parâmetros de inicialização específicos do servlet:

[Servlet].getServletContext()
um método da classe Servlet da qual deriva a classe HttpServlet utilizada na programação web. Devolve um objeto ServletContext que fornece acesso aos parâmetros de configuração da aplicação
[ServletContext].getInitParameter("parameter")
método da classe ServletContext que devolve o valor do parâmetro de inicialização "parameter"

A classe gener6.java faz apenas as seguintes alterações ao código Java da classe gener5.java utilizada anteriormente:

             // retrieve servlet initialization parameters
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");

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

             //have all the parameters been recovered?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // table of values is filled from a ODBC database
             // name DSN : DSNvaleurs
...............

O parâmetro title específico do servlet é obtido através de um objeto ServletConfig. Os outros três parâmetros definidos ao nível da aplicação são obtidos através de um objeto ServletContext. Compilamos esta classe e colocamo-la, tal como as outras, em <application>\WEB-INF\classes:

Image

Reiniciamos o Tomcat para que ele tenha em conta o novo ficheiro web.xml da aplicação e solicitamos a URL http://localhost:8080/liste/valeurs3:

Image

3.3.6. Parâmetros de inicialização para uma página JSP

Já vimos como definir parâmetros de inicialização para um servlet ou uma aplicação web. Será que podemos fazer o mesmo para uma página JSP? Voltemos ao início do código da página listvaleurs.jsp que já estudámos:

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

<%!
        // variables globales de l'application
        // le titre de la page
        private final String title="Génération d'un formulaire";
        // la base de données des valeurs de liste
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
.........

Encontramos as quatro constantes title, DSNValues, admDbValues e mdpDbValues definidas no ficheiro web.xml da aplicação. As constantes DSNValues, admDbValues e mdpDbValues foram agora definidas ao nível da aplicação, pelo que podemos assumir que uma página JSP pertencente a esta aplicação terá acesso às mesmas. É de facto esse o caso. Sabemos que a página JSP será traduzida num servlet. O servlet terá acesso ao contexto através do método getServletContext(). O caso da constante title é mais delicado. Na verdade, definimo-la ao nível do servlet em vez de ao nível da aplicação, da seguinte forma:

    <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 a página JSP, a sintaxe anterior já não é adequada, uma vez que o conceito de ficheiro de classe já não se aplica. No entanto, a sintaxe de configuração para uma página JSP é muito semelhante à de um servlet. É a seguinte:

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

Na verdade, uma página JSP é tratada como um servlet ao qual é atribuído um nome (servlet-name). Em vez de associar um ficheiro de classe a este servlet, associamos o ficheiro fonte da página JSP a ser executada (jsp-file). Assim, as linhas anteriores definem um servlet chamado JSPlstValues associado à página JSP /listvalues2.jsp. O caminho /listvalues2.jsp é relativo à raiz da aplicação. Assim, no caso da nossa aplicação list, o ficheiro listvalues2.jsp estaria localizado na pasta docBase (ver server.xml) da aplicação list:

Image

A configuração da página JSP será a seguinte no ficheiro web.xml da aplicação:

<?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>

A página JSP listvaleurs2.jsp está localizada no diretório raiz da aplicação liste e está associada ao nome do servlet JSP JSPlstValeurs (servlet-name), que por sua vez está associado ao alias /jspvaleurs (servlet-mapping). Assim, a nossa página JSP estará acessível através do URL http://localhost:8080/liste/jspvaleurs.

A página JSP original listvaleurs.jsp é renomeada para listvaleurs2.jsp e recupera os seus quatro parâmetros de inicialização no método jspInit():

<%!
         // application global variables
         // page title
        private String title=null;
        // the list values database
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
         // list values
        private String[] valeurs=null;
         // error msg
        private String msgErreur=null;

         // initialization of page JSP - executed only once
        public void jspInit(){

             // retrieve servlet initialization parameters
      ServletConfig config=getServletConfig();
            title=config.getInitParameter("JSPtitle");
      ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");

             //have all the parameters been recovered?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // fills the table of values from a ODBC database
             // name DSN : DSNvaleurs
..............

A página JSP recupera os seus parâmetros de inicialização da mesma forma que os servlets. O ficheiro anterior é guardado no diretório raiz da aplicação web «liste»:

Image

O servidor Tomcat é reiniciado para forçá-lo a recarregar o novo ficheiro de configuração web.xml da aplicação. Pode então aceder ao URL http://localhost:8080/liste/jspvaleurs:

Image

3.3.7. Colaboração entre Servlets e JSP numa Aplicação Web

Quando um cliente faz uma solicitação a um servidor web, a resposta pode ser gerada por vários servlets e páginas JSP. Até agora, a resposta era gerada por um único servlet ou página JSP. Vimos que a página JSP oferece melhor legibilidade da estrutura do documento HTML gerado. No entanto, ela geralmente também contém muito código Java. Podemos melhorar isso colocando

  • o código Java que não gera o código HTML da resposta em um ou mais servlets
  • nas páginas JSP, o código para gerar os vários documentos HTML enviados em resposta ao cliente

Isto deverá ajudar a melhorar a separação entre o código Java e o código HTML. Vamos aplicar esta nova estrutura à nossa aplicação de lista: um servlet Java chamado lstValeurs4 será responsável por ler os valores da base de dados no arranque e, em seguida, processar os pedidos do cliente em . Dependendo do resultado desta análise, o pedido do cliente será direcionado para uma página de erro (erreur.jsp) ou para a página que apresenta a lista de números (liste.jsp). A aplicação de lista consistirá, portanto, num servlet e em duas páginas JSP.

Como é que um servlet pode passar a solicitação que recebeu de um cliente para outro servlet ou para uma página JSP? Iremos utilizar os seguintes métodos:

[ServletContext].getRequestDispatcher(
String url)
da classe ServletContext, que devolve um objeto RequestDispatcher. O parâmetro url é o nome da URL para a qual queremos reencaminhar a solicitação do cliente. Este reencaminhamento de solicitação só pode ocorrer dentro da mesma aplicação. Por conseguinte, o parâmetro url é um caminho relativo à estrutura de diretórios web dessa aplicação.
[RequestDispatcher].forward
(ServletRequest request,
 ServletResponse response)
Um método da interface RequestDispatcher que encaminha a solicitação do cliente e o objeto de resposta — que deve ser usado para construir a resposta — para a URL anterior.
[ServletRequest].setAttribute(String name, Object obj)
Quando um servlet ou uma página JSP encaminha um pedido para outro servlet ou página JSP, geralmente precisa de transmitir outras informações além do próprio pedido do cliente — informações derivadas do seu próprio processamento do pedido. O método setAttribute da classe ServletRequest permite adicionar atributos ao objeto de pedido do cliente num formato semelhante a um dicionário de pares (atributo, valor), em que atributo é o nome do atributo e valor é qualquer objeto que represente o seu valor.
[ServletRequest].getAttribute(
String atributo)
permite-lhe recuperar os valores dos atributos de uma solicitação. Este método será utilizado pelo servlet ou pela página JSP para o qual a solicitação foi encaminhada, a fim de obter as informações adicionadas à mesma.

O servlet responsável pelo processamento do formulário será configurado da seguinte forma no ficheiro 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>

O servlet lstValeurs4 terá quatro parâmetros de inicialização específicos:

título
o título do documento HTML a ser gerado
JSPerreur
o URL da página JSP de erro
JSPlist
A URL da página JSP que exibe a lista de números
URL do Servlet
A URL associada ao atributo action do formulário apresentado pela página JSPlist. Esta URL será a do servlet lstValeurs4

O servlet terá o alias /valeurs4 (servlet-mapping) e, portanto, estará acessível através da URL http://localhost:8080/liste/valeurs4. Está ligado ao ficheiro de classe gener7.java, cujo código-fonte completo é o seguinte:

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

public class gener7 extends HttpServlet{
        // page title
        private String title=null;
        // the list values database
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
         // JSP display pages
        private String JSPerreur=null;
        private String JSPliste=null;
        // the URL of the servlet
        private String URLservlet=null;
        // list values
        private String[] valeurs=null;
         // error msg
        private String msgErreur=null;

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

            // put msgErreur,title in the query attributes
            request.setAttribute("msgErreur",msgErreur);
            request.setAttribute("title",title);
            request.setAttribute("URLservlet",URLservlet);

             // was there an error loading the servlet?
            if(msgErreur!=null){
                 // we hand over to a JSP error page
                getServletContext().getRequestDispatcher(JSPerreur).forward(request,response);
                 // end
                return;
            }

             // there was no error
             // put the list of values in the query attributes
            request.setAttribute("valeurs",valeurs);

             // we retrieve the user's possible choice
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";
            request.setAttribute("choix",choix);

             // hand over to the JSP list presentation page
            getServletContext().getRequestDispatcher(JSPliste).forward(request,response);
            // end
            return;
        }//GET

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

             // returns to GET
            doGet(request,response);
        }//POST

        // -----------------------------------------------------------------
         // servlet initialization
        public void init(){

             // retrieve servlet initialization parameters
            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");


             //have all the parameters been recovered?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null || JSPerreur==null || JSPliste==null || URLservlet==null){
                msgErreur="Configuration incorrecte";
                return;
            }

             // fills the table of values from a ODBC database
             // name DSN : DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                 // connection to the ODBC database
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                 // statement object
                st=connexion.createStatement();
                 // execute select query to retrieve values
                rs=st.executeQuery("select valeur from Tvaleurs");
                // values are retrieved and put into a dynamic table
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // the value is saved in the
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                 // transformation list --> table
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                 // problem
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//init
    }//class

A nova funcionalidade desta classe é que ela redireciona o pedido do cliente para a página JSPerreur em caso de erro e, caso contrário, para a página JSPliste. A classe não gera a resposta por si própria. As páginas JSP JSPerreur e JSPliste tratam disso. Anteriormente, o servlet adicionava atributos (setAttribute) ao pedido do cliente:

  • uma mensagem de erro msgError em caso de erro para a página JSPerreur
  • os valores (values) a exibir, o valor selecionado (choice) pelo utilizador, o título do formulário (title) e o URL (URLservlet) do atributo action do formulário para a página JSPliste

Esta classe é compilada e colocada nas classes da aplicação:

Image

A página JSP que exibe uma mensagem de erro é configurada da seguinte forma:

    <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>

O ficheiro JSP associado à página de erro chama-se error.jsp e está localizado na raiz da aplicação:

Image

Tem o alias /JSPerreur, o que o torna acessível através do URL http://localhost:8080/liste/JSPerreur. Possui um parâmetro de inicialização chamado mainServlet, cujo valor é o alias do servlet principal descrito acima. Note-se que este alias é relativo à raiz da aplicação liste; caso contrário, seria /liste/valeurs4. O código da página erreur.jsp é o seguinte:

<%
    // code de _jspService
  // on récupère le paramètre d'initialisation mainServlet
  String servletListValeurs=config.getInitParameter("mainServlet");
  // on récupère l'attribut msgErreur
  String msgErreur=(String)request.getAttribute("msgErreur");
  // attribut valide ?
  if(msgErreur!=null){
%>
       <!-- code HTML -->
    <html>
        <head>
          <title>Erreur</title>
      </head>
      <body>
          <h3>Application indisponible (<%= msgErreur %>)</h3>
      </body>
    </html>
<%
    } else { // attribut msgErreur invalide - retour à la servlet principale
%>
    <jsp:forward page="<%= servletListValeurs %>" />  
<%    
  }
%>  

Esta página deve normalmente ser chamada pelo servlet anterior, que deve passar-lhe o atributo msgError. No entanto, nada o impede de a chamar diretamente se souber o seu URL. Além disso, se verificar que o atributo msgError está em falta, deve passar o pedido para o servlet principal. Aqui, utilizamos uma tag específica para páginas JSP, cuja sintaxe é:

<jsp:forward page="URL" />

onde URL é a URL do servlet para o qual a solicitação do cliente é encaminhada. Se o atributo msgError estiver presente, a página de erro é exibida.

A página JSP que exibe a lista de números está configurada da seguinte forma:

    <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>

O ficheiro JSP associado à página de erro chama-se liste.jsp e está localizado na raiz da aplicação:

Image

O servlet tem o alias /JSPliste, o que o torna acessível através do URL http://localhost:8080/liste/JSPliste. Possui um parâmetro de inicialização chamado mainServlet, cujo valor é o alias do servlet principal. O código da página liste.jsp é o seguinte:

  <%-- page d'affichage de la liste des valeurs --%>
  <%
      // code de jspService
      // on récupère le paramètre d'initialisation
      String servletListValeurs=config.getInitParameter("mainServlet");

    // on récupère les attributs de la requête venant de la servlet principale
    String title=(String) request.getAttribute("title");
    String[] valeurs=(String[]) request.getAttribute("valeurs");
    String choix=(String) request.getAttribute("choix");
    String URLservlet=(String) request.getAttribute("URLservlet");

    // attributs valides ?
    if(title==null || valeurs==null || choix==null){
        // il y a un attribut invalide - on passe la main à la servlet
   %>
   <jsp:forward page="<%= servletListValeurs %>" />
    <%
    }//if
  %>

  <%-- code HTML --%>  
      <html>
        <head>
          <title><%= title %></title>
      </head>
      <body>
          <h3>Choisissez une valeur</h3>
          <form method="POST" action="<%= URLservlet %>">
            <select name="cmbValeurs">
              <%
                // affichage dynamique des valeurs
                          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>
                <%
            // y-avait-il une valeur choisie ?
                    if(! choix.equals("")){
                // on affiche le choix de l'utilisateur
                %>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
                <%        
                    }//if
                %>        
      </body>
    </html>

Esta página funciona como a página error.jsp. Normalmente, deve ser chamada pelo servlet /list/values4 e receber os atributos title, values e choice. Se algum destes parâmetros estiver em falta, o controlo é passado para o servlet URLservlet (/liste/valeurs4). Se todos os parâmetros estiverem presentes, a lista de números é apresentada juntamente com o número escolhido pelo utilizador, se houver algum.

Se solicitar a URL do servlet principal, obtém o seguinte resultado:

Image

com o seguinte código-fonte (Ver/Fonte):

<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 foi gerado pela página JSP liste.jsp. Podemos ver que os atributos title, values e URLservlet foram recuperados com sucesso.

Para concluir sobre a colaboração entre servlets e páginas JSP, observamos que as páginas JSP aqui são muito curtas e não contêm código Java que não contribua diretamente para a criação da resposta HTML. A estrutura dos documentos gerados é, assim, mais visível.

3.4. Ciclo de vida de servlets e páginas JSP

3.4.1. O ciclo de vida

Aqui, focamo-nos no ciclo de vida do servlet. O ciclo de vida das páginas JSP decorre deste. Considere um servlet a ser chamado pela primeira vez. Uma instância da classe é então criada pelo servidor web e carregada na memória. Em seguida, ela atenderá à solicitação. Uma vez feito isso, o servlet não é descarregado da memória. Ele permanece lá para atender a outras solicitações, a fim de otimizar os tempos de resposta do servidor. Ele será descarregado quando tiver passado um período de tempo suficientemente longo sem que tenha atendido a nenhuma nova solicitação. Esse tempo é geralmente configurável no servidor web.

Enquanto estiver na memória, o servlet pode processar várias solicitações simultaneamente. O servidor web cria um thread por solicitação, todos os quais utilizam a mesma instância do servlet:

Todas as threads acima referidas partilham as variáveis da instância do servlet. Pode ser necessário sincronizar as threads para evitar a corrupção dos dados do servlet. Voltaremos a este assunto mais tarde.

Quando um servlet é carregado, um método específico do servlet é executado:

public void init() throws ServletException{
}

Para uma página JSP, é o método


  public void jspInit(){
  }

que é executado. Aqui está um exemplo de uma página JSP que utiliza o método jspInit:

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

<%!
  // variables et méthodes globales de la page JSP

  // variable d'instance
  int compteur;

  // méthode pour incrémenter le compteur  
  public int getCompteur(){
    // on incrémente le compteur
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
    // on le rend
    return compteur;
  }

  // la méthode exécutée au chargement initial de la page
  public void jspInit(){
    // init compteur
    compteur=100;
  }
%>

A página JSP anterior inicializa um contador com o valor 100 em jspInit. Qualquer pedido subsequente ao servlet incrementa e, em seguida, apresenta o valor deste contador:

Na primeira vez:

Image

Na segunda vez:

Image

Como se pode ver acima, entre as duas solicitações, o servlet não foi descarregado; caso contrário, o contador teria sido 101 durante a segunda solicitação. Quando o servlet é descarregado, o método

public void destroy(){
}

é executado, caso exista. Para páginas JSP, é o método


  public void jspDestroy(){
  }

Nestes métodos, pode, por exemplo, fechar ligações à base de dados que foram abertas nos métodos init correspondentes.

3.4.2. Sincronização de pedidos para um servlet

Voltemos à página JSP anterior que incrementa um contador e o devolve ao cliente web. Suponhamos que existem duas solicitações simultâneas. São então criadas duas threads para as executar; estas threads utilizarão a mesma instância de servlet e, portanto, o mesmo contador. Recordemos o código que incrementa o contador:


  public int getCompteur(){
    // on incrémente le compteur
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
    // on le rend
    return compteur;
  }

O incremento do contador foi intencionalmente escrito de forma desajeitada. Suponha que a execução das duas threads decorra da seguinte forma:

 
  1. No momento T1, a thread TH1 é executada. Ela lê o contador (=145) de myCounter, depois é interrompida e perde o processador. Por isso, não teve tempo de incrementar myCounter e copiar o novo valor para counter.
  2. No momento T2, a thread TH2 é executada. Ela lê o contador (=145) de myCounter, depois é interrompida e perde o processador. Note-se que as duas threads têm variáveis myCounter diferentes. Elas partilham apenas variáveis de instância, aquelas que são globais para os métodos.
  3. No momento T3, a thread TH1 recupera o controlo e termina. Por isso, devolve 146 ao seu cliente.
  4. No momento T4, a thread TH2 recupera o controlo e termina. Ela também devolve 146 ao seu cliente, quando deveria ter devolvido 147.

Trata-se de um problema de sincronização entre threads. Quando a TH1 pretende incrementar o contador, deve impedir que qualquer outra thread faça o mesmo. Para destacar este problema, reescrevemos a página JSP da seguinte forma:

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

<%!
  // variables et méthodes globales de la page JSP

  // variable d'instance
  int compteur;

  // méthode pour incrémenter le compteur  
  public int getCompteur(){
    // on lit le compteur
    int myCompteur=compteur;
    // on s'arrête 10 secondes
    try{
      Thread.sleep(10000);
    }catch (Exception ignored){}
    // on incrémente le compteur
    compteur=myCompteur+1;
    // on le rend
    return compteur;
  }

  // la méthode exécutée au chargement initial de la page
  public void jspInit(){
    // init compteur
    compteur=100;
  }
%>

Aqui, forçámos o thread a parar 10 segundos após a leitura do contador. Deverá, portanto, libertar a CPU, permitindo que outro thread leia um contador que não foi incrementado. Quando fazemos pedidos com um navegador, não notamos qualquer diferença além da espera de 10 segundos antes de obter o resultado.

Image

Agora, se abrirmos duas janelas do navegador e fizermos duas solicitações com intervalo de tempo muito curto entre elas:

Image

Image

Obtemos o mesmo valor do contador. Podemos destacar melhor o problema com um cliente programado em vez de um manual, como o navegador. Aqui está um cliente Perl que é chamado da seguinte forma:

*program URL N*

onde

URL é a URL do servlet de contagem

N é o número de pedidos a fazer a este servlet

Aqui estão os resultados obtidos para 5 pedidos, que demonstram claramente o problema da má sincronização de threads: todos devolvem o mesmo valor do contador.


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

O código do cliente Java é o seguinte.

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

public class clientCompteurJSP {

    public static void main(String[] params){

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

         // parameter verification
        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
         // number of calls
        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

         // parameters are correct - connections can be made to the 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
    }//hand

    private static void getCompteurs (URL urlCompteur, int nbAppels)
            throws Exception {
         // does nbAppels at URL urlCompteur
         // displays the counter value returned by the web server each time


         // remove from urlCompteur the info needed to connect to the tax server
        String path=urlCompteur.getPath();
        if(path.equals("")) path="/";
        String host=urlCompteur.getHost();
        int port=urlCompteur.getPort();
        if(port==-1) port=urlCompteur.getDefaultPort();

         // calls are made to the URL
        Socket[] clients=new Socket[nbAppels];
        for(int i=0;i<nbAppels;i++){
             // connect to the server
            clients[i]=new Socket(host,port);
             // create a write stream to the server
            PrintWriter OUT=new PrintWriter(clients[i].getOutputStream(),true);
             // request URL - send HTTP headers
            OUT.println("GET " + path + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            OUT.println("Connection: close");
            OUT.println("");
        }//for

         // local data
        String réponse=null;                        // server response
         // the model searched for in the HTML server response
        Pattern modèleCompteur=Pattern.compile("^\\s*Compteur= (\\d+)");
         // the model for a correct answer
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // the result of the model comparison
        Matcher résultat=null;

        for(int i=0;i<nbAppels;i++){
             // each client reads the response sent by the server

             // create customer input/output flows TCP
            BufferedReader IN=new BufferedReader(new InputStreamReader(clients[i].getInputStream()));

             // read the 1st line of the answer
            réponse=IN.readLine();
             // compare the HTTP line with the model of the correct answer
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                 // we have a URL problem
                throw new Exception("Client n° " + i + " - Le serveur a répondu : URL ["+ urlCompteur + "] inconnue");
            }//if

             // the response is read through to the end of the headers
            while((réponse=IN.readLine())!=null && ! réponse.equals("")){
            }//while

             // that's it for HTTP headers - move on to HTML code
             // to retrieve the counter value
            boolean compteurTrouvé=false;
            while((réponse=IN.readLine())!=null){
                 // compare the line with the counter model
                if(! compteurTrouvé){
                    résultat=modèleCompteur.matcher(réponse);
                    if(résultat.find()){
                         // meter found
                        System.out.println("Compteur="+résultat.group(1));
                        compteurTrouvé=true;
                    }//if
                }//if
            }//while

             // it's over
            clients[i].close();
        }//for
    }//getCompteurs

}//class

Vamos explicar o código anterior:

  • O programa aceita dois parâmetros:
    • a URL da página JSP para o contador
    • o número de clientes a criar para esta URL
  • O programa começa, portanto, por verificar a validade dos parâmetros: que existem efetivamente dois, que o primeiro se assemelha a uma URL sintaticamente e que o segundo é um número inteiro maior que 0. Para verificar se a URL está sintaticamente correta, utilizamos a classe URL e o seu construtor URL(String), que cria um objeto URL a partir de uma string como http://istia.univ-angers.fr. É lançada uma exceção se a string não for uma URL sintaticamente válida. Isto permite-nos verificar a validade do primeiro parâmetro.
  • Uma vez verificados os parâmetros, o controlo é passado para o procedimento getCompteurs. Este procedimento irá criar nbAppels clientes, todos os quais se ligarão simultaneamente (ou quase) à URL urlCompteur.
  • A porta e a máquina à qual os clientes devem ligar-se são derivadas da URL urlCompteur: [URL].getHost() devolve o nome da máquina e [URL].getPort() devolve a porta.
  • Um primeiro ciclo permite que cada cliente:
    • ligar-se ao servidor web
    • solicitar a URL urlCompteur

Neste ciclo, o cliente não aguarda a resposta do servidor. Isto porque queremos que o servidor receba pedidos quase simultâneos.

  • Um segundo ciclo permite que cada cliente receba e processe a resposta enviada pelo servidor. O processamento envolve encontrar a linha na resposta que contém o valor do contador e exibi-lo.

Para resolver o problema destacado anteriormente (o mesmo contador a ser enviado a todos os cinco clientes), precisamos de sincronizar os threads do serviço de contagem num único objeto antes de entrar na secção crítica para ler e atualizar o contador. A nova página JSP é a seguinte:


<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>
 
<%!
  // variables et méthodes globales de la page JSP
 
  // variables d'instance
  int compteur;
  Object verrou=new Object();
 
  // méthode pour incrémenter le compteur  
  public int getCompteur(){
 
    // on syncronise la section critique
    synchronized(verrou){
      // on lit le compteur
      int myCompteur=compteur;
      // on s'arrête 10 secondes
      try{
        Thread.sleep(10000);
      }catch (Exception ignored){}
      // on incrémente le compteur
      compteur=myCompteur+1;
    }//synchronized
    // on le rend
    return compteur;
  }//getCompteur
 
  // la méthode exécutée au chargement initial de la page
  public void jspInit(){
    // init compteur
    compteur=100;
  }
%>

Quando executado, obtêm-se os seguintes 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

A documentação indica que o servidor web pode, por vezes, criar várias instâncias do mesmo servlet. Neste caso, a sincronização anterior deixa de funcionar porque a variável de bloqueio é local a uma única instância e, por isso, não é conhecida pelas outras instâncias. O mesmo se aplica à variável contador. Para torná-las globais para todas as instâncias, escrevemos:


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

O resto do código permanece inalterado.