Skip to content

4. Rastreamento de sessão

4.1. O problema

Um aplicativo web pode consistir em várias trocas de formulários entre o servidor e o cliente. Nesse caso, o funcionamento é o seguinte:

  • etapa 1
    • o cliente C1 abre uma conexão com o servidor e faz sua solicitação inicial.
    • O servidor envia o formulário F1 ao cliente C1 e encerra a conexão aberta na etapa 1.
  • etapa 2
    • O cliente C1 preenche o formulário e o reenvia ao servidor. Para isso, o navegador abre uma nova conexão com o servidor.
    • Este processa os dados do formulário 1, calcula as informações I1 a partir deles, envia um formulário F2 ao cliente C1 e encerra a conexão aberta na etapa 3.
  • Etapa 3
    • O ciclo das etapas 3 e 4 se repete nas etapas 5 e 6. Ao final da etapa 6, o servidor terá recebido dois formulários F1 e F2 e, a partir deles, terá calculado as informações I1 e I2.

O problema que se coloca é: como o servidor consegue manter as informações I1 e I2 associadas ao cliente C1? Esse problema é conhecido como acompanhamento da sessão do cliente C1. Para compreender sua origem, vamos examinar o esquema de um aplicativo servidor TCP-IP que atende simultaneamente a vários clientes:

Em uma aplicação cliente-servidor TCP-IP clássica:

  • o cliente estabelece uma conexão com o servidor
  • troca dados com o servidor por meio dessa conexão
  • a conexão é encerrada por um dos dois participantes

Os dois pontos importantes desse mecanismo são:

  1. uma única conexão é criada para cada um dos clientes
  2. essa conexão é utilizada durante toda a duração do diálogo entre o servidor e seu cliente

O que permite ao servidor saber, a qualquer momento, com qual cliente está trabalhando é a conexão, ou, em outras palavras, o “canal” que o conecta ao seu cliente. Como esse canal é dedicado a um determinado cliente, tudo o que chega por esse canal vem desse cliente e tudo o que é enviado por esse canal chega ao cliente.

O mecanismo cliente-servidor HTTP segue fielmente o esquema anterior, com a particularidade de que a comunicação cliente-servidor se limita a uma única troca entre o cliente e o servidor:

  • o cliente abre uma conexão com o servidor e faz sua solicitação
  • o servidor responde e encerra a conexão

Se, no momento T1, um cliente C fizer uma solicitação ao servidor, ele obterá uma conexão C1 que servirá para a única troca de solicitação e resposta. Se, no momento T2, esse mesmo cliente fizer uma segunda solicitação ao servidor, ele obterá uma conexão C2, diferente da conexão C1. Para o servidor, não há, portanto, nenhuma diferença entre essa segunda solicitação do usuário C e sua solicitação inicial: nos dois casos, o servidor considera o cliente como um novo cliente. Para que haja uma ligação entre as diferentes conexões do cliente C ao servidor, é necessário que o cliente C seja “reconhecido” pelo servidor como um “usuário habitual” e que o servidor recupere as informações que possui sobre esse usuário habitual.

Imaginemos um sistema de gerenciamento que funcionasse da seguinte maneira:

  • Há uma única fila
  • Existem vários guichês. Portanto, vários clientes podem ser atendidos simultaneamente. Quando um guichê fica livre, um cliente sai da fila para ser atendido nesse guichê
  • Se for a primeira vez que o cliente se apresenta, a pessoa no guichê lhe entrega uma ficha com um número. O cliente só pode fazer uma pergunta. Ao receber a resposta, ele deve sair do guichê e ir para o final da fila de espera. O atendente do guichê anota as informações desse cliente em um arquivo com o número da ficha.
  • Quando chegar novamente a sua vez, o cliente poderá ser atendido por um atendente diferente do da vez anterior. Este solicita a ficha e pega o arquivo com o número da ficha. Mais uma vez, o cliente faz uma solicitação, recebe uma resposta e as informações são adicionadas ao seu arquivo.
  • E assim por diante... Com o passar do tempo, o cliente terá a resposta para todas as suas solicitações. O acompanhamento entre as diferentes solicitações é feito por meio do ficha e do arquivo associado a ele.

O mecanismo de acompanhamento de sessão em um aplicativo cliente-servidor web é semelhante ao funcionamento descrito acima:

  • na sua primeira solicitação, o cliente recebe um token do servidor web
  • ele apresentará esse token em cada uma de suas solicitações subsequentes para se identificar

O token pode assumir diferentes formas:

  • um campo oculto em um formulário
    • o cliente faz sua primeira solicitação (o servidor o reconhece pelo fato de o cliente não ter um token)
    • o servidor envia sua resposta (um formulário) e insere o token em um campo oculto desse formulário. Nesse momento, a conexão é encerrada (o cliente sai do servidor com seu token). O servidor pode ter se encarregado de associar informações a esse token.
    • o cliente faz sua segunda solicitação, reenviando o formulário. O servidor recupera o token desse formulário. Ele pode então processar a segunda solicitação do cliente, tendo acesso, graças ao token, às informações calculadas durante a primeira solicitação. Novas informações são adicionadas ao arquivo vinculado ao token, uma segunda resposta é enviada ao cliente e a conexão é encerrada pela segunda vez. O token foi colocado novamente no formulário da resposta para que o usuário possa apresentá-lo em sua próxima solicitação.
    • e assim por diante...

A principal desvantagem dessa técnica é que o token precisa ser inserido em um formulário. Se a resposta do servidor não for um formulário, o método do campo oculto não pode mais ser utilizado.

  • A técnica do cookie
    • o cliente faz sua primeira solicitação (o servidor o reconhece pelo fato de o cliente não ter um token)
    • o servidor responde adicionando um cookie nos cabeçalhos HTTP da resposta. Isso é feito por meio do comando HTTP Set-Cookie:

Set-Cookie: param1=valor1;param2=valor2;....

onde parami são nomes de parâmetros e valeursi são seus valores. Entre os parâmetros, estará o token. Muitas vezes, há apenas o token no cookie, sendo que as demais informações são registradas pelo servidor na pasta vinculada ao token. O navegador que recebe o cookie irá armazená-lo em um arquivo no disco. Após a resposta do servidor, a conexão é encerrada (o cliente sai do guichê com seu token).

  • (continuação)
    • o cliente faz sua segunda solicitação ao servidor. Sempre que uma solicitação é feita a um servidor, o navegador verifica, entre todos os cookies que possui, se há algum proveniente do servidor solicitado. Se houver, ele o envia ao servidor sempre na forma de um comando HTTP, o comando Cookie, cuja sintaxe é semelhante à do comando Set-Cookie utilizado pelo servidor:

Cookie: param1=valor1;param2=valor2;....

Entre os parâmetros enviados pelo navegador, o servidor encontrará o token que lhe permite reconhecer o cliente e recuperar as informações associadas a ele.

Essa é a forma mais utilizada de token. Ela apresenta uma desvantagem: um usuário pode configurar seu navegador para que ele não aceite cookies. Esse tipo de usuário, portanto, não tem acesso a aplicativos da web que utilizam cookies.

  • Reescrita de URL
    • o cliente faz sua primeira solicitação (o servidor o reconhece pelo fato de o cliente não possuir um token)
    • o servidor envia sua resposta. Ela contém links que o usuário deve utilizar para continuar na aplicação. No URL de cada um desses links, o servidor adiciona o token na forma URL;token=valor.
    • Quando o usuário clica em um dos links para continuar a aplicação, o navegador faz sua solicitação ao servidor web, enviando nos cabeçalhos HTTP o URL URL;token=valor solicitado. O servidor é então capaz de recuperar o token.

4.2. Java para acompanhamento de sessão

Apresentamos agora os principais métodos úteis para o acompanhamento de sessão:

HttpSession [HttpServletRequest].getSession()
obtém o objeto Session ao qual pertence a solicitação atual. Se esta ainda não fizesse parte de uma sessão, a sessão é criada.
String [HttpSession].getId()
identificador da sessão atual
long [HttpSession].getCreationTime()
data de criação da sessão atual (número de milissegundos decorridos desde 1º de janeiro de 1970, 0h).
long [HttpSession].getLastAccessedTime()
data do último acesso à sessão pelo cliente
long [HttpSession].getMaxInactiveInterval()
tempo máximo, em segundos, de inatividade de uma sessão. Após esse período, a sessão é invalidada.
[HttpSession].setMaxInactiveInterval(int durée)
define, em segundos, o tempo máximo de inatividade de uma sessão. Após esse período, a sessão é invalidada.
boolean [HttpSession].isNew()
verdadeiro se a sessão acabou de ser criada
[HttpSession].setAttribute(String paramètre, Object valeur)
associa um valor a um parâmetro em uma determinada sessão. É esse mecanismo que permite armazenar informações que permanecerão disponíveis durante toda a sessão.
[HttpSession].removeAttribute(String paramètre)
remove parametre dos dados da sessão.
Object [HttpSession].getAttribute(String paramètre)
valor associado ao parâmetro paramètre da sessão. Retorna null caso este não exista.
Enumeration [HttpSession].getAttributeNames()
lista, na forma de enumeração, de todos os atributos da sessão atual
[HttpSession].invalidate()
encerra a sessão atual. Todas as informações associadas a ela são eliminadas.

4.3. Exemplo 1

Apresentamos um exemplo extraído do excelente livro “Programação com J2EE”, publicado pela editora Wrox e distribuído pela Eyrolles. Este livro é uma fonte rica de informações de alto nível para desenvolvedores de soluções web em Java. O aplicativo apresentado neste livro na forma de um único servlet Java foi aqui reproduzido na forma de um servlet principal que utiliza páginas JSP para exibir as diversas respostas possíveis ao cliente.

A aplicação chama-se “sessions” e está configurada da seguinte forma no arquivo <tomcat>\conf\server.xml:

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

Na pasta docBase acima, encontram-se os seguintes elementos:

Image

Os arquivos erreur.jsp, invalide.jsp e valide.jsp estão todos associados à aplicação sessions. Na pasta WEB-INF acima, encontram-se:

Image

Acima, vemos o arquivo web.xml de configuração do aplicativo sessions. Na pasta classes, encontramos o arquivo de classe do servlet:

Image

O arquivo web.xml da aplicação é 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>cycledevie</servlet-name>
    <servlet-class>cycledevie</servlet-class>
    <init-param>
          <param-name>urlSessionValide</param-name>
        <param-value>/valide.jsp</param-value>
    </init-param>
    <init-param>
          <param-name>urlSessionInvalide</param-name>
        <param-value>/invalide.jsp</param-value>
    </init-param>
    <init-param>
          <param-name>urlErreur</param-name>
        <param-value>/erreur.jsp</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
      <servlet-name>cycledevie</servlet-name>
    <url-pattern>/cycledevie</url-pattern>
  </servlet-mapping>
</web-app>

O servlet principal se chama cycledevie (servlet-name) e está associado ao arquivo de classe cycledevie.class (servlet-class). Ela possui um alias /cycledevie (servlet-mapping) que permite chamá-la por meio do URL http://localhost:8080/sessions/cycledevie. Ela possui três parâmetros de inicialização:

urlSessionValide
URL da página que apresenta as características da sessão atual
urlSessionInvalide
URL da página exibida após a invalidação da sessão atual
urlErreur
URL da página exibida em caso de erro na inicialização do servlet principal `cycledevie`

Os componentes da aplicação “sessions” são os seguintes:

cycledevie
servlet principal — analisa a solicitação do cliente:
  • se ela fizer parte de uma sessão, redireciona para a página valide.jsp, que exibirá as características dessa sessão. A partir dessa página, o usuário pode:
    • recarregá-la
    • cancelá-la
  • se a solicitação pedir para invalidar a sessão atual, o servlet redireciona para a página invalide.jsp, que oferecerá ao usuário a opção de recriar uma nova sessão
  • se, durante sua inicialização, o servlet encontrar erros, ele repassa o controle para a página erreur.jsp, que exibirá uma mensagem de erro.
valide.jsp
  • exibe as características da sessão atual e oferece dois links:
    • um para recarregar a página e, assim, acompanhar a evolução do parâmetro do último acesso à sessão atual
    • e outro para invalidar a sessão atual
invalide.jsp
exibida quando o usuário invalida a sessão atual. Sugere, então, recriar uma nova sessão.
erreur.jsp
exibida quando o servlet principal encontra erros durante sua inicialização.

O servlet principal cycledevie é o seguinte:

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

public class cycledevie extends HttpServlet{

    // variáveis de instância
    String msgErreur=null;
    String urlSessionInvalide=null;
    String urlSessionValide=null;
    String urlErreur=null;

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

        // A inicialização ocorreu corretamente?
        if(msgErreur!=null){
             // redirecionamos para a página de erro
            getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
        }

         // recuperamos a sessão atual
        HttpSession session=request.getSession();

         // analisando a ação a ser realizada
        String action=request.getParameter("action");
        // invalidar a sessão atual
        if(action!=null && action.equals("invalider")){
            // invalida-se a sessão atual
            session.invalidate();
             // passa o controle para a URL urlSessionInvalide
            getServletContext().getRequestDispatcher(urlSessionInvalide).forward(request,response);
        }
         // outros casos
         // o controle é transferido para a URL urlSessionInvalide
        getServletContext().getRequestDispatcher(urlSessionValide).forward(request,response);
    }

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

     //-------- INIT
    public void init(){
         // recuperamos os parâmetros de inicialização
        ServletConfig config=getServletConfig();
        urlSessionInvalide=config.getInitParameter("urlSessionInvalide");
        urlSessionValide=config.getInitParameter("urlSessionValide");
        urlErreur=config.getInitParameter("urlErreur");

        // parâmetros corretos?
        if(urlSessionValide==null || urlSessionInvalide==null){
            msgErreur="Configuration incorrecte";
        }
    }
}

Observe os seguintes pontos:

  • em seu método de inicialização, o servlet recupera seus três parâmetros
  • no processamento (doGet) de uma solicitação, a servlet:
    • primeiramente verifica se não houve erros durante a inicialização. Caso tenha havido, ela passa o controle para a página erreur.jsp.
    • verifica o valor do parâmetro action. Se este tiver o valor “invalider”, o servlet passa o controle para a página invalide.jsp; caso contrário, para a página valide.jsp.

A página JSP valide.jsp exibe as características da sessão atual:

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

<%
     // jspService
   // aqui estamos no caso em que é preciso descrever a sessão atual
  String etat= session.isNew() ? "Nouvelle session" : "Ancienne session";
%>
<!-- início da página HTML -->
  <html>
      <meta http-equiv="pragma" content="no-cache">
    <head>
        <title>Cycle de vie d'une session</title>
    </head>
    <body>
        <h3>Cycle de vie d'une session</h3>
        <hr>
        <br>Etat session : <%= etat %>
      <br>ID session : <%= session.getId() %>
      <br>Heure de création : <%= new Date(session.getCreationTime()) %>
      <br>Heure du dernier accès : <%= new Date(session.getLastAccessedTime()) %>
      <br>Intervalle maximum d'inactivité : <%= session.getMaxInactiveInterval() %>
      <br><a href="/sessions/cycledevie?action=invalider">Invalider la session</a>
      <br><a href="/sessions/cycledevie">Recharger la page</a>
    <body>
  </html>

Observe-se que na linha

  String etat= session.isNew() ? "Nouvelle session" : "Ancienne session";

é utilizado um objeto de sessão que surge do nada. Na verdade, esse objeto faz parte dos objetos implícitos disponibilizados para as páginas JSP, assim como os objetos request, response, out, config (ServletConfig), context (ServletContext), já mencionados anteriormente. Os dois links da página remetem ao servlet cycledevie apresentado anteriormente:

      <br><a href="/sessions/cycledevie?action=invalider">Invalider la session</a>
      <br><a href="/sessions/cycledevie">Recharger la page</a>

O link para invalidar a sessão inclui o parâmetro action=invalider, que permitirá que o servlet cycledevie reconheça que o usuário deseja invalidar a sessão atual. O outro link permite recarregar a página. Para que o navegador não busque a página no cache, a diretiva HTML:

      <meta http-equiv="pragma" content="no-cache">

foi utilizada. Ela instrui o navegador a não utilizar o cache para a página que recebe.

A página invalide.jsp é a seguinte:

<!-- início da página HTML -->
<html>
  <head>
      <title>Cycle de vie d'une session</title>
  </head>
  <body>
      <h3>Cycle de vie d'une session</h3>
      <hr>
    Votre session a été invalidée
    <a href="/sessions/cycledevie">Créer une nouvelle session</a>
  </body>
</html>

Ela oferece um link que aponta para o servlet cycledevie sem o parâmetro action. Esse link fará com que o servlet cycledevie crie uma nova sessão.

A página erreur.jsp é a seguinte:

<%
     // jspService
   // aqui estamos no caso em que é preciso descrever a sessão atual
  String msgErreur= request.getAttribute("msgErreur");
  if(msgErreur==null) msgErreur="Erreur non identifiée)";
%>
<!-- início da página HTML -->
<html>
  <head>
      <title>Cycle de vie d'une session</title>
  </head>
  <body>
      <h3>Cycle de vie d'une session</h3>
      <hr>
    Application indisponible(<%= msgErreur %>)
  </body>
</html>

Sua função é exibir a mensagem de erro que lhe foi enviada pelo servlet cycledevie. Vejamos agora alguns exemplos de execução. O servlet é chamado pela primeira vez:

Image

A página acima indica que estamos em uma nova sessão. Utilizamos o link “Atualizar a página”:

Image

O resultado anterior indica que ainda estamos na mesma sessão da página anterior (o mesmo ID). Observe que a hora do último acesso a essa sessão mudou. Agora, vamos usar o link “Invalidar a sessão”:

Image

Observe-se o URL desta nova página com o parâmetro action=invalider. Vamos usar o link “Criar uma nova sessão” para criar uma nova sessão:

Image

Percebe-se que uma nova sessão foi iniciada. Nos exemplos anteriores, a sessão se baseia no mecanismo de cookies. Vamos agora desativar o uso de cookies em nosso navegador e repetir os testes. Os exemplos a seguir foram realizados com o Netscape Communicator. Por uma razão inexplicável, os testes realizados com IE6 apresentavam resultados inesperados, como se IE6 continuasse a usar cookies, embora estes tivessem sido desativados. O servlet cycledevie é solicitado pela primeira vez:

Image

Agora utilizamos o link “Atualizar a página”:

Image

É possível observar duas coisas:

  • o ID da sessão mudou
  • o servlet detecta a sessão como uma nova sessão

O servidor Tomcat oferece uma solução para o problema dos usuários que desativam o uso de cookies em seus navegadores. Ele utiliza dois mecanismos para implementar o token mencionado no início deste parágrafo: os cookies e a reescrita do URL. Se o cookie de sessão não estiver disponível, ele tentará obter o token a partir do URL solicitado pelo cliente. Para isso, é necessário que a solicitação contenha o token. De modo geral, todos os links gerados em um documento HTML para o aplicativo web devem conter o token deste. Isso pode ser feito com o método encodeURL:

String [HttpResponse].encodeURL(String URL)
adiciona o token da sessão atual ao URL passado como parâmetro na forma URL;jsessionid=xxxx

Modificamos nossa aplicação da seguinte maneira:

  • no servlet cycledevie.java, os URL são codificados:
             // passamos o controle para a página de erro
            getServletContext().getRequestDispatcher(response.encodeURL(urlErreur)).forward(request,response);
....
             // passamos o controle para a URL urlSessionInvalide
            getServletContext().getRequestDispatcher(response.encodeURL(urlSessionInvalide)).forward(request,response);
....
         // redireciona para a URL urlSessionInvalide
        getServletContext().getRequestDispatcher(response.encodeURL(urlSessionValide)).forward(request,response);
  • na página valide.jsp, os URL estão codificados:
<%
     // jspService
   // aqui estamos no caso em que é preciso descrever a sessão atual
  String etat= session.isNew() ? "Nouvelle session" : "Ancienne session";
   // codificação URL ciclo de vida
  String URLcycledevie=response.encodeURL("/sessions/cycledevie");  
%>
............
      <br><a href="<%= URLcycledevie %>?action=invalider">Invalider la session</a>
      <br><a href="<%= URLcycledevie %>">Recharger la page</a>
  • na página invalide.jsp, os URL estão codificados:
<%
     // jspservice — invalida-se a sessão atual
  session.invalidate();
   // codificação URL ciclo de vida
  String URLcycledevie=response.encodeURL("/sessions/cycledevie");
%>  
..........
    <a href="<%= URLcycledevie %>">Créer une nouvelle session</a>

Agora estamos prontos para os testes. Estamos usando o Netscape 4.5 e os cookies foram desativados. Acessamos pela primeira vez o servlet cycledevie:

Image

e recarregamos a página usando o link “Recarregar a página”:

Image

Podemos observar que:

  • a sessão não mudou (ainda é ID)
  • o URL do servlet cycledevie contém, de fato, o token, conforme mostra o campo Adresse acima
  • o servidor Tomcat, portanto, recupera o token de sessão no URL solicitado (desde que o desenvolvedor tenha se certificado de codificá-lo).

4.4. Exemplo 2

Apresentamos agora um exemplo que mostra como armazenar informações na sessão de um cliente. Aqui, a única informação será um contador que será incrementado sempre que o usuário chamar o URL do servlet. Quando este é chamado pela primeira vez, temos a seguinte página:

Image

Se clicarmos no link “Atualizar a página” acima, obtemos a seguinte nova página:

Image

O aplicativo possui três componentes:

  • um servlet que processa a solicitação do cliente
  • uma página JSP que exibe o valor do contador
  • uma página JSP que exibe um possível erro

Esses três componentes estão instalados na aplicação web “sessions” já utilizada. O arquivo web.xml dessa aplicação foi modificado para configurar os novos servlets:

<?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>compteur</servlet-name>
    <servlet-class>compteur</servlet-class>
    <init-param>
          <param-name>urlAffichageCompteur</param-name>
        <param-value>/compteur.jsp</param-value>
    </init-param>
    <init-param>
          <param-name>urlErreur</param-name>
        <param-value>/erreurcompteur.jsp</param-value>
    </init-param>
  </servlet>
...
  <servlet-mapping>
      <servlet-name>compteur</servlet-name>
    <url-pattern>/compteur</url-pattern>
  </servlet-mapping>
</web-app>
  • A servlet se chama contador (servlet-name) e está vinculada ao arquivo de classe compteur.class (servlet-class)
  • ela possui dois parâmetros de inicialização:
    • urlAffichageCompteur: URL da página JSP de exibição do contador
    • urlErreur: URL da página JSP de exibição de um possível erro
  • e um alias /contador que faz com que ele seja chamado por meio do URL http://localhost:8080/sessions/compteur

O servlet compteur.java é o seguinte:

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

public class compteur extends HttpServlet{

     // variáveis de instância
    String msgErreur=null;
    String urlAffichageCompteur=null;
    String urlErreur=null;

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

        // a inicialização ocorreu corretamente?
        if(msgErreur!=null){
             // passamos o controle para a página de erro
            getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
        }

         // recuperamos a sessão atual
        HttpSession session=request.getSession();
         // e o contador
        String compteur=(String)session.getAttribute("compteur");
        if(compteur==null) compteur="0";
         // incremento do contador
        try{
            compteur=""+(Integer.parseInt(compteur)+1);
        }catch(Exception ex){}
         // armazenamento do contador na sessão
        session.setAttribute("compteur",compteur);
         // e na consulta
        request.setAttribute("compteur",compteur);

         // passamos o controle para a URL de exibição do contador
        getServletContext().getRequestDispatcher(urlAffichageCompteur).forward(request,response);
    }

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

     //-------- INIT
    public void init(){
         // recuperam-se os parâmetros de inicialização
        ServletConfig config=getServletConfig();
        urlAffichageCompteur=config.getInitParameter("urlAffichageCompteur");
        urlErreur=config.getInitParameter("urlErreur");

         // parâmetros corretos?
        if(urlAffichageCompteur==null){
            msgErreur="Configuration incorrecte";
        }
    }
}

Este servlet tem a mesma estrutura dos servlets já apresentados. Destaca-se apenas o gerenciamento do contador:

  • a sessão é recuperada por meio de request.getSession()
  • o contador é recuperado nessa sessão por meio de session.getAttribute("contador")
  • se for recuperado um valor null, significa que a sessão acaba de começar. O contador é então zerado.
  • o contador é incrementado, colocado de volta na sessão (session.setAttribute("contador",contador)) e inserido na solicitação que será enviada ao servlet de exibição (request.setAttribute("contador",contador)).

A página de exibição compteur.jsp é a seguinte:

<%
     // jspService
   // recuperando o contador
  String compteur= (String) request.getAttribute("compteur");
  if(compteur==null) compteur="inconnu";
%>
<!-- início da página HTML -->
<html>
  <head>
      <title>Comptage au fil d'une session</title>
  </head>
  <body>
      <h3>Comptage au fil d'une session (nécessite l'activation des cookies)</h3>
      <hr>
    compteur = (<%= compteur %>)
    <br><a href="/sessions/compteur">Recharger la page</a>
  </body>
</html>

A página acima se limita a recuperar o atributo compteur (request.getAttribute("contador")) que lhe foi passado pelo servlet principal e a exibi-lo.

A página de erro erreurcompteur.jsp é a seguinte:

<%
     // jspService
   // ocorreu um erro
  String msgErreur= request.getAttribute("msgErreur");
  if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- início da página HTML -->
<html>
  <head>
      <title>Comptage au fil d'une session</title>
  </head>
  <body>
      <h3>Comptage au fil d'une session (nécessite l'activation des cookies)</h3>
      <hr>
    Application indisponible(<%= msgErreur %>)
  </body>
</html>

4.5. Exemplo 3

Propomos escrever um aplicativo Java que funcionaria como cliente do aplicativo compteur anterior. Ele o chamaria N vezes seguidas, sendo que N seria passado como parâmetro. Nosso objetivo é mostrar um cliente web programado e como gerenciar cookies. Nosso ponto de partida será um cliente web genérico apresentado no material didático sobre Java do mesmo autor. Ele é chamado da seguinte forma:

clientweb URL GET/HEAD

  • URL: URL solicitada
  • GET/HEAD: GET para solicitar o código HTML da página, HEAD para limitar-se apenas aos cabeçalhos HTTP

Aqui está um exemplo com o URL e o http://localhost:8080/sessions/compteur:


E:\data\serge\JAVA\SOCKETS\client web>java clientweb http://localhost:8080/sessions/contador GET

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 14:21:18 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=B8A9076E552945009215C34A97A0EC5D;Path=/sessions


<!-- início da página HTML -->
<html>
  <head>
        <title>Comptage au fil d'une session</title>
  </head>
  <body>
        <h3>Comptage au fil d'une session (nécessite l'activation des cookies)</h3>
        <hr>
    compteur = (1)
    <br><a href="/sessions/compteur">Recharger la page</a>
  </body>
</html>

O programa clientweb exibe tudo o que recebe do servidor. Acima, vemos o comando HTTP Set-cookie, com o qual o servidor envia um cookie ao seu cliente. Aqui, o cookie contém duas informações:

  • JSESSIONID, que é o token da sessão
  • Path, que define a URL à qual o cookie pertence. Path=/sessions indica ao navegador que ele deverá reenviar o cookie ao servidor sempre que solicitar uma URL que comece com /sessions. Na aplicação sessions, utilizamos diferentes servlets, entre os quais os servlets /sessions/cycledevie e /sessions/compteur. Se chamarmos o servlet /sessions/cycledevie, o navegador receberá um token J. Se, com esse mesmo navegador, em seguida for chamado o servlet /sessions/compteur, o navegador reenviará ao servidor o token J, pois este se aplica a todos os URL que começam com /sessions. No nosso exemplo, os servlets cycledevie e compteur não precisam compartilhar o mesmo token de sessão. Portanto, eles não deveriam ter sido colocados na mesma aplicação web. É importante lembrar: todos os servlets de uma mesma aplicação compartilham o mesmo token de sessão.
  • Um cookie também pode definir um prazo de validade. Aqui, essa informação está ausente. O cookie será, portanto, excluído ao fechar o navegador. Um cookie pode ter um prazo de validade de N dias, por exemplo. Enquanto estiver válido, o navegador o reenviará sempre que uma das páginas URL de seu domínio (Path) for acessada. Tomemos como exemplo um site de vendas online de CD. Ele pode acompanhar a navegação do cliente em seu catálogo e determinar, aos poucos, suas preferências: música clássica, por exemplo. Essas preferências podem ser armazenadas em um cookie com validade de 3 meses. Se esse mesmo cliente retornar ao site após um mês, o navegador reenviará o cookie para o servidor. Com base nas informações contidas no cookie, o servidor poderá então adaptar as páginas geradas às preferências do cliente.

Segue-se o código do cliente web. Ele servirá posteriormente como ponto de partida para outro cliente.

// pacotes importados
import java.io.*;
import java.net.*;

public class clientweb{

    // solicita um URL
     // exibe o conteúdo dela na tela

    public static void main(String[] args){
        // sintaxe
        final String syntaxe="pg URI GET/HEAD";

        // número de argumentos
        if(args.length != 2)
            erreur(syntaxe,1);

         // observa-se o URI solicitado
        String URLString=args[0];
        String commande=args[1].toUpperCase();

        // verificação da validade do URI
        URL url=null;
        try{
            url=new URL(URLString);
        }catch (Exception ex){
             // URI incorreto
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
        }//exceção
         // verificação do pedido
        if(! commande.equals("GET") && ! commande.equals("HEAD")){
            // pedido incorreto
            erreur("Le second paramètre doit être GET ou HEAD",3);
        }

         // extraindo as informações úteis do URL
    String path=url.getPath();
    if(path.equals("")) path="/";
    String query=url.getQuery();
    if(query!=null) query="?"+query; else query="";
    String host=url.getHost();
    int port=url.getPort();
    if(port==-1) port=url.getDefaultPort();

         // é possível prosseguir
        Socket  client=null;                        // o cliente
        BufferedReader IN=null;                    // o fluxo de leitura do cliente
        PrintWriter OUT=null;                        // o fluxo de gravação do cliente
        String réponse=null;                        // resposta do servidor
        try{
             // conectamos-nos ao servidor
            client=new Socket(host,port);

            // são criados os fluxos de entrada e saída do cliente TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // solicitação do URL — envio dos cabeçalhos HTTP
            OUT.println(commande + " " + path + query + " HTTP/1.1");   
            OUT.println("Host: " + host + ":" + port);
            OUT.println("Connection: close");
            OUT.println();
             // lê-se a resposta
            while((réponse=IN.readLine())!=null){
                 // processa-se a resposta
                System.out.println(réponse);
            }//enquanto
             // concluído
            client.close();
        } catch(Exception e){
            // tratamos a exceção
            erreur(e.getMessage(),4);
        }//catch
    }//main

     // exibição de erros
    public static void erreur(String msg, int exitCode){
         // exibição do erro
        System.err.println(msg);
         // encerramento com erro
        System.exit(exitCode);
    }//erro
}//classe

Agora criamos o programa clientCompteur, chamado da seguinte forma:

clientCompteur URL N [JSESSIONID]

  • URL: URL do servlet contador
  • N: número de chamadas a serem feitas a essa servlet
  • JSESSIONID: parâmetro opcional — token de uma sessão

O objetivo do programa é chamar o servlet contador N vezes, gerenciando o cookie de sessão e exibindo, a cada vez, o valor do contador retornado pelo servidor. Ao final das N chamadas, o valor do contador deve ser N. Aqui está um primeiro exemplo de execução:


E:\data\serge\Servlets\sessions\jb7>java.bat clientCompteur http://localhost:8080/sessions/contador 3
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:00 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A;Path=/sessions
cookie trouvÚ : 92DB3808CE8FCB47D47D997C8B52294A

compteur : 1

--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:00 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)

compteur : 2

--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:00 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)

compteur : 3

O programa exibe:

  • os cabeçalhos HTTP que ele envia ao servidor na forma -->
  • os cabeçalhos HTTP que recebe
  • o valor do contador após cada chamada

Percebe-se que, na primeira chamada:

  • o cliente não envia nenhum cookie
  • o servidor envia um

Nas chamadas seguintes:

  • o cliente reenvia sistematicamente o cookie que recebeu do servidor na primeira chamada. É isso que permitirá ao servidor reconhecê-lo e incrementar seu contador.
  • o servidor, por sua vez, não reenvia mais o cookie

Reexecutamos o programa anterior, passando o token acima como terceiro parâmetro:


E:\data\serge\Servlets\sessions\jb7>java.bat clientCompteur http://localhost:8080/sessions/contador 3 92DB3808CE8FCB47D47D997C8B52294A

--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:25 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)

compteur : 4

--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:25 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)

compteur : 5

--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:25 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)

compteur : 6

Vemos aqui que, logo na primeira chamada do cliente, o servidor recebe um cookie de sessão válido. É importante saber que, no Tomcat, o tempo máximo de inatividade de uma sessão é, por padrão, de 20 minutos (na verdade, isso é configurável). Se a segunda chamada do programa enviar rapidamente o cookie recebido na primeira chamada, para o servidor trata-se da mesma sessão. Isso aponta para uma possível falha de segurança. Se eu conseguir interceptar um token de sessão na rede, poderei me passar por quem iniciou essa sessão. No nosso exemplo, a primeira chamada representa quem inicia a sessão (talvez com um login e senha que lhe darão o direito de obter um token) e a segunda chamada representa quem “hackeou” o token de sessão da primeira chamada. Se a operação em andamento for uma transação bancária, isso pode se tornar muito problemático...

O código do cliente é o seguinte:

// pacotes importados
import java.io.*;
import java.net.*;
import java.util.regex.*;

public class clientCompteur{

     // solicita um URL
     // exibe o conteúdo dela na tela

    public static void main(String[] args){
        // sintaxe
        final String syntaxe="pg URL-COMPTEUR N [JSESSIONID]";

         // número de argumentos
        if(args.length !=2 && args.length != 3)
            erreur(syntaxe,1);

         // observa-se o URL solicitado
        String URLString=args[0];

        // verificação da validade do URL
        URL url=null;
        try{
            url=new URL(URLString);
        }catch (Exception ex){
             // URI incorreto
            erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
        }//exceção
         // verificação do número de chamadas N
        int N=0;
        try{
            N=Integer.parseInt(args[1]);
            if(N<=0) throw new Exception();
        }catch(Exception ex){
             // argumento N incorreto
            erreur("Le nombre d'appels N doit être un entier >0",3);
        }
         // o token JSESSIONID foi passado como parâmetro?
        String JSESSIONID="";
        if (args.length==3) JSESSIONID=args[2];

        // extraindo as informações úteis do URL
        String path=url.getPath();
        if(path.equals("")) path="/";
        String query=url.getQuery();
        if(query!=null) query="?"+query; else query="";
        String host=url.getHost();
        int port=url.getPort();
        if(port==-1) port=url.getDefaultPort();

         // é possível prosseguir
        Socket  client=null;                        // o cliente
        BufferedReader IN=null;                    // o fluxo de leitura do cliente
        PrintWriter OUT=null;                        // o fluxo de gravação do cliente
        String réponse=null;                        // resposta do servidor
         // o modelo procurado nos cabeçalhos HTTP
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // o padrão procurado no código HTML
        Pattern modèleCompteur=Pattern.compile("compteur = .*?(\\d+)");
         // o resultado da comparação com o modelo
        Matcher résultat=null;
         // um valor booleano que indica o resultado da busca pelo contador
        boolean compteurTrouvé;

        try{
             // são feitas as N chamadas ao servidor
            for(int i=0;i<N;i++){
                // conectamos-nos ao servidor
                client=new Socket(host,port);

                // são criados os fluxos de entrada e saída do cliente TCP
                IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
                OUT=new PrintWriter(client.getOutputStream(),true);

                // solicita-se o URL — envio dos cabeçalhos HTTP
                envoie(OUT,"GET " + path + query + " HTTP/1.1");
                envoie(OUT,"Host: " + host + ":" + port);
                if(! JSESSIONID.equals("")){
                    envoie(OUT,"Cookie: JSESSIONID="+JSESSIONID);
                }
                envoie(OUT,"Connection: close");
                envoie(OUT,"");

                 // lê-se a resposta até o final dos cabeçalhos, procurando por um eventual cookie
                while((réponse=IN.readLine())!=null){
                     // acompanhamento da resposta
                    System.out.println(réponse);
                     // linha vazia?
                    if(réponse.equals("")) break;
                     // linha HTTP não vazia
                     // se não houver o token da sessão, procura-se por ele
                    if (JSESSIONID.equals("")){
                        // comparamos a linha HTTP com o modelo do cookie
                        résultat=modèleCookie.matcher(réponse);
                        if(résultat.find()){
                            // encontramos o cookie
                            JSESSIONID=résultat.group(1);
                        }
                    }
                }//enquanto

                 // concluída a análise dos cabeçalhos HTTP — passamos para o código HTML
                compteurTrouvé=false;
                while((réponse=IN.readLine())!=null){
                     // a linha atual contém o contador?
                    if (! compteurTrouvé){
                        résultat=modèleCompteur.matcher(réponse);
                        if(résultat.find()){
                            // encontramos o contador — vamos exibi-lo
                            System.out.println("compteur : " + résultat.group(1));
                            compteurTrouvé=true;
                        }
                    }
                }//while
                 // terminou
                client.close();
            }//for
        } catch(Exception e){
            // tratamos a exceção
            erreur(e.getMessage(),4);
        }//catch
    }//main

     // exibição de erros
    public static void erreur(String msg, int exitCode){
         // exibição de erros
        System.err.println(msg);
         // encerramento com erro
        System.exit(exitCode);
    }//erro

     // rastreamento das trocas cliente-servidor
    public static void envoie(PrintWriter OUT,String msg){
        // envio de mensagem ao servidor
        OUT.println(msg);
         // monitoramento da tela
        System.out.println("--> "+msg);
    }//erro
}//classe

Vamos analisar os pontos importantes desse programa:

  • é preciso realizar N trocas cliente-servidor. É por isso que elas estão em um loop
            for(int i=0;i<N;i++){
  • a cada troca, o cliente abre uma conexão TCP-IP com o servidor. Uma vez estabelecida a conexão, ele envia ao servidor os cabeçalhos HTTP de sua solicitação:
                 // solicitação do URL - envio dos cabeçalhos HTTP
                envoie(OUT,"GET " + path + query + " HTTP/1.1");
                envoie(OUT,"Host: " + host + ":" + port);
                if(! JSESSIONID.equals("")){
                    envoie(OUT,"Cookie: JSESSIONID="+JSESSIONID);
                }
                envoie(OUT,"Connection: close");
                envoie(OUT,"");

Se o token JSESSIONID estiver disponível, ele é enviado na forma de um cookie; caso contrário, não é enviado.

  • Depois de enviar sua solicitação, o cliente aguarda a resposta do servidor. Ele começa analisando os cabeçalhos HTTP dessa resposta em busca de um possível cookie. Para encontrá-lo, ele compara as linhas recebidas com a expressão regular do cookie:
         // o modelo procurado nos cabeçalhos HTTP
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
...........................
                 // a resposta é lida até o final dos cabeçalhos, procurando por um eventual cookie
                while((réponse=IN.readLine())!=null){
                     // acompanhamento da resposta
                    System.out.println(réponse);
                     // linha vazia?
                    if(réponse.equals("")) break;
                     // linha HTTP não vazia
                     // se não houver o token da sessão, procura-se por ele
                    if (JSESSIONID.equals("")){
                        // comparamos a linha HTTP com o modelo do cookie
                        résultat=modèleCookie.matcher(réponse);
                        if(résultat.find()){
                            // encontramos o cookie
                            JSESSIONID=résultat.group(1);
                        }
                    }
                }//enquanto
  • Quando o token for encontrado pela primeira vez, ele não será mais procurado nas chamadas subsequentes ao servidor. Após o processamento dos cabeçalhos HTTP da resposta, passa-se para o código HTML dessa mesma resposta. Nesta resposta, procura-se a linha que fornece o valor do contador. Essa busca também é feita com uma expressão regular:
         // o modelo do contador procurado no código HTML
        Pattern modèleCompteur=Pattern.compile("compteur = .*?(\\d+)");
..................................
                 // concluída a análise dos cabeçalhos HTTP — passando para o código HTML
                compteurTrouvé=false;
                while((réponse=IN.readLine())!=null){
                     // a linha atual contém o contador?
                    if (! compteurTrouvé){
                        résultat=modèleCompteur.matcher(réponse);
                        if(résultat.find()){
                            // encontramos o contador — vamos exibi-lo
                            System.out.println("compteur : " + résultat.group(1));
                            compteurTrouvé=true;
                        }
                    }
                }//while

4.6. Exemplo 4

No exemplo anterior, o cliente web retorna o token na forma de um cookie. Vimos que ele também poderia retorná-lo dentro da própria solicitação URL, na forma URL;jsessionid=xxx. Vamos verificar isso. O programa clientCompteur.java é transformado em clientCompteur2.java e modificado da seguinte forma:

....
                 // solicitamos o URL  envio dos cabeçalhos HTTP
                if(JSESSIONID.equals(""))
                    envoie(OUT,"GET " + path + query + " HTTP/1.1");
                else envoie(OUT,"GET " + path + query + ";jsessionid=" + JSESSIONID + " HTTP/1.1");
                envoie(OUT,"Host: " + host + ":" + port);
                envoie(OUT,"Connection: close");
                envoie(OUT,"");
....

O cliente solicita, portanto, o URL do contador por meio de GET URL;jsessionid=xx HTTP/1.1 e não envia mais nenhum cookie. Essa é a única alteração. Aqui estão os resultados de uma primeira chamada:


E:\data\serge\Servlets\sessions\jb7>java.bat clientCompteur2 http://localhost:8080/sessions/contador 2

--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:49:30 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=48A6DBA8357D808EC012AAF3A2AFDA63;Path=/sessions
cookie trouvÚ : 48A6DBA8357D808EC012AAF3A2AFDA63

compteur : 1

--> GET /sessions/compteur;jsessionid=48A6DBA8357D808EC012AAF3A2AFDA63 HTTP/1.1
--> Host: localhost:8080
--> Connection: close
-->

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:49:30 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)

compteur : 2

Na primeira chamada, o cliente solicita o URL sem token de sessão. O servidor responde enviando-lhe o token. O cliente então reenvia a mesma solicitação URL, anexando o token recebido a ela. Percebe-se que o contador foi incrementado, o que comprova que o servidor reconheceu corretamente que se tratava da mesma sessão.

4.7. Exemplo 5

Este exemplo mostra um aplicativo composto por três páginas, que chamaremos de page0, page1 e page2. O usuário deve acessá-las nesta ordem:

  • a página0 é um formulário que solicita uma informação: um nome
  • a página1 é um formulário exibido em resposta ao envio do formulário da página0. Ela solicita uma segunda informação: uma idade
  • a página2 é um documento HTML que exibe o nome obtido pela página0 e a idade obtida pela página1.

Há aqui três trocas cliente-servidor:

  • na primeira troca, o formulário da página0 é solicitado pelo cliente e enviado pelo servidor
  • na segunda troca, o formulário página1 é solicitado pelo cliente e enviado pelo servidor. O cliente envia o nome ao servidor.
  • na terceira troca, o documento página3 é solicitado pelo cliente e enviado pelo servidor. O cliente envia a idade ao servidor. O documento page3 deve exibir o nome e a idade. O nome foi obtido pelo servidor na segunda troca e, desde então, foi “esquecido”. Utiliza-se uma sessão para registrar o nome na troca 2, de modo que ele esteja disponível na troca 3.

A página page0 obtida na primeira troca é a seguinte:

Image

Preenche-se o campo do nome:

Image

Utiliza-se o botão Suite e obtém-se, então, a seguinte página page1:

Image

Preencha o campo de idade:

Image

Utiliza-se o botão Suite e obtém-se então a seguinte página page2:

Image

Ao enviar a página page0 ao servidor, este pode devolvê-la com um código de erro se o nome estiver vazio:

Image

Ao enviar a página page1 ao servidor, este pode devolvê-la com um código de erro se a idade for inválida:

Image

O aplicativo é composto por um servlet e quatro páginas JSP:

page0.jsp
exibe a página0
page1.jsp
exibe a página 1
page2.jsp
exibe a página 2
erreur.jsp
exibe uma página de erro

O aplicativo web se chama suitedepages e está configurado da seguinte forma no arquivo server.xml do Tomcat:

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

O arquivo de configuração web.xml do aplicativo suitedepages é 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>main</servlet-name>
    <servlet-class>main</servlet-class>
    <init-param>
          <param-name>urlPage0</param-name>
        <param-value>/page0.jsp</param-value>
    </init-param>
    <init-param>
          <param-name>urlPage1</param-name>
        <param-value>/page1.jsp</param-value>
    </init-param>
    <init-param>
          <param-name>urlPage2</param-name>
        <param-value>/page2.jsp</param-value>
    </init-param>
    <init-param>
          <param-name>urlErreur</param-name>
        <param-value>/erreur.jsp</param-value>
    </init-param>    
  </servlet>
  <servlet-mapping>
      <servlet-name>main</servlet-name>
    <url-pattern>/main</url-pattern>
  </servlet-mapping>
</web-app>

O servlet principal se chama main e, graças ao seu alias (servlet-mapping), pode ser acessado por meio do URL http://localhost:8080/suitedepages/main. Ela possui quatro parâmetros de inicialização, que correspondem aos URL das quatro páginas JSP utilizadas para as diferentes exibições. O código do servlet “main” é o seguinte:

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

public class main extends HttpServlet{

    // variáveis de instância
    String msgErreur=null;
    String urlPage0=null;
    String urlPage1=null;
    String urlPage2=null;
    String urlErreur=null;

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

        // a inicialização ocorreu corretamente?
        if(msgErreur!=null){
             // passando o controle para a página de erro
            getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
        }
         // recuperando o parâmetro de etapa
        String étape=request.getParameter("etape");
         // recuperando a sessão atual
        HttpSession session=request.getSession();
         // processando a etapa atual
        if(étape==null) étape0(request,response,session);
        if(étape.equals("1")) étape1(request,response,session);
        if(étape.equals("2")) étape2(request,response,session);
         // outros casos são inválidos
        étape0(request,response,session);
    }

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

     //-------- INIT
    public void init(){
         // recuperando os parâmetros de inicialização
        ServletConfig config=getServletConfig();
        urlPage0=config.getInitParameter("urlPage0");
        urlPage1=config.getInitParameter("urlPage1");
        urlPage2=config.getInitParameter("urlPage2");
        urlErreur=config.getInitParameter("urlErreur");

         // parâmetros corretos?
        if(urlPage0==null || urlPage1==null || urlPage2==null){
            msgErreur="Configuration incorrecte";
        }
    }

     //-------- etapa 0
    public void étape0(HttpServletRequest request, HttpServletResponse response, HttpSession session)
            throws IOException, ServletException{
        // definindo alguns atributos
        request.setAttribute("nom","");
         // exibindo a página 0
        request.getRequestDispatcher(urlPage0).forward(request,response);
    }

     //-------- etapa 1
    public void étape1(HttpServletRequest request, HttpServletResponse response, HttpSession session)
            throws IOException, ServletException{
         // recuperando o nome da consulta
        String nom=request.getParameter("nom");
        // nome definido?
        if(nom==null) étape0(request,response,session);
         // removemos eventuais espaços do nome
        nom=nom.trim();
         // colocamos em um atributo da consulta
        request.setAttribute("nom",nom);
         // nome vazio?
        if(nom.equals("")){
             // isso é um erro
            ArrayList erreurs=new ArrayList();
            erreurs.add("Nous n'avez pas indiqué de nom");
             // colocamos os erros na consulta
            request.setAttribute("erreurs",erreurs);
             // retorno à página 0
            étape0(request,response,session);
        }
         // nome válido  é armazenado na sessão atual
        session.setAttribute("nom",nom);
         // definimos o atributo idade na consulta
        request.setAttribute("age","");
         // exibe-se a página 1
        request.getRequestDispatcher(urlPage1).forward(request,response);
    }

     //-------- etapa 2
    public void étape2(HttpServletRequest request, HttpServletResponse response, HttpSession session)
            throws IOException, ServletException{
         // recuperamos o nome da sessão
        String nom=(String)session.getAttribute("nom");
         // nome definido?
        if(nom==null) étape0(request,response,session);
         // colocando-o em um atributo da consulta
        request.setAttribute("nom",nom);
         // recuperamos a idade na consulta
        String age=request.getParameter("age");
        // idade definida?
        if(age==null){
            // retorno à página 1
            request.setAttribute("age","");
            request.getRequestDispatcher(urlPage1).forward(request,response);
        }
         // a idade é armazenada na consulta
        age=age.trim();
        request.setAttribute("age",age);
        // idade válida?
        if(! Pattern.matches("^\\s*\\d+\\s*$",age)){
            // é um erro
            ArrayList erreurs=new ArrayList();
            erreurs.add("Age invalide");
            // colocamos os erros na consulta
            request.setAttribute("erreurs",erreurs);
             // retorno à página 1
            request.getRequestDispatcher(urlPage1).forward(request,response);
        }
         // idade válida  exibe-se a página 2
        request.getRequestDispatcher(urlPage2).forward(request,response);
    }
}
  • O método init recupera os quatro parâmetros de inicialização e exibe uma mensagem de erro caso algum deles esteja faltando
  • Vimos que a solicitação compreendia três trocas de dados. Para saber em que ponto estamos nessas trocas, os formulários page0 e page1 possuem uma variável oculta etape que tem o valor 1 (page0) ou 2 (page1). Esse número pode ser interpretado como o número da próxima página a ser exibida. No método doGet, esse parâmetro é recuperado da solicitação e, de acordo com seu valor, o processamento é delegado a três outros métodos:
    • étape0 processa a solicitação inicial e chama page0
    • étape1 processa o formulário de page0 e envia page1 ou, novamente, page0 caso tenha ocorrido um erro
    • A etapa 2 processa o formulário page1 e envia o page2 ou, novamente, o page1, caso tenha ocorrido um erro
  • etapa 0
    • exibe o page0 com um nome vazio
  • etapa 1
    • recupera o parâmetro nom do formulário page0.
    • verifica se o nome existe (não é nulo). Caso contrário, exibe novamente page0 como se fosse a primeira chamada.
    • Verifica se o nome não está vazio. Caso contrário, exibe novamente o page0 com uma mensagem de erro.
    • armazena o nome na sessão atual e exibe page1 se o nome for válido.
  • etapa 2
    • recupera o parâmetro nom na sessão atual.
    • Verifica se o nome existe (não é nulo). Caso contrário, exibe novamente page0 como se fosse a primeira chamada.
    • recupera o parâmetro age na solicitação atual enviada por page1.
    • verifica se a idade é válida. Caso contrário, exibe novamente page1 com uma mensagem de erro.
    • armazena o nome e a idade como atributos da solicitação e exibe page2 se o nome e a idade forem válidos.

A página page0.jsp é a seguinte:

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

<% // page0.jsp
     // recuperam-se os atributos da consulta
  String nom=(String)request.getAttribute("nom");
  ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
   // atributos válidos?
  if(nom==null){
       // retorno ao servlet principal
    request.getRequestDispatcher("/main").forward(request,response);
  }
%>  

<html>
  <head>
    <title>page 0</title>
  </head>
  <body>
    <h3>Page 0/2</h3>
    <form name="frmNom" method="POST" action="/suitedepages/main">
        <input type="hidden" name="etape" value="1">
      <table>
        <tr>
          <td>Votre nom</td>
          <td><input type="text" name="nom" value="<%= nom %>"></td>
        </tr>
      </table>
      <input type="submit" value="Suite">
    </form>
    <% // erros?
      if (erreurs!=null){
    %>
      <hr>
      <font color="red">
        Les erreurs suivantes se sont produites
        <ul>
        <% for(int i=0;i<erreurs.size();i++){ %>
            <li><%= erreurs.get(i) %>
        <% }//for %>
        </ul>
     <% }//if %>
  </body>
</html>
  • A página page0.jsp pode ser chamada pelo servlet principal em dois casos:
    • durante a solicitação inicial
    • após o processamento do formulário de page0, quando ocorre um erro
  • o parâmetro nom a ser exibido é fornecido pelo servlet principal, assim como a eventual lista de erros. O servlet page0.jsp começa, portanto, recuperando essas duas informações.
  • O formulário é “enviado” para o servlet principal com o campo oculto (hidden) etape, que indica em que etapa do aplicativo estamos.

A página page1.jsp é a seguinte:

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

<% // page1.jsp
     // recuperando os atributos da solicitação
  String nom=(String)request.getAttribute("nom");
  String age=(String)request.getAttribute("age");
  ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
  // atributos válidos?
  if(nom==null || age==null){
      // retorno ao servlet principal
    request.getRequestDispatcher("/main").forward(request,response);
  }
%>  

<html>
  <head>
    <title>page 1</title>
  </head>
  <body>
    <h3>Page 1/2</h3>
    <form name="frmAge" method="POST" action="/suitedepages/main">
        <input type="hidden" name="etape" value="2">    
      <table>
        <tr>
          <td>Nom</td>
          <td><font color="green"><%= nom %></font></td>
        </tr>
        <tr>
          <td>Votre âge</td>
          <td><input type="text" name="age" size="3" value="<%= age %>"></td>
        </tr>
      </table>
      <input type="submit" value="Suite">
    </form>
    <% // erros?
      if (erreurs!=null){
    %>
      <hr>
      <font color="red">
        Les erreurs suivantes se sont produites
        <ul>
        <% for(int i=0;i<erreurs.size();i++){ %>
            <li><%= erreurs.get(i) %>
        <% }//for %>
        </ul>
     <% }//if %>
  </body>
</html>

A página page1.jsp tem uma estrutura semelhante à da página page0.jsp, com a diferença de que agora recebe dois atributos do servlet principal: nom e age. Por fim, a página page2.jsp é a seguinte:

<% 
     // page2.jsp
     // recuperando os atributos da solicitação
  String nom=(String)request.getAttribute("nom");
  String age=(String)request.getAttribute("age");
  // atributos válidos?
  if(nom==null || age==null){
      // retorno ao servlet principal
    request.getRequestDispatcher("/main").forward(request,response);
  }
%>  


<html>
  <head>
    <title>page 2</title>
  </head>
  <body>
    <h3>Page 2/2</h3>
      <table>
        <tr>
          <td>Nom</td>
          <td><font color="green"><%= nom %></font></td>
        </tr>
        <tr>
          <td>Votre âge</td>
          <td><font color="green"><%= age %></font></td>
        </tr>
      </table>
  </body>
</html>

A página page2.jsp também recebe os atributos nom e age do servlet principal. Ela se limita a exibi-los. Por fim, a página erreur.jsp, responsável por exibir um erro em caso de inicialização incorreta do servlet, é a seguinte:

<%
     // jspService
   // ocorreu um erro
  String msgErreur= request.getAttribute("msgErreur");
  if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- início da página HTML -->
<html>
  <head>
      <title>Suite de pages</title>
  </head>
  <body>
      <h3>Suite de pages</h3>
      <hr>
    Application indisponible(<%= msgErreur %>)
  </body>
</html>

Ela exibe o atributo msgErreur que lhe foi passado pelo servlet principal.

Concluindo, pode-se observar que, ao longo das três etapas da aplicação, é sempre o servlet principal que é consultado primeiro pelo navegador. Mas não é ele que gera a resposta a ser exibida, e sim uma das quatro páginas JSP. O usuário não percebe isso, pois o navegador continua exibindo no campo “Endereço” a URL solicitada inicialmente, ou seja, a da servlet principal.