Repitam comigo: Redirect não é forward

É engraçado o número de desenvolvedores que se utilizam da tag de navegação <redirect/> quando configuram suas regras de navegação no faces-config.xml sem entender o porquê de sua finalidade, na maioria das vezes a única coisa que eles tem conhecimento -e acreditam que esta é finalidade da tag- é que ao utiliza-la a url na barra de endereços do browser muda.

Depois disso eles não entendem porque os valores não existem mais no managed bean ou porque as mensagens de erro não são mais exibidas ao usuário ou mesmo porque uma nova instância do managed bean foi criada, enfim, eles não entendem porque afinal a aplicação parou de funcionar!

Tentarei com este post esclarer a diferença entre um redirect e um forward e como contornar o problema acima para que novos desenvolvedores não caiam em “maus lençóis”.

Redirect não é forward

Um servlet (controller) pode executar tanto uma operação de um redirect como de um forward no final do processamento de uma requisição, e isso influencia como as páginas no browser serão recarregadas. É importante que um desenvolvedor entenda os fundamentos por trás de uma aplicação web, e um deles é a diferença entre redirect e um forward.

Segue abaixo a diferença entre eles:

Forward

  • É executado internamente pelo servlet (controller);
  • O browser não sabe o que está ocorrendo durante o processamento no servidor, ou seja, não sabe por quais servlets ou páginas a requisição está passando;
  • No final do processamento da requisição a url da barra de endereços do browser não muda;
  • O reload da página resultante irá executar a requisição original;

Redirect

  • É um processo de dois passos, ao receber uma requisição a aplicação web “pede” ao browser para acessar uma segunda url, por isso a url muda;
  • O reload de página não repetirá a requisição original, mas sim a nova url (2ª requisição);
  • É um processo muito mais lento que um forward, pois são necessárias duas requisições, e não uma;
  • Objetos colocados no escopo do request original são perdidos durante o segundo request;

Resumindo, um redirect é uma nova requisição que o cliente (browser) faz a pedido da aplicação web, logo ele fica ciente sobre como está ocorrendo a navegação e para onde ele está sendo redirecionado, enquanto um forward pode executar várias requisições no lado servidor sem o conhecimento do cliente e no final retornar uma página qualquer. Atentem também que um forward mantém os atributos e parâmetros do request original, já um redirect não.

Como podem ver, de maneira sútil eles fazem a mesma coisa, mas são bem diferentes.

O problema

Vamos observar um caso clássico abaixo:

Imagine que temos um managed bean configurado em escopo de request e que temos uma regra de navegação no nosso faces-config.xml na qual explicitamos o uso de redirect através da tag <redirect/>:

<navigation-rule>
	<from-view-id>/pages/pageX.jsp</from-view-id>
	<navigation-case>
		<from-outcome>nova_pagina_com_redirect</from-outcome>
		<to-view-id>/pages/pageY.jsp</to-view-id>
		<redirect />
	</navigation-case>
</navigation-rule>

E em uma determinada página X o usuário clica em um botão que executa um método no managed bean e depois disso o usuário é enviado para uma outra página Y com uma mensagem de sucesso (ou erro) e alguns objetos populados no managed bean para serem exibidos, logo teríamos um método no managed bean semelhante a isso:

public String submit() {
	// processa algo
	BacalhauService.processaRequisicao();

	// popula atributo do managed bean
	this.att1 = "A ligeira raposa marrom saltou sobre o cão preguiçoso.";

	// adiciona mensagem de sucesso
	FacesContext ctx = FacesContext.getCurrentInstance();
	ctx.addMessage(null, new FacesMessage("Operação concluída com sucesso."));

	return "nova_pagina_com_redirect";
}

Imagine que quando o usuário executou a ação o processamento no método do managed bean ocorreu, a navageção para a página Y funcionou perfeitamente e a url na barra de endereços do browser mudou como previsto, mas espere um pouco! “What the hell is this?” A mensagem de sucesso e os dados contidos no managed bean não foram exibidos, onde elas foram parar afinal de contas?

No caso acima nós navegamos da página X para a página Y através de um redirect (lembram da tag <redirect/> na regra de navegação?), e já sabemos que quando há um redirect todos os dados no escopo de request são perdidos, logo o nosso managed bean e nossa mensagem de sucesso foram perdidos.

Agora que já entendemos o funcionamento do redirect não foi uma surpresa o managed bean ter sido perdido (reinstanciado) pois sabiamos que ele estava configurado com escopo de request, mas e a mensagem de sucesso? Bem, isso fica óbvio ao saber que as mensagens do JSF são adicionadas no escopo de request, mesmo que seu managed bean esteja noutro escopo.

A solução

Como resolver isso? Bem, existem algumas soluções, porém a mais simples sem dúvida é remover a tag <redirect/> da regra de navegação, assim a navegação entre páginas ocorrerá através de forward (que é o default do framework), e os dados do seu managed bean e mensagens serão exibidos ao usuário como esperado.

Na maioria das aplicações web um dos motivos para se utilizar um redirect é para evitar que no reload/refresh da página ocorra uma resubmissão do form, evitando-se assim resultados inesperados na aplicação, sendo, uma solução para isso seria a utilização do Post-Redirect-Get Pattern (PRG). Uma excelente solução é a implementação do padrão PRG através deste Phase Listener implementado pelo BalusC.

Não tenho certeza, mas acho (quase certeza) que o JBoss Seam já tem algo implemetado para solucionar o problema. Se alguém puder confirmar, eu ficaria grato :)

Felizmente hoje em dia as aplicações web estão caminhando para uma GUI mais rica e versátil, e ao abrir mão da “velha escola” conseguimos contornar os problemas citados acima facilmente, como também obter inúmeras outras vantagens.

Concluindo

Como podem ver, o problema não era do JavaServer Faces (JSF) ou mesmo de qualquer outro framework MVC, mas certamente da falta de fundamentos sobre a web. Espero sinceramente que os desenvolvedores busquem entender os fundamentos dos frameworks, padrões e da web antes de desenvolver uma aplicação web, pois utilizar algo nas cegas provavelmente trará vários problemas mais cedo ou mais tarde.

Enfim, este é um problema decorrente na lista de discussão do javasf, e sempre acabamos por explicar a diferença entre as duas operações, que no final resolve-se apenas seguindo o caminho mais simples. Fico aqui e espero que este post tenha servido de ajuda a muitos desenvolvedores.