Comment implémenter le pattern Post/Redirect/Get avec Struts2

01/01/2012

Après avoir vu comment implémenter le design pattern Post/Redirect/Get avec des servlets, voici comment faire avec le framework Struts2 :

Environnement de développement :
jdk1.7
Eclipse Indigo
Tomcat 7.0.12

Tout d’abord, l’action Struts2 redirigeant vers la page index.jsp :

import com.opensymphony.xwork2.ActionSupport;

public class IndexAction extends ActionSupport {

	private boolean confirm;

	public String execute() throws Exception {
		return SUCCESS;
	}

	public boolean isConfirm() {
		return confirm;
	}

	public void setConfirm(boolean confirm) {
		this.confirm = confirm;
	}
}

Ensuite, la JSP index.jsp contenant un formulaire avec un bouton submit :

<!DOCTYPE html>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@taglib prefix="s" uri="/struts-tags" %>

<html>
<body>
	<s:if test="confirm" >
		L'article a bien été ajouté au panier.
	</s:if>
	<s:else>
		Cliquer pour ajouter l'article au panier.
	</s:else>
	<s:form action="prgAction">
		<s:submit />
	</s:form>
</body>
</html>

Ensuite, l’action Struts2 vers laquelle le formulaire est posté, qui effectue la redirection 302 vers une autre action Struts2. Contrairement au code utilisant des servlets, Struts2 ne permet pas de définir le code HTTP 303 pour la redirection.

import com.opensymphony.xwork2.ActionSupport;

public class PRGAction extends ActionSupport {

	public String execute() throws Exception {
		return "redirect";
	}
}

Enfin, le fichier de configuration struts.xml :

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
	<package name="myPackage" extends="struts-default">
		<action name="index" class="IndexAction">
			<result>/jsp/index.jsp</result>
		</action>
		<action name="prgAction" class="PRGAction">
			 <result name="redirect" type="redirectAction">
		 		<param name="actionName">index</param>
			 	<param name="confirm">true</param>
			 </result>
		</action>
	</package>
</struts>

Comment implémenter le pattern Post/Redirect/Get avec des Servlets

30/12/2011

Il existe 2 méthodes HTTP pour afficher une page web : Get et Post.

  • La méthode Get est utilisée lorsqu’on clique sur un lien ou pour afficher les résultats d’un formulaire de recherche. Dans ce 2ème cas, le fait que les paramètres de la requête soit présents dans l’url permet de bookmarker la page de résultats, ce qui n’est pas possible lorsqu’un formulaire utilise la méthode Post.
  • La méthode Post est généralement utilisée pour des formulaires contenant des données tapées par l’utilisateur et destinées à être traitées. C’est le cas des formulaires d’inscription, de login ou d’ajout d’un article à un panier.

Le problème avec la méthode Post, c’est le risque de double submit. En effet, après avoir soumis un formulaire, si un utilisateur tente de rafraîchir la page ou d’utiliser le bouton back du navigateur pour revenir à la page précédente, il obtient le message d’avertissement suivant :

Ce message peut faire peur aux utilisateurs, surtout après avoir soumis un formulaire de paiement.

Heureusement, grâce au design pattern Post/Redirect/Get (PRG), on peut éviter l’affichage de cette pop-up. Le principe du PRG est le suivant :

  • L’utilisateur soumet un formulaire avec la méthod Post
  • Le server traite les données et retourne au browser une réponse avec le code HTTP 303 (un redirect de type See Other) indicant l’url de la page de résultat
  • Le browser effectue une requête HTTP avec la méthode Get pour aller chercher la page de résultat

Il existe toutefois 2 inconvénients au design pattern PRG :

  • 2 allers-retours entre le client et le serveur au lieu d’un peut avoir un impact légèrement néfaste sur le temps de réponse de la page.
  • L’url de la page de résultat peut contenir des paramètres sensibles (qui ne seraient pas apparus dans l’url s’il n’y avait eu qu’un Post), ce qui peut poser problème au niveau de la sécurité.

Voici le code permettant d’implémenter le design pattern Post/Redirect/Get avec des servlets :

Environnement de développement :
jdk1.7
Eclipse Indigo
Tomcat 7.0.12

Tout d’abord, la JSP index.jsp contenant un formulaire avec un bouton submit :

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>

<%
	Boolean confirm = (Boolean)request.getAttribute("confirm");
%>

<!DOCTYPE html>
<html>
<body>
	<%
		if(confirm != null && confirm)
		{
			%>
			L'article a bien été ajouté au panier.
			<%
		}
		else
		{
			%>
			Cliquer pour ajouter l'article au panier.
			<%
		}
	%>

	<form action="/myapp/prgServlet" method="post" >
		<input type="submit" />
	</form>
</body>
</html>

Ensuite, la servlet vers laquelle le formulaire est posté, qui effectue la redirection 303 vers une autre servlet. On remarquera l’utilisation de l’annotation @WebServlet, ce qui évite d’avoir à déclarer la servlet dans le fichier web.xml :

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/prgServlet")
public class PRGServlet extends HttpServlet {

	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) {

		response.setStatus(HttpServletResponse.SC_SEE_OTHER);
		response.setHeader("Location", "/myapp/confirmationServlet?confirm=true");
	}

}

Enfin, la servlet qui redirige vers la jsp correspondant à la page de résultat :

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/confirmationServlet")
public class ConfirmationServlet extends HttpServlet {

	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		request.setAttribute("confirm",
				Boolean.valueOf(request.getParameter("confirm")));

		getServletContext().getRequestDispatcher("/index.jsp").forward(request,
				response);
	}
}

Dans un prochain article, nous verrons comment implémenter le design pattern Post/Redirect/Get avec Struts2.