4. Acompanhamento da sessão
4.1. O problema
Uma aplicação web pode consistir em várias trocas de formulários entre o servidor e o cliente. O funcionamento é então o seguinte:
- etapa 1
- o cliente C1 estabelece uma ligação com o servidor e efetua o seu pedido inicial.
- O servidor envia o formulário F1 ao cliente C1 e encerra a ligação estabelecida na etapa 1.
- etapa 2
- O cliente C1 preenche-o e reenvia-o ao servidor. Para tal, o navegador estabelece uma nova ligação com o servidor.
- Este processa os dados do formulário 1, calcula as informações I1 a partir desses dados, envia um formulário F2 ao cliente C1 e encerra a ligação aberta na etapa 3.
- Etapa 3
- O ciclo das etapas 3 e 4 repete-se nas etapas 5 e 6. No final da etapa 6, o servidor terá recebido dois formulários F1 e F2 e, a partir destes, terá calculado as informações I1 e I2.
A questão que se coloca é: como é que o servidor consegue manter as informações I1 e I2 associadas ao cliente C1? A este problema chama-se «acompanhamento da sessão do cliente C1». Para compreender a sua origem, analisemos o esquema de uma aplicação de servidor TCP-IP que atende simultaneamente vários clientes:
![]() |
Numa aplicação cliente-servidor TCP-IP clássica:
- o cliente estabelece uma ligação com o servidor
- troca dados com o servidor através dessa ligação
- a ligação é encerrada por um dos dois parceiros
Os dois pontos importantes deste mecanismo são:
- é criada uma ligação única para cada um dos clientes
- esta ligação é utilizada durante todo o período de diálogo entre o servidor e o seu cliente
O que permite ao servidor saber, num determinado momento, com que cliente está a trabalhar é a ligação ou, por outras palavras, o «canal» que o liga ao seu cliente. Sendo este canal dedicado a um determinado cliente, tudo o que chega por esse canal provém 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 ligação com o servidor e faz o seu pedido
- o servidor responde e encerra a ligação
Se, no momento T1, um cliente C efetuar uma solicitação ao servidor, obtém uma ligação C1 que servirá para a única troca de solicitação-resposta. Se, no momento T2, esse mesmo cliente efetuar uma segunda solicitação ao servidor, obterá uma ligação C2 diferente da ligação C1. Para o servidor, não há, portanto, qualquer diferença entre esta segunda solicitação do utilizador C e a sua solicitação inicial: em ambos os casos, o servidor considera o cliente como um novo cliente. Para que exista uma ligação entre as diferentes ligações do cliente C ao servidor, é necessário que o cliente C seja «reconhecido» pelo servidor como um «cliente habitual» e que o servidor recupere as informações que possui sobre esse cliente habitual.
Imaginemos um sistema de gestão que funcionasse da seguinte forma:
- Existe uma única fila de espera
- Existem vários balcões. Assim, vários clientes podem ser atendidos simultaneamente. Quando um balcão fica livre, um cliente sai da fila de espera para ser atendido nesse balcão
- Se for a primeira vez que o cliente se apresenta, a pessoa no balcão entrega-lhe uma ficha com um número. O cliente só pode fazer uma pergunta. Quando obtém a resposta, deve sair do balcão e passar para o fim da fila de espera. O funcionário do balcão regista as informações desse cliente num processo com o seu número.
- Quando chega novamente a sua vez, o cliente pode ser atendido por um funcionário diferente do da vez anterior. Este pede-lhe a ficha e recupera o processo com o número da ficha. Mais uma vez, o cliente faz um pedido, obtém uma resposta e são adicionadas informações ao seu processo.
- E assim sucessivamente... Com o passar do tempo, o cliente terá a resposta a todas as suas solicitações. O acompanhamento entre as diferentes solicitações é feito através do ficha e do processo a ele associado.
O mecanismo de acompanhamento de sessão numa aplicação web cliente-servidor é análogo ao funcionamento anterior:
- na sua primeira solicitação, um cliente recebe um token do servidor web
- ele apresentará esse token em cada uma das suas solicitações subsequentes para se identificar
O token pode assumir diferentes formas:
- um campo oculto num formulário
- o cliente faz a sua primeira solicitação (o servidor reconhece-o pelo facto de o cliente não ter um token)
- o servidor envia a sua resposta (um formulário) e coloca o token num campo oculto do mesmo. Nesse momento, a ligação é encerrada (o cliente sai do servidor com o seu token). O servidor terá, eventualmente, associado informações a esse token.
- o cliente faz o seu segundo pedido, reenviando o formulário. O servidor recupera o token desse formulário. Pode então processar o segundo pedido do cliente, tendo acesso, graças ao token, às informações calculadas durante o primeiro pedido. São adicionadas novas informações ao ficheiro associado ao token, é enviada uma segunda resposta ao cliente e a ligação é encerrada pela segunda vez. O token foi novamente incluído no formulário da resposta para que o utilizador o possa apresentar na sua próxima solicitação.
- E assim sucessivamente...
A principal desvantagem desta técnica é que o token tem de ser inserido num formulário. Se a resposta do servidor não for um formulário, o método do campo oculto deixa de ser utilizável.
- A técnica do cookie
- o cliente faz a sua primeira solicitação (o servidor reconhece-o pelo facto de o cliente não ter um token)
- o servidor responde adicionando um cookie aos cabeçalhos HTTP da resposta. Isto é feito através do comando HTTP Set-Cookie:
Set-Cookie: param1=valor1;param2=valor2;....
onde parami são nomes de parâmetros e valeursi os respetivos valores. Entre os parâmetros, estará o token. Muitas vezes, apenas o token consta no cookie, sendo as restantes informações registadas pelo servidor na pasta associada ao token. O navegador que recebe o cookie irá armazená-lo num ficheiro no disco. Após a resposta do servidor, a ligação é encerrada (o cliente sai da janela com o seu token).
- (continuação)
- o cliente faz a sua segunda solicitação ao servidor. Sempre que é feita uma solicitação a um servidor, o navegador verifica, entre todos os cookies que possui, se existe algum proveniente do servidor solicitado. Se sim, envia-o ao servidor sempre sob a forma de um comando HTTP, o comando «Cookie», cuja sintaxe é análoga à do comando Set-Cookie utilizado pelo servidor:
Cookie: param1=valor1;param2=valor2;....
Entre os parâmetros enviados pelo navegador, o servidor irá identificar o token que lhe permite reconhecer o cliente e recuperar as informações que lhe estão associadas.
Esta é a forma mais utilizada de token. Apresenta uma desvantagem: um utilizador pode configurar o seu navegador para que não aceite cookies. Este tipo de utilizador não tem, então, acesso às aplicações web que utilizam cookies.
- Reescrita de URL
- o cliente faz o seu primeiro pedido (o servidor reconhece-o pelo facto de o cliente não ter um token)
- o servidor envia a sua resposta. Esta contém links que o utilizador deve utilizar para continuar a aplicação. No URL de cada um desses links, o servidor adiciona o token na forma URL;token=valor.
- Quando o utilizador clica num dos links para continuar a aplicação, o navegador envia o seu pedido ao servidor web, incluindo nos cabeçalhos HTTP o token solicitado na forma URL URL;token=valor. O servidor consegue então recuperar o token.
4.2. Java para o acompanhamento de sessões
Apresentamos agora os principais métodos úteis para o acompanhamento da sessão:
obtém o objeto Session ao qual pertence o pedido em curso. Se este ainda não fizesse parte de uma sessão, esta é criada. | |
identificador da sessão atual | |
data de criação da sessão atual (número de milissegundos decorridos desde 1 de janeiro de 1970, 0h). | |
data do último acesso à sessão pelo cliente | |
tempo máximo, em segundos, de inatividade de uma sessão. Após esse período, a sessão é invalidada. | |
define, em segundos, a duração máxima de inatividade de uma sessão. Após esse período, a sessão é invalidada. | |
verdadeiro se a sessão tiver acabado de ser criada | |
associa um valor a um parâmetro numa determinada sessão. É este mecanismo que permite memorizar informações que permanecerão disponíveis ao longo de toda a sessão. | |
remove parametre dos dados da sessão. | |
valor associado ao parâmetro paramètre da sessão. Retorna null caso este não exista. | |
lista, sob a forma de enumeração, de todos os atributos da sessão atual | |
encerra a sessão atual. Todas as informações associadas à mesma são eliminadas. |
4.3. Exemplo 1
Apresentamos um exemplo retirado do excelente livro «Programação com J2EE», publicado pela editora Wrox e distribuído pela Eyrolles. Este livro é uma fonte de informação de alto nível para os programadores de soluções Web em Java. A aplicação apresentada neste livro sob a forma de um único servlet Java foi aqui reproduzida sob a forma de um servlet principal que recorre a páginas JSP para apresentar as várias respostas possíveis ao cliente.
A aplicação chama-se «sessions» e está configurada da seguinte forma no ficheiro <tomcat>\conf\server.xml:
Na pasta docBase acima, encontram-se os seguintes elementos:

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

Acima, vemos o ficheiro web.xml de configuração da aplicação «sessions». Na pasta classes, encontra-se o ficheiro de classe do servlet:

O ficheiro 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>
A servlet principal chama-se cycledevie (servlet-name) e está associada ao ficheiro de classe cycledevie.class (servlet-class). Possui um alias /cycledevie (servlet-mapping) que permite chamá-la através do URL http://localhost:8080/sessions/cycledevie. Possui três parâmetros de inicialização:
URL da página que apresenta as características da sessão atual | |
URL da página apresentada após a invalidação da sessão atual | |
URL da página apresentada em caso de erro de inicialização do servlet principal «cycledevie» |
Os componentes da aplicação «sessions» são os seguintes:
servlet principal — analisa o pedido do cliente:
| |
| |
exibida quando o utilizador invalidou a sessão atual. Sugere então que se crie uma nova sessão. | |
exibida quando o servlet principal encontra erros durante a sua inicialização. |
A servlet principal cycledevie é a 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 decorreu corretamente?
if(msgErreur!=null){
// passamos o controlo para a página de erro
getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
}
// recuperamos a sessão atual
HttpSession session=request.getSession();
// analisamos a ação a realizar
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 controlo para o URL urlSessionInvalide
getServletContext().getRequestDispatcher(urlSessionInvalide).forward(request,response);
}
// outros casos
// passa o controlo para o 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(){
// recuperam-se 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";
}
}
}
É de salientar o seguinte:
- no seu método de inicialização, o servlet recupera os seus três parâmetros
- no processamento (doGet) de um pedido, a servlet:
- verifica, em primeiro lugar, se não ocorreu nenhum erro durante a inicialização. Caso tenha ocorrido algum erro, a servlet passa o controlo para a página erreur.jsp.
- verifica o valor do parâmetro action. Se este tiver o valor «invalider», a servlet passa o controlo para a página invalide.jsp; caso contrário, para a página valide.jsp.
A página JSP valide.jsp para a visualização das características da sessão atual:
<%@ page import="java.util.*" %>
<%
// jspService
// aqui estamos no caso em que é necessário descrever a sessão em curso
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>
Note-se que na linha
é utilizado um objeto de sessão que surge do nada. Na verdade, este objeto faz parte dos objetos implícitos disponibilizados às páginas JSP, tal como os objetos request, response, out, config (ServletConfig), context (ServletContext), que já foram mencionados. Os dois links da página remetem para o 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 utilizador pretende invalidar a sessão atual. O outro link permite recarregar a página. Para que o navegador não a recupere da cache, a diretiva HTML:
foi utilizada. Esta indica ao navegador para 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>
Esta página apresenta um link que aponta para o servlet cycledevie sem o parâmetro action. Este 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 é necessário descrever a sessão em curso
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>
A sua função é apresentar a mensagem de erro que lhe foi transmitida pelo servlet cycledevie. Vejamos agora alguns exemplos de execução. O servlet é solicitado pela primeira vez:

A página acima indica que estamos numa nova sessão. Utilizamos o link «Atualizar a página»:

O resultado anterior indica que continuamos na mesma sessão da página anterior (o mesmo ID). Repare-se que a hora do último acesso a esta sessão mudou. Agora, utilizemos o link «Anular a sessão»:

Repare-se no URL desta nova página, que difere do parâmetro action=invalider. Utilizemos o link «Criar uma nova sessão» para criar uma nova sessão:

Verifica-se que foi iniciada uma nova sessão. Nos exemplos anteriores, a sessão baseia-se no mecanismo dos cookies. Vamos agora desativar a utilização de cookies no nosso navegador e repetir os testes. Os exemplos seguintes 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 utilizar cookies, apesar de estes terem sido desativados. A servlet cycledevie é solicitada uma primeira vez:

Utilizamos agora o link «Atualizar a página»:

É possível observar duas coisas:
- o ID da sessão mudou
- o servlet deteta a sessão como uma nova sessão
O servidor Tomcat oferece uma solução para o problema dos utilizadores que desativam a utilização de cookies no seu navegador. Utiliza dois mecanismos para implementar o token de que falámos no início deste parágrafo: os cookies e a reescrita do URL. Se o cookie de sessão não estiver disponível, tentará obter o token a partir do URL solicitado pelo cliente. Para tal, é necessário que esta contenha o token. De um modo geral, todos os links gerados num documento HTML para a aplicação web devem conter o token da mesma. Isto pode ser feito com o método encodeURL:
adiciona o token da sessão atual ao URL passado como parâmetro na forma URL;jsessionid=xxxx |
Alteramos a nossa aplicação da seguinte forma:
- no servlet cycledevie.java, os URL são codificados:
// passamos o controlo para a página de erro
getServletContext().getRequestDispatcher(response.encodeURL(urlErreur)).forward(request,response);
....
// passamos o controlo para o URL urlSessionInvalide
getServletContext().getRequestDispatcher(response.encodeURL(urlSessionInvalide)).forward(request,response);
....
// passa-se o controlo para o 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 é necessário descrever a sessão em curso
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 a utilizar o Netscape 4.5 e os cookies foram desativados. Acedemos pela primeira vez ao servlet cycledevie:

e atualizamos a página através do link «Atualizar a página»:

Podemos verificar que:
- a sessão não se alterou (continua a ser ID)
- o URL do servlet cycledevie contém efetivamente o token, tal como mostra o campo Adresse acima
- o servidor Tomcat recupera, portanto, o token de sessão no URL solicitado (desde que o programador tenha tido o cuidado de o codificar).
4.4. Exemplo 2
Apresentamos agora um exemplo que mostra como armazenar informações na sessão de um cliente. Neste caso, a única informação será um contador que será incrementado sempre que o utilizador chamar o URL do servlet. Quando este é chamado pela primeira vez, obtém-se a seguinte página:

Se clicarmos na ligação «Atualizar a página» acima, obtemos a seguinte nova página:

A aplicação tem três componentes:
- um servlet que processa o pedido do cliente
- uma página JSP que apresenta o valor do contador
- uma página JSP que apresenta um eventual erro
Estes três componentes estão instalados na aplicação web «sessions» já utilizada. O ficheiro web.xml desta aplicação foi alterado 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 chama-se «contador» (servlet-name) e está associada ao ficheiro de classe compteur.class (servlet-class)
- Possui dois parâmetros de inicialização:
- urlAffichageCompteur: URL da página JSP de visualização do contador
- urlErreur: URL da página JSP de exibição de um eventual erro
- e um alias /contador que faz com que seja chamado através 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 decorreu corretamente?
if(msgErreur!=null){
// passamos o controlo 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);
// passa-se o controlo 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á abordados. Destaca-se apenas a gestão do contador:
- a sessão é recuperada através de request.getSession()
- o contador é recuperado nesta sessão através de session.getAttribute("contador")
- se for recuperado um valor null, significa que a sessão acabou de começar. O contador é então colocado a 0.
- o contador é incrementado, colocado de volta na sessão (session.setAttribute("contador",contador)) e incluído na solicitação que será enviada ao servlet de visualização (request.setAttribute("contador",contador)).
A página de visualização compteur.jsp é a seguinte:
<%
// jspService
// a recuperar 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 limita-se 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 uma aplicação Java que funcione como cliente da aplicação compteur anterior. Esta chamá-la-ia N vezes consecutivas, sendo que N seria passado como parâmetro. O nosso objetivo é mostrar um cliente web programado e a forma de gerir os cookies. O nosso ponto de partida será um cliente web genérico apresentado no folheto sobre Java do mesmo autor. É 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 se limitar apenas aos cabeçalhos HTTP
Eis 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 apresenta tudo o que recebe do servidor. Acima, vemos o comando HTTP Set-cookie, com o qual o servidor envia um cookie ao seu cliente. Neste caso, 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 deverá reenviar o cookie ao servidor sempre que solicitar uma URL que comece por /sessions. Na aplicação sessions, utilizámos diferentes servlets, incluindo os servlets /sessions/cycledevie e /sessions/compteur. Se chamarmos o servlet /sessions/cycledevie, o navegador receberá um token J. Se, com esse mesmo navegador, chamar em seguida a servlet /sessions/compteur, o navegador reenviará ao servidor o token J, uma vez que este é válido para todas as servlets URL que começam por /sessions. No nosso exemplo, as servlets cycledevie e compteur não têm de partilhar o mesmo token de sessão. Por isso, não deveriam ter sido colocadas na mesma aplicação web. Este é um ponto a reter: todas as servlets de uma mesma aplicação partilham o mesmo token de sessão.
- Um cookie também pode definir um período de validade. Neste caso, essa informação não está presente. O cookie será, portanto, eliminado quando o navegador for fechado. Um cookie pode ter um período de validade de N dias, por exemplo. Enquanto estiver válido, o navegador irá reenviá-lo sempre que uma das páginas URL do seu domínio (Path) for consultada. Tomemos como exemplo um site de vendas online de CD. Este pode acompanhar o percurso do cliente no seu catálogo e determinar, gradualmente, as suas preferências: música clássica, por exemplo. Essas preferências podem ser armazenadas num cookie com uma validade de 3 meses. Se esse mesmo cliente regressar ao site ao fim de um mês, o navegador reenviará o cookie para a aplicação do servidor. Esta, com base nas informações contidas no cookie, poderá então adaptar as páginas geradas às preferências do seu cliente.
Segue-se o código do cliente web. Este servirá posteriormente de ponto de partida para outro cliente.
// pacotes importados
import java.io.*;
import java.net.*;
public class clientweb{
// solicita um URL
// exibe o conteúdo da mesma no ecrã
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);
// regista-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")){
// encomenda incorreta
erreur("Le second paramètre doit être GET ou HEAD",3);
}
// extraímos 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 escrita do cliente
String réponse=null; // resposta do servidor
try{
// estabelece-se a ligação ao servidor
client=new Socket(host,port);
// criam-se 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
// terminou
client.close();
} catch(Exception e){
// trata-se 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
Vamos agora criar o programa clientCompteur, chamado da seguinte forma:
clientCompteur URL N [JSESSIONID]
- URL: URL do servlet contador
- N: número de chamadas a efetuar a esta servlet
- JSESSIONID: parâmetro opcional — token de uma sessão
O objetivo do programa é chamar a servlet contador N vezes, gerindo o cookie de sessão e apresentando, em cada chamada, o valor do contador devolvido pelo servidor. No final das N chamadas, o valor do contador deve ser N. Eis 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 apresenta:
- os cabeçalhos HTTP que envia ao servidor na forma -->
- os cabeçalhos HTTP que recebe
- o valor do contador após cada chamada
Vê-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 o seu contador.
- o servidor, por sua vez, já não reenvia 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 predefinição, de 20 minutos (na verdade, é 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. Isto aponta para uma potencial falha de segurança. Se eu conseguir interceptar na rede um token de sessão, posso então fazer-me passar por quem iniciou essa sessão. No nosso exemplo, a primeira chamada representa quem inicia a sessão (talvez com um nome de utilizador e palavra-passe que lhe conferem o direito a obter um token) e a segunda chamada representa quem «hackeou» o token de sessão da primeira chamada. Se a operação em curso for uma operação bancária, isto pode tornar-se 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 da mesma no ecrã
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);
// regista-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];
// extraem-se 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 escrita 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 pesquisa do contador
boolean compteurTrouvé;
try{
// são efetuadas as N chamadas ao servidor
for(int i=0;i<N;i++){
// estabelece-se a ligação ao servidor
client=new Socket(host,port);
// criam-se 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é ao fim dos cabeçalhos, procurando 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 tivermos o token da sessão, procuramo-lo
if (JSESSIONID.equals("")){
// comparamos a linha HTTP com o modelo do cookie
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// encontrou-se o cookie
JSESSIONID=résultat.group(1);
}
}
}//enquanto
// terminou a verificação dos cabeçalhos HTTP — passa-se 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()){
// encontrámos o contador — exibimo-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 do erro
System.err.println(msg);
// encerramento com erro
System.exit(exitCode);
}//erro
// acompanhamento das trocas cliente-servidor
public static void envoie(PrintWriter OUT,String msg){
// envia mensagem ao servidor
OUT.println(msg);
// acompanhamento do ecrã
System.out.println("--> "+msg);
}//erro
}//classe
Vamos analisar os pontos importantes deste programa:
- é necessário efetuar N trocas cliente-servidor. É por isso que estas se encontram num ciclo
- em cada troca, o cliente abre uma ligação TCP-IP com o servidor. Assim que a ligação é estabelecida, o cliente envia ao servidor os cabeçalhos HTTP do seu pedido:
// 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,"");
Se o token JSESSIONID estiver disponível, é enviado na forma de um cookie; caso contrário, não é enviado.
- Depois de enviar o seu pedido, o cliente aguarda a resposta do servidor. Começa por analisar os cabeçalhos HTTP dessa resposta, à procura de um eventual cookie. Para o encontrar, compara as linhas que recebe com a expressão regular do cookie:
// o modelo procurado nos cabeçalhos HTTP
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
...........................
// lê-se a resposta até ao fim dos cabeçalhos, procurando o 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 tivermos o token da sessão, procuramo-lo
if (JSESSIONID.equals("")){
// comparamos a linha HTTP com o modelo do cookie
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// encontrou-se o cookie
JSESSIONID=résultat.group(1);
}
}
}//enquanto
- Quando o token for encontrado pela primeira vez, não será novamente procurado nas chamadas seguintes ao servidor. Depois de processados os cabeçalhos HTTP da resposta, passa-se para o código HTML dessa mesma resposta. Nesta, procura-se a linha que fornece o valor do contador. Esta pesquisa é feita também com uma expressão regular:
// o padrão do contador procurado no código HTML
Pattern modèleCompteur=Pattern.compile("compteur = .*?(\\d+)");
..................................
// terminou a análise dos cabeçalhos HTTP — passa-se 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()){
// Encontrámos o contador — exibimo-lo
System.out.println("compteur : " + résultat.group(1));
compteurTrouvé=true;
}
}
}//while
4.6. Exemplo 4
No exemplo anterior, o cliente web devolve o token sob a forma de um cookie. Vimos que também o pode devolver no próprio URL solicitado, sob a forma URL;jsessionid=xxx. Vamos verificar isso. O programa clientCompteur.java é transformado em clientCompteur2.java e alterado 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 através de GET URL;jsessionid=xx HTTP/1.1 e deixa de enviar o cookie. Esta é a única alteração. Eis 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-lhe enviando-lhe o token. O cliente volta então a consultar o mesmo URL, anexando-lhe o token recebido. Verifica-se que o contador foi efetivamente incrementado, o que comprova que o servidor reconheceu corretamente que se tratava da mesma sessão.
4.7. Exemplo 5
Este exemplo mostra uma aplicação composta por três páginas, a que chamaremos page0, page1 e page2. O utilizador deve aceder a elas nesta ordem:
- a página0 é um formulário que solicita uma informação: um nome
- a página1 é um formulário obtido em resposta ao envio do formulário da página0. Solicita uma segunda informação: uma idade
- a página2 é um documento HTML que apresenta o nome obtido pela página0 e a idade obtida pela página1.
Existem aqui três trocas cliente-servidor:
- na primeira interação, 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 page3 é solicitado pelo cliente e enviado pelo servidor. O cliente envia a idade ao servidor. O documento page3 deve apresentar 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 registar o nome na troca 2, para que este esteja disponível na troca 3.
A página «page0», obtida na primeira troca, é a seguinte:

Preenche-se o campo do nome:

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

Preenche-se o campo da idade:

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

Quando se envia a página «page0» para o servidor, este pode devolvê-la com um código de erro se o nome estiver em branco:

Quando se envia a página page1 para o servidor, este pode devolvê-la com um código de erro se a idade for inválida:

A aplicação é composta por um servlet e quatro páginas JSP:
exibe a página0 | |
exibe a página 1 | |
exibe a página 2 | |
exibe uma página de erro |
A aplicação web chama-se «suitedepages» e está configurada da seguinte forma no ficheiro server.xml do Tomcat:
O ficheiro de configuração web.xml da aplicação 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 chama-se «main» e, graças ao seu alias (servlet-mapping), é acessível através do URL http://localhost:8080/suitedepages/main. Possui quatro parâmetros de inicialização, que correspondem aos URL das quatro páginas JSP utilizadas para as diferentes visualizaçõ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 decorreu corretamente?
if(msgErreur!=null){
// passamos o controlo para a página de erro
getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
}
// recuperamos o parâmetro de etapa
String étape=request.getParameter("etape");
// recuperamos a sessão atual
HttpSession session=request.getSession();
// processa-se 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(){
// recuperam-se 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 ok?
if(urlPage0==null || urlPage1==null || urlPage2==null){
msgErreur="Configuration incorrecte";
}
}
//-------- etapa 0
public void étape0(HttpServletRequest request, HttpServletResponse response, HttpSession session)
throws IOException, ServletException{
// definem-se alguns atributos
request.setAttribute("nom","");
// apresenta-se a página 0
request.getRequestDispatcher(urlPage0).forward(request,response);
}
//-------- etapa 1
public void étape1(HttpServletRequest request, HttpServletResponse response, HttpSession session)
throws IOException, ServletException{
// recuperamos o nome na consulta
String nom=request.getParameter("nom");
// nome definido?
if(nom==null) étape0(request,response,session);
// removem-se os eventuais espaços do nome
nom=nom.trim();
// coloca-se num atributo da consulta
request.setAttribute("nom",nom);
// nome vazio?
if(nom.equals("")){
// é um erro
ArrayList erreurs=new ArrayList();
erreurs.add("Nous n'avez pas indiqué de nom");
// colocam-se os erros na consulta
request.setAttribute("erreurs",erreurs);
// regresso à página 0
étape0(request,response,session);
}
// nome válido — é guardado na sessão atual
session.setAttribute("nom",nom);
// define-se o atributo «idade» na consulta
request.setAttribute("age","");
// apresenta-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{
// recupera-se o nome da sessão
String nom=(String)session.getAttribute("nom");
// nome definido?
if(nom==null) étape0(request,response,session);
// coloca-se num atributo da consulta
request.setAttribute("nom",nom);
// recuperamos a idade da consulta
String age=request.getParameter("age");
// idade definida?
if(age==null){
// regresso à página 1
request.setAttribute("age","");
request.getRequestDispatcher(urlPage1).forward(request,response);
}
// a idade é memorizada 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);
// regresso à página 1
request.getRequestDispatcher(urlPage1).forward(request,response);
}
// idade válida - apresenta-se a página 2
request.getRequestDispatcher(urlPage2).forward(request,response);
}
}
- O método init recupera os quatro parâmetros de inicialização e apresenta uma mensagem de erro se algum deles estiver em falta
- vimos que a consulta incluía três trocas de dados. Para saber em que ponto nos encontramos nessas trocas, os formulários page0 e page1 têm uma variável oculta etape que tem o valor 1 (page0) ou 2 (page1). Este número pode ser considerado como o número da próxima página a apresentar. No método doGet, este parâmetro é recuperado da solicitação e, consoante o seu valor, o processamento é delegado a três outros métodos:
- étape0 processa o pedido inicial e envia 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). Se não for esse o caso, exibe novamente page0 como se fosse a primeira chamada.
- Verifica se o nome não está vazio. Se não for esse o caso, volta a apresentar o page0 com uma mensagem de erro.
- armazena o nome na sessão atual e apresenta 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). Se não for esse o caso, volta a apresentar 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. Se não for o caso, volta a apresentar page1 com uma mensagem de erro.
- armazena o nome e a idade como atributos da consulta e apresenta 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){
// regresso 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 o pedido inicial
- após o processamento do formulário de page0, caso ocorra um erro
- o parâmetro nom a apresentar é-lhe fornecido pelo servlet principal, bem como a eventual lista de erros. O servlet page0.jsp começa, portanto, por recuperar estas duas informações.
- O formulário é «enviado» para o servlet principal com o campo oculto (hidden) etape, que indica em que etapa da aplicação nos encontramos.
A página page1.jsp é a seguinte:
<%@ page import="java.util.*" %>
<% // page1.jsp
// recuperam-se 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){
// regresso 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
// recuperam-se 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){
// regresso 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. Limita-se a exibi-los. Por fim, a página erreur.jsp, responsável por apresentar 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>
Exibe o atributo msgErreur que lhe foi passado pelo servlet principal.
Em conclusão, pode-se observar que, ao longo das três etapas da aplicação, é sempre a servlet principal que é consultada em primeiro lugar pelo navegador. No entanto, não é ela que gera a resposta a apresentar, mas sim uma das quatro páginas JSP. O utilizador não percebe este pormenor, uma vez que o navegador continua a apresentar no seu campo «Endereço» a URL inicialmente solicitada, ou seja, a da servlet principal.
