Skip to content

10. Aplicação Web MVC [person] – Versão 5

10.1. Introdução

Nesta versão, estamos a fazer duas alterações:

A primeira diz respeito à forma como o cliente indica ao servidor a ação que pretende realizar. Até agora, isto era especificado utilizando um parâmetro chamado [action] no pedido GET ou POST do cliente. Aqui, a ação será especificada pelo último elemento do URL solicitado pelo cliente, conforme mostrado na sequência seguinte:

Image

Em [1], a URL para a qual o formulário foi enviado é [/person5/do/validateForm]. É o último elemento [validateForm] da URL que permitiu ao controlador reconhecer a ação a ser realizada. Em [2], o POST acionado pelo link [Voltar ao formulário] foi enviado para a URL [/person5/do/returnForm]. Aqui, mais uma vez, o último elemento [returnForm] da URL indica ao controlador qual ação deve ser executada.

Estamos a introduzir esta alteração porque é o método utilizado pelas estruturas de desenvolvimento web mais utilizadas, tais como Struts ou Spring MVC.

Todas as URLs na aplicação terão o formato [/person5/do/action]. O ficheiro [web.xml] da aplicação [/person5] especificará que aceita URLs do formato [/do/*]:


    <servlet-mapping>
        <servlet-name>personne</servlet-name>
        <url-pattern>/do/*</url-pattern>
</servlet-mapping>

O controlador irá recuperar o nome da ação a ser executada da seguinte forma:

        // on récupère l'action à exécuter
String action=request.getPathInfo();

O método [getPathInfo] do objeto [request] devolve o último elemento do URL do pedido.

A segunda alteração diz respeito à forma como as entradas do utilizador são armazenadas entre os ciclos de pedido/resposta. Atualmente, esta informação é armazenada numa sessão. Esta abordagem pode ter desvantagens se houver muitos utilizadores e uma grande quantidade de dados a armazenar para cada um. De facto, cada utilizador tem a sua própria sessão pessoal. Além disso, a sessão permanece ativa durante algum tempo após o utilizador terminar a sessão, a menos que tenha sido fornecida uma opção de encerramento. Assim, 1 000 sessões de 100 bytes cada ocuparão 1 MB de memória. Este continua a ser um requisito moderado, e poucas aplicações têm 1 000 sessões ativas simultaneamente.

No entanto, existem alternativas às sessões que consomem menos memória, e é bom estar ciente delas. Aqui, utilizaremos o método dos cookies. Vamos ilustrar isto com um exemplo.


Passo 1: O utilizador envia um formulário:


Este ciclo de solicitação/resposta resulta nas seguintes trocas HTTP entre o cliente e o servidor:

[1]: [solicitação do cliente]

POST /personne5/do/validationFormulaire HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://localhost:8080/personne5/do/formulaire
Cookie: JSESSIONID=6C6F4D112803A7E3696D41F5750CEDE7
Content-Type: application/x-www-form-urlencoded
Content-Length: 24

txtNom=pauline&txtAge=18

Esta é uma solicitação POST padrão. Não há nada de invulgar a destacar aqui, exceto que, embora não vamos utilizar uma sessão, o servidor web cria uma na mesma. Isto é evidente a partir do token de sessão que o navegador reenvia ao servidor na linha 11, o qual tinha recebido anteriormente do servidor.

[2]: [resposta do servidor]

1
2
3
4
5
6
7
HTTP/1.x 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: nom=pauline
Set-Cookie: age=18
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 547
Date: Mon, 22 May 2006 08:03:51 GMT

Podemos ver que nas linhas 3 e 4, foram enviados cabeçalhos HTTP [Set-Cookie] para o navegador do cliente, um para o nome (linha 3) e outro para a idade (linha 4). Os valores destes cookies são os valores enviados na linha 14 do POST [1] acima.


Passo 2: Voltar ao formulário


Image

Este ciclo de pedido/resposta resulta nas seguintes trocas HTTP entre o cliente e o servidor:

[1]: [solicitação do cliente]

POST /personne5/do/retourFormulaire HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://localhost:8080/personne5/do/validationFormulaire
Cookie: nom=pauline; age=18; JSESSIONID=6C6F4D112803A7E3696D41F5750CEDE7
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

Aqui vemos o pedido POST desencadeado ao clicar na ligação [Voltar ao formulário]. Na linha 11, vemos que o navegador reenvia os cookies que recebeu [name, age, JSESSIONID] para o servidor utilizando o cabeçalho HTTP [Cookie]. É assim que os cookies funcionam. O cliente reenvia para o servidor os cookies que o servidor lhe enviou. Neste exemplo, o controlador receberá os valores [pauline, 18], que deve colocar nos campos [txtName, txtAge] da vista [form] apresentada em [2].

[2]: [resposta do servidor]

1
2
3
4
5
HTTP/1.x 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 2341
Date: Mon, 22 May 2006 08:16:47 GMT

Não há nada de especial a destacar aqui, além do facto de, nesta resposta, o servidor não ter enviado quaisquer cookies. Isto não impedirá o navegador de reenviar todos os cookies que recebeu do servidor na próxima troca, mesmo que isso não tenha qualquer utilidade. Reduzimos, assim, a carga sobre a memória disponível do servidor, à custa de um aumento no fluxo de caracteres nas trocas cliente/servidor.

10.2. O projeto Eclipse

Para criar o projeto Eclipse [mvc-personne-05] para a aplicação web [/personne5], duplique o projeto [mvc-personne-04] seguindo o procedimento descrito na secção 6.2.

10.3. Configurar a aplicação web [personne5]

O ficheiro web.xml para a aplicação /personne5 é o seguinte:


<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>mvc-personne-05</display-name>
    <!--  ServletPersonne -->
    <servlet>
        <servlet-name>personne</servlet-name>
        <servlet-class>
            istia.st.servlets.personne.ServletPersonne
        </servlet-class>
...
    </servlet>
    <!--  Mapping ServletPersonne-->
    <servlet-mapping>
        <servlet-name>personne</servlet-name>
        <url-pattern>/do/*</url-pattern>
    </servlet-mapping>
    <!--  welcome files -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>
 

Este ficheiro é idêntico ao da versão anterior, exceto por alguns detalhes:

  • linha 6: o nome de exibição da aplicação web mudou para [mvc-personne-05]
  • linha 18: os URLs tratados pela aplicação têm o formato [/do/*]. Anteriormente, apenas o URL [/main] era tratado. Agora, existem tantos URLs quantas as ações a tratar.

A página inicial [index.jsp] muda:


<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
 
<c:redirect url="/do/formulaire"/>
  • linha 5: a página [index.jsp] redireciona o cliente para a URL [/person5/do/form], o que equivale a solicitar ao controlador que execute a ação [form].

10.4. O código da vista

As visualizações [form, response, errors] mudam muito pouco. A única alteração é que a ação a ser executada já não é especificada da mesma forma que antes, quando era definida num campo oculto denominado [action] nos formulários enviados. Agora é definida no URL de destino dos formulários enviados, ou seja, no atributo [action] da tag <form>:

[form.jsp]:


...
<html>
  <head>
    <title>Personne - formulaire</title>
    <script language="javascript">
...
    </script>
  </head>
  <body>
    <center>
      <h2>Personne - formulaire</h2>
      <hr>
      <form name="frmPersonne" action="validationFormulaire" method="post">
...
      </form>
    </center>
  </body>
</html>
  • Linha [13]: O parâmetro [action] do formulário reaparece após ter estado ausente durante algum tempo nas versões anteriores. Para compreender o valor deste atributo aqui, lembre-se de que todos os URLs processados pela aplicação têm o formato [/do/action]. Na linha [13], o atributo [action] tem como valor um URL relativo (que não começa por /). Portanto, o navegador irá completá-lo com a URL da página atualmente exibida, que é necessariamente uma URL do formato [/do/action]. O último elemento será substituído pela URL relativa do atributo [action] da tag <form> para produzir a URL [/do/validationFormulaire] como destino do POST.
  • O campo oculto [action] desapareceu

[response.jsp]:


...
 
<html>
...
  <body>
      ...
    <form name="frmPersonne" action="retourFormulaire" method="post">
    </form>
    <a href="javascript:document.frmPersonne.submit();">
      ${lienRetourFormulaire}
    </a>
  </body>
</html>
 
  • linha [7]: o destino do POST será [/do/returnForm]
  • O campo oculto [action] foi removido do formulário nas linhas 7–8.

[errors.jsp]:


...
<html>
...
  <body>
...
    <form name="frmPersonne" action="retourFormulaire" method="post">
    </form>
    <a href="javascript:document.frmPersonne.submit();">
      ${lienRetourFormulaire}
    </a>
  </body>
</html>
 
  • linha [6]: o destino do POST será [/do/returnForm]
  • O campo oculto [action] foi removido do formulário nas linhas 6–7.

Convidamos os leitores a testarem estas novas visualizações utilizando a abordagem apresentada nas versões anteriores.

10.5. O controlador [ServletPersonne]

O controlador [ServletPersonne] da aplicação web [/personne5] irá tratar das seguintes ações:

N.º
pedido
origem
processamento
1
[GET /person5/do/form]
URL introduzida pelo utilizador
- enviar a vista [form] vazia
2
[POST
/person5/do/formValidation]
com os parâmetros [txtName, txtAge]
enviado
clique no
[Enviar] no
[formulário]
- verifique os valores dos parâmetros [txtName, txtAge]
- se estiverem incorretos, envie a vista [errors(errors)]
- se estiverem corretos, envie a visualização [response(name,age)]
3
[POST
/person5/do/returnForm]
sem parâmetros enviados
Clique no [Voltar ao
formulário] a partir da
[resposta] e [erros].
- enviar a vista [formulário] pré-preenchida com os valores introduzidos mais recentemente

A estrutura do controlador [ServletPersonne] é idêntica à da versão anterior. Iremos rever as alterações feitas aos métodos [doValidationFormulaire, doRetourFormulaire, doGet]; os métodos [init, doInit, doPost] permanecem inalterados.

10.5.1. O método [doGet]

O método [doGet] não recupera a ação a ser executada da mesma forma que nas versões anteriores:

        @SuppressWarnings("unchecked")
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {

        // check how the servlet was initialized
        if (erreursInitialisation.size() != 0) {
...
        }
        // retrieve the request sending method
        String méthode=request.getMethod().toLowerCase();
        // retrieve the action to be executed
        String action=request.getPathInfo();
        // action?
        if(action==null){
            action="/formulaire";
        }
        // execution action
        if(méthode.equals("get") && action.equals("/formulaire")){
            // start application
            doInit(request,response);
            return;
        }
        if(méthode.equals("post") && action.equals("/validationFormulaire")){
            // validation of input form
            doValidationFormulaire(request,response);
            return;
        }
        if(méthode.equals("post") && action.equals("/retourFormulaire")){
            // back to input form
            doRetourFormulaire(request,response);
            return;
        }
        // other cases
        doInit(request,response);
    }
  • linha 12: recuperar a ação a ser executada. Está no formato [/action].
  • linhas 18–22: processamento da ação [/form] solicitada por uma solicitação GET
  • linhas 23–27: processamento da ação [/validateForm] solicitada por uma solicitação POST
  • linhas 28–32: processamento da ação [/returnForm] solicitada por uma solicitação POST

10.5.2. O método [doValidationFormulaire]

Este método processa a solicitação n.º 2 [POST /person5/do/validationFormulaire] com [txtName, txtAge] nos elementos enviados. O seu código é o seguinte:

// form validation
    void doValidationFormulaire(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException{
        // parameters are retrieved
        String nom = request.getParameter("txtNom");
        String age = request.getParameter("txtAge");
        // stored in a cookie
        response.addCookie(new Cookie("nom",nom));
        response.addCookie(new Cookie("age",age));
        // parameter verification
        ...
    }

Novidades:

  • O método [doValidationFormulaire] devolve uma das vistas [response, errors]. Independentemente da resposta, o controlador define dois cookies no seu interior, linhas 8–9. Um cookie é representado por um objeto [Cookie] cujo construtor aceita dois parâmetros: a chave do cookie e o valor associado a ela.
  • linha 8: o valor introduzido para o nome é colocado num cookie com a chave «name»
  • linha 9: o valor introduzido para a idade é colocado num cookie com a chave "age"
  • Um cookie é adicionado à resposta HTTP enviada ao cliente utilizando o método [response.addCookie]. Esta resposta é apenas preparada aqui. Só será efetivamente enviada quando a página JSP da vista enviada ao cliente for executada.

10.5.3. O método [doRetourFormulaire]

Este método processa o pedido n.º 2 [POST /person5/do/retourFormulaire] sem quaisquer dados enviados. O seu código é o seguinte:

        // display pre-filled form
    void doRetourFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        // retrieve the user's cookies
        Cookie[] cookies=request.getCookies();
        String nom=null;
        String age=null;
        int nbCookies=0;
        for(int i=0;i<cookies.length && nbCookies<2;i++){
            if(cookies[i].getName().equals("nom")){
                nom=cookies[i].getValue();
                nbCookies++;
            }else{
                if(cookies[i].getName().equals("age")){
                    age=cookies[i].getValue();
                    nbCookies++;
                }
            }
        }
        // prepare the form template
        request.setAttribute("nom",nom);
        request.setAttribute("age",age);
        // the form is displayed
        getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward(
                request, response);
        return;
    }

Novidades:

O método [doRetourFormulaire] deve apresentar um formulário pré-preenchido com os dados mais recentes. Na versão anterior, estes eram armazenados na sessão. Nesta versão, já não utilizamos a sessão, mas sim cookies para armazenar dados entre as trocas cliente-servidor. Quando o cliente solicitava a validação do formulário, recebia a vista [response] ou [errors] em resposta, conforme o caso, juntamente com dois cookies denominados «name» e «age». Quando o link [Voltar ao formulário] nestas duas vistas é clicado — o que desencadeia um pedido POST para o URL [/do/retourFormulaire] — o navegador reenvia os dois cookies que recebeu para o servidor.

  • Linhas 4–18: Recuperamos os valores dos cookies denominados «name» e «age». Curiosamente, não existe um método para recuperar o valor de um cookie com base na sua chave. Por isso, temos de percorrer cada um dos cookies recebidos.
  • Feito isso, os dois valores obtidos são colocados no modelo da vista [form] (linhas 20–21) para que este os possa apresentar.

10.6. Testes

Inicie ou reinicie o Tomcat após integrar o projeto Eclipse [person-mvc-05] nele e, em seguida, solicite a URL [http://localhost:8080/personne5].