DESAFIO ! Descartar uma requisição

5 respostas
G

Pessoal, estou com um problemão para resolver e queria ver se alguém pode me ajudar a descascar este abacaxi.

Seguinte:
Estou implementado o pattern Syncronize Token, segundo o livro Core J2EE Patterns, em uma implementação do MVC própria nossa.

Tenho um classe ControlerServlet que faz o papel da camda control do MVC.

Implementei uma classe CommandToken que possui os metodos de geracao e verificacao dos tokens para as requisicoes.

Isto está funcionando ok.
O problema é o que fazer quando eu receber uma requisição duplicada de uma página.

Segundo o pattern Token, apenas a primeira requisição deve ser executada, as outras devem ser descartadas.
Mas o problema é como descartar uma requisicao enviada para o meu ControlerServlet pelo Tomcat.
Se recebo a segunda requisicao e ignoro o seu processamento, dando um return void, o tomcat mostra uma pagina em branco. Quando a primeira requisicao termina o seu processo recebo uma mensagem dizendo que a resposta desta requisicao já foi enviada.

Conseguiram entender ?

Alguém já viu alguma implementacao deste pattern Token ?

Preciso resolver isso urgentemente. Conto a com a ajuda dos amigos.

5 Respostas

M

cara… eu nao entendi o teu problema, mas tb to lendo o Core Patterns, ah… o Struts tem um exemplo de synchronizer token :slight_smile: o proprio Core apresenta um código do Struts disso

H

Tenho um código desenvolvido para isso que ainda não foi 100% testado. Caso vc usá-lo, please poste o resultado no fórum para avaliação de todos:

Considerações Iniciais
Na maioria das aplicações web, reenviar um formulário não causa maiores transtornos. Porém, em algumas aplicações críticas, financeiras por exemplo, todo o cuidado deve ser tomado para que isto não ocorra. O uso de Javascript ajuda a resolver esse problema no lado do navegador. No lado servidor, Java deve ser chamado para o resgate.

O código apresentado neste tópico é uma tentativa de implementar as idéias de um artigo sobre o assunto na revista Dr. Dobbs.

O código

As servlets da aplicação devem ser derivadas de uma servlet abstrata que contém o seguinte código:

// os métodos a seguir são para a consistência de transações web 

  /** 
   * Verifica a consistência de uma transação Web. 
   * Este método pode ser chamado por uma servlet 
   * antes de completar uma transação. 
   * 
   * @return int com o código da verificação: 
   *   0: Ok 
   *   1: flowId não encontrado (transações cruzadas) 
   *   2: transaction tokens são diferentes (envio repetido de formulário) 
   *   3: resultado inesperado: flowDict é null (problema nas páginas html?) 
   * @throws ServletException se um dos parâmetros da transação faltar (flowId ou transTk) 
   */ 
  public int checaTransa() throws ServletException 
  { 
    String flowId = req.getParameter("flowId"); 
    String transTk1 = req.getParameter("transTk"); 
    if(flowId == null || transTk1 == null) 
      throw new ServletException("transaction token missing"); 
    Hashtable flowDict = (Hashtable) session.getAttribute("flowDict"); 
    if(flowDict == null) 
      return 3; 
    String transTk2 = (String) flowDict.get(flowId); 
    if(transTk2 == null) 
      return 1; 
    if(transTk1.equals(transTk2)) 
      return 0; 
    return 2; 
  } 

  /** 
   * Gera o identificador único de uma transação web. 
   * Este método pode ser chamado por um Estado que gere um formulário dinâmico. 
   * Se for usado diretamente num URL, deve passar antes por URLEncoder.encode() 
   * 
   * @param flowId String identificando o fluxo 
   * @return String codificada em base64 com o transaction token, que deve ser colocado num 
   * campo hidden do formulário 
   * @throws ServletException 
   */ 
  public String geraTransa(String flowId) throws ServletException 
  { 
    // abre o dicionário de fluxo 
    // 
    Hashtable flowDict = (Hashtable) session.getAttribute("flowDict"); 
    if(flowDict == null) 
    { 
      flowDict = new Hashtable(); 
      session.setAttribute("flowDict", flowDict); 
    } 
    // 
    // gera um novo transaction token 
    // 
    String token = null; 
    try 
    { 
      token = Acme.Utils.base64Encode(generateTransactionToken(flowId)); 
    } 
    catch(Exception e) 
    { 
      throw new ServletException(e); 
    } 
    // 
    // atualiza o dicionário 
    // 
    flowDict.put(flowId, token); 
    return token; 
  } 

  /** 
   * Gera o próximo transaction token. 
   * Ele é o hash de um número aleatório baseado no tempo real. 
   * 
   * @param flowId String identificadora do fluxo de acessos 
   * @return um array de bytes contendo o token 
   */ 
  protected byte[] generateTransactionToken(String flowId) throws Exception 
  { 
    MessageDigest sha = MessageDigest.getInstance("SHA-1"); 
    sha.update(flowId.getBytes()); 
    sha.update(session.getId().getBytes()); 
    java.util.Date d = new java.util.Date(); 
    long dl = d.getTime(); 
    sha.update((Long.toString(dl)).getBytes()); 
    return (sha.digest()); 
  }

As páginas dinâmicas de formulário que iniciam uma transação devem gerar os tokens de transação:

String flowId = "flow" + Util.getNonce(); 
    String transTk = geraTransa(flowId); 
    // 
    setTemplate("principal.html"); 
    synchronized (pagina) 
    { 
      pagina.setPlaceHolder("flowId", flowId); 
      pagina.setPlaceHolder("transTk", transTk); 
      pagina.send(); 
    }

As servlets que processam uma etapa da transação (geralmente uma só) devem verificar a validade da transação e agir de acordo:

// 
    // analisa a transação 
    // 
    try 
    { 
      int transa = checaTransa();  
      switch(transa) 
      { 
        case 0: 
          ... // Ok, transação válida 
          break; 
        case 1: 
          ... // flowId não encontrado (transações cruzadas?) 
          break; 
        case 2: 
          ... // transaction tokens são diferentes (envio repetido de formulário?) 
          break; 
        case 3: 
          ... //resultado inesperado: flowDict é null (problema nas páginas html?) 
          break; 
      } 
    } 
    catch(ServletException e) 
    { 
      // um dos parâmetros da transação faltando (flowId ou transTk) 
    } 

    ... 
    
    // repassa os transactions para a página dinâmica (caso do Velox) 
    // 
    String flowId = req.getParameter("flowId"); 
    String transTk = geraTransa(flowId); 
    // 
    pagina.setPlaceHolder("flowId", flowId); 
    pagina.setPlaceHolder("transTk", transTk); 
    ...

Os formulários que têm que ser rastreados devem receber os tokens de transação para serem repassados para a servlet que processa o post:

<FORM ACTION="post" ...> 
    <INPUT TYPE="hidden" NAME="flowId" VALUE="$flowId"> 
    <INPUT TYPE="hidden" NAME="transTk" VALUE="$transTk"> 
    ... 
    </FORM>
G

O problema meu amigo, não é o controle dos tokens e da verificação se uma transação é valida ou não.

Isto o meu codigo ja faz e pelo que vi o seu codigo tambem.

O problema que estou tendo é o que fazer com uma requisicao que tenha o token invalido.
Não tenho como descartar a requisicao, entao quando a primeira requisicao (a correta) termina de fazer o seu processamento ela nao consegue processar o response, pois a requisicao invalida ja deu uma resposta para o usuario (uma pagina em branco).

Teria que ter uma forma de dizer para o Tomcat para ignorar a requisicao invalida, e nao gerar nenhuma resposta. Mas nao estou conseguindo fazer isso.

Este é o Q da questão.

No Struts foi implementado toda a parte de criacao e verificacao de tokens, mas vc tem que usar estes metodos manualmente e esta parte de decidir o que fazer com a requisicao fica por sua conta. Isso me leva a acreditar que eles nao tinham uma solucao para isso, por isso so implementaram até esta parte do token.

Estou precisando muito resolver isso.

[]'s
Glaucio Jannotti

M

bem, o pattern Token não é bem assim…, o esquema é, adicionar uma chave na sessão do usuário, uma chave q muda a cada requisição do cliente, por exemplo, se eu calculo uma chave pra um formulario enviado pro usuário… ele envia o formulário pro controller, o controller faz oq tiver q fazer com os dados, gera um novo valor pra chave, e retorna… com um campo hiddel de novo… caso o usuário mande a mesma chave anterior repetida (ou seja, ele deu um “Back” e tentou inserir de novo os mesmos dados) o controller verifica q nao é mais a mesma chave (pois a cada request, ele gera um novo valor)… e não deixa inserir esses ultimos dados… possivelmente errados… , não to entendendo o lance de descartar a requisição q tu diz… :oops: não te entendi bem… “quando a primeira requisicao (a correta) termina de fazer o seu processamento ela nao consegue processar o response, pois a requisicao invalida ja deu uma resposta para o usuario” :oops:

L

Cara, quando o Token é invalido (ouve uma segunda requisição) gero uma exception o qual mostra um erro na tela do usuário. “Não é permitido papapapa…”. Desta forma o usuário acaba se acostumando, fica transparente para ele que não adianta ficar clicando que não vai ir mais rapido o processo o que em pouco tempo acaba resultando em pouquíssimas ocorrências de segundas requisições. Solução, aborta tudo e gera a exception… Não fica matando requisição pois não fica transparente para o usuário e ele continua fazendo varias requisições!

:lol:

Att
Lucas Balensiefer

Criado 11 de junho de 2004
Ultima resposta 15 de jun. de 2004
Respostas 5
Participantes 4