Redirections dans une application Web
Dans une application web construite selon l'architecture REST, l'URL (adresse web) est considéré comme une une URI (Uniform Resource Identifier) qui identifie une ressource (par exemple un compte client, une facture, une référence catalogue...). Dans ce contexte, idéalement, utiliser une application, c'est naviguer de ressource en ressource.
Par delà l'idéologie, cette approche présente un intérêt : elle rend tous les écrans de l'application "bookmarkables" (il peuvent être classés en tant que favoris). On n'est plus contraint de suivre un cheminement imposé par l'applicatif. On peut, au contraire, aller directement à un écran donné. NB : cela est possible dans la mesure où l'architecture REST est une architecture sans état.
Comment pratiquement arriver à un tel résultat ?
Solution 1 : REST naïf.
On peut imaginé de rester(!) dans un contexte REST simple. Prenons un exemple : ajouter une facture à un compte client, c'est poster (faire une requête avec la méthode Post) la facture à l'URL du client. Dans la pratique cela n'est pas forcément ce que l'on veut. Il est par exemple plus rationnel de rester non pas sur le compte client, mais plutôt sur la facture. Il faudrait donc poster sur l'URL de la facture. Cette solution n'est pas satisfaisante pour trois raisons :
- c'est une entorse au REST et à l'interprétation des méthodes de requête;
- changer la navigation d'un écran à un autre nécessite des modifications importantes dans le programme de l'application web;
- on ne connaît pas, en général, à l'avance l'URI d'une nouvelle facture, car c'est traditionnellement l'application qui affecte de manière unique URI et ressource à partir, par exemple d'un numéro généré arbitrairement.
Afin de changer complètement l'approche, on peut avoir l'idée d'utiliser un mécanisme de redirection contrôlée par le serveur. Lorsque le navigateur web demande une URL, il est redirigé vers une autre URL. Il existe plusieurs manières de faire des redirections et nous allons en citer 2 qui peuvent être utilisée pour notre problème et une troisième non-testée
Solution 2 : utiliser les rédirections du protocole HTTP
Il existe un article de référence sur le sujet. Il est assez costaud pour un débutant mais est très complet. Essayons de simplifier l'approche.
Lorsque l'on cherche à visualiser une page web, on envoie une requête vers le serveur web qui héberge la page en question. Celui-ci, s'il est en fonctionnement , renvoie une réponse. Cette réponse contient un code, dit statut, qui indique la nature de la réponse. Le code le plus connu de l'internaute est le code 404 qui signifie : page non-trouvée. Pourtant le code le plus fréquent, heureusement, est le code 200 qui indique que la page a bien été trouvée.
Ce mode d'échange entre navigateur et serveur respecte une norme. Il s'agit du protocole HTTP.
Il existe des statuts, compris dans la plage 300 à 307 et décrits par la norme HTTP, qui sont spécialement utilisés pour faire des redirections. Examinons les codes utilisables (la manière dont les différents navigateurs comprennent les statuts de redirections est détaillée dans l'article de référence cité ci-dessus) :
- 300 : Choix multiple : ce statut consiste à présenter au navigateur plusieurs représentations de la même ressource pour laisser à celui-ci le choix automatique de la représentation la plus adaptée à ses capacités ou à sa configuration. Ce statut ne répond pas à notre besoin.
- 301 : Redirection permanente : permet d'indiquer un déplacement définitif de la ressource. C'est ce statut qui est utilisé, par exemple, pour faire en sorte que toute requête vers http://www.dubourg.name/ soient redirigées vers le blog http://www.dubourg.name/s9y/. Ce code n'est pas utilisable, car la norme précise que le navigateur peut enregistrer la réponse du serveur de manière permanente et ainsi ne plus jamais effectuer la requête originale.
- 302 : Page non trouvée : la réponse présente un URL alternatif et le navigateur doit demander à l'Internaute s'il souhaite utiliser le nouveau URL. Ce code est théoriquement non-utilisable puisqu'il suppose une action de la part de l'utilisateur, mais dans la pratique, une erreur d'interprétation de la norme par tous les navigateurs, conduit à un fonctionnement automatique. Opéra qui suivait initialement la norme à du lui-même introduire ce bug dans son fonctionnement pour être compatible avec les autres navigateurs. 302 est donc un choix possible qui est même mieux reconnu que le code 303. Le code 302 dans son fonctionnement d'origine a été rebaptisé 307, dans l'espoir d'une meilleure implémentation par les navigateurs. 307 n'est donc pas utilisable pour notre problème
- 303 : aller vers un autre URL. C'est exactement pour régler notre problème que ce statut a été inventé. Pour entrer un peut dans le détail le navigateur qui effectue initialement un POST (méthode utilisée pour effectuer une modification d'une ressource - voir l'explication sur les méthodes associées aux requêtes), doit, lorsqu'il reçoit cette réponse, effectuer en suivant un GET (méthode utilisée pour visualiser une ressource). Ce code est moins bien supporté que le 302. wget ne le reconnaît pas par exemple.
Les autres codes ne présentent pas d'intérêt pour notre problème.
Il est donc possible d'utiliser la redirection HTTP. Mais supposons qu'en plus d'afficher la ressource, on veuille indiquer à l'utilisateur que l'opération s'est bien déroulée avec un message du type "Votre facture à été téléchargée". On ne peut pas faire cela avec cette méthode. On peut en revanche la compléter pour arriver à nos fins.
Solution 3 : utiliser les rédirections du protocole HTTP + des cookies
Utiliser des cookies consiste à enregistrer dans le navigateur des données. A chaque requête, le navigateur ré-émet ces données vers le serveur. On pourrait, tout en utilisant une redirection HTTP, utiliser un cookie pour passer le message de l'URL d'origine à l'URL de redirection.
Cette méthode présente quelques inconvénients
- les données du cookies ne peuvent pas dépasser 4000 caractères, ce qui peut être insuffisant dans certains cas;
- certains navigateur (Mozilla, Konqueror) n'ont qu'un environnement cookie par utilisateur : si on fait deux connexionx c1, c2 à la même application, c2 recevra les cookies émis par c1 et inversement. Très gênant si on veut faire deux choses à la fois dans la même application, par exemple, ouvrir 2 onglets qui affichent des données différentes.
- certains navigateurs (Internet explorer) gère bien des environnements cookies séparés, mais uniquement si on relance un navigateur mais pas si on ouvre une nouvelle fenêtre. Cela est source de confusion;
- l'utilisation des cookies viole REST voir le chapitre 6.3.4.2 de la thèse de Fielding;
- les fait que le cookies représente un état de la relation navigateur <-> serveur, état indépendant de l'état représenté par la page web elle-même peut entraîner des décorrélations lorsqu'on utilise le bouton "back" de l'historique du navigateur. Il faut évidemment chercher un exemple plus complexe que le simple passage de message entre pages pour illustrer ce problème.
Bref, l'utilisation de cookies présente bien des inconvénients dans le cadre de notre problème d'origine. Voir aussi :la RFC qui définit les cookies.
Solution 4 : utiliser la redrection HTTP + une session
Pour passer le message avec une redirection HTTP, on peut penser à utiliser un mécanisme de session. Cela consiste à stocker le message côté serveur.
Or pour identifier une session entre le navigateur et le serveur, on utilise :
- soit un cookie : dans ce cas là, on retrouve les problèmes cités pour les cookies dans la solution 3 (sauf le problème de taille maximale du cookie);
- soit un paramètre (jsession par exemple) : dans ce cas là, on n'est plus dans le problème initial. Rappel : on veut un URL qui soit un URI. L'introduction dans l'URL d'un &jsession=... ne permet pas de rester dans ce cadre.
Par ailleurs on est complètement en dehors de REST (on n'est plus dans un système sans état) et on est obliger de mettre en place un serveur de session, architecture technique en général complexe.
En résumé, on utilisera préférentiellement la redirection HTTP, si en navigant d'une étape à l'autre, on n'a pas besoin de passer une donnée à vie éphémère, comme par exemple un message de bon déroulement vers l'utilisateur.
Solution 5 : utilisation d'une redirection Javascript
Voici par exemple une fonction en C++ que l'on peut utiliser pour faire cette redirection (on suppose que le message s'il existe est contenu dans la variable error) :
Cette méthode fonction très bien. Son inconvénient principal est d'inscrire, pour une seule action, deux URL dans l'historique du navigateur.
Solution 6 : utiliser XmlHttpRequest
Attention : ceci n'est qu'une ébauche, c'est la seule solution que je n'ai pas testé.
L'idée est, lorsque l'on veut valider une modification, de donner la main à une fonction javascript qui effectue les opérations suivantes :
1. Envoi de la demande de modification, tout en restant dans la page d'origine grâce à un objet de type XmlHttpRequest.
2. Récupération de l'URL de redirection dans la réponse du serveur à la requête ci-dessous.
3. Demande au navigateur de l'affichage de la page correspondante
Cette méthode résout le problème de doublon dans l'historique. L'inconvénient principal est de rendre moins visible les interactions Navigateur <-> Serveur
On peut imaginé de rester(!) dans un contexte REST simple. Prenons un exemple : ajouter une facture à un compte client, c'est poster (faire une requête avec la méthode Post) la facture à l'URL du client. Dans la pratique cela n'est pas forcément ce que l'on veut. Il est par exemple plus rationnel de rester non pas sur le compte client, mais plutôt sur la facture. Il faudrait donc poster sur l'URL de la facture. Cette solution n'est pas satisfaisante pour trois raisons :
- c'est une entorse au REST et à l'interprétation des méthodes de requête;
- changer la navigation d'un écran à un autre nécessite des modifications importantes dans le programme de l'application web;
- on ne connaît pas, en général, à l'avance l'URI d'une nouvelle facture, car c'est traditionnellement l'application qui affecte de manière unique URI et ressource à partir, par exemple d'un numéro généré arbitrairement.
Afin de changer complètement l'approche, on peut avoir l'idée d'utiliser un mécanisme de redirection contrôlée par le serveur. Lorsque le navigateur web demande une URL, il est redirigé vers une autre URL. Il existe plusieurs manières de faire des redirections et nous allons en citer 2 qui peuvent être utilisée pour notre problème et une troisième non-testée
Solution 2 : utiliser les rédirections du protocole HTTP
Il existe un article de référence sur le sujet. Il est assez costaud pour un débutant mais est très complet. Essayons de simplifier l'approche.
Lorsque l'on cherche à visualiser une page web, on envoie une requête vers le serveur web qui héberge la page en question. Celui-ci, s'il est en fonctionnement , renvoie une réponse. Cette réponse contient un code, dit statut, qui indique la nature de la réponse. Le code le plus connu de l'internaute est le code 404 qui signifie : page non-trouvée. Pourtant le code le plus fréquent, heureusement, est le code 200 qui indique que la page a bien été trouvée.
Ce mode d'échange entre navigateur et serveur respecte une norme. Il s'agit du protocole HTTP.
Il existe des statuts, compris dans la plage 300 à 307 et décrits par la norme HTTP, qui sont spécialement utilisés pour faire des redirections. Examinons les codes utilisables (la manière dont les différents navigateurs comprennent les statuts de redirections est détaillée dans l'article de référence cité ci-dessus) :
- 300 : Choix multiple : ce statut consiste à présenter au navigateur plusieurs représentations de la même ressource pour laisser à celui-ci le choix automatique de la représentation la plus adaptée à ses capacités ou à sa configuration. Ce statut ne répond pas à notre besoin.
- 301 : Redirection permanente : permet d'indiquer un déplacement définitif de la ressource. C'est ce statut qui est utilisé, par exemple, pour faire en sorte que toute requête vers http://www.dubourg.name/ soient redirigées vers le blog http://www.dubourg.name/s9y/. Ce code n'est pas utilisable, car la norme précise que le navigateur peut enregistrer la réponse du serveur de manière permanente et ainsi ne plus jamais effectuer la requête originale.
- 302 : Page non trouvée : la réponse présente un URL alternatif et le navigateur doit demander à l'Internaute s'il souhaite utiliser le nouveau URL. Ce code est théoriquement non-utilisable puisqu'il suppose une action de la part de l'utilisateur, mais dans la pratique, une erreur d'interprétation de la norme par tous les navigateurs, conduit à un fonctionnement automatique. Opéra qui suivait initialement la norme à du lui-même introduire ce bug dans son fonctionnement pour être compatible avec les autres navigateurs. 302 est donc un choix possible qui est même mieux reconnu que le code 303. Le code 302 dans son fonctionnement d'origine a été rebaptisé 307, dans l'espoir d'une meilleure implémentation par les navigateurs. 307 n'est donc pas utilisable pour notre problème
- 303 : aller vers un autre URL. C'est exactement pour régler notre problème que ce statut a été inventé. Pour entrer un peut dans le détail le navigateur qui effectue initialement un POST (méthode utilisée pour effectuer une modification d'une ressource - voir l'explication sur les méthodes associées aux requêtes), doit, lorsqu'il reçoit cette réponse, effectuer en suivant un GET (méthode utilisée pour visualiser une ressource). Ce code est moins bien supporté que le 302. wget ne le reconnaît pas par exemple.
Les autres codes ne présentent pas d'intérêt pour notre problème.
Il est donc possible d'utiliser la redirection HTTP. Mais supposons qu'en plus d'afficher la ressource, on veuille indiquer à l'utilisateur que l'opération s'est bien déroulée avec un message du type "Votre facture à été téléchargée". On ne peut pas faire cela avec cette méthode. On peut en revanche la compléter pour arriver à nos fins.
Solution 3 : utiliser les rédirections du protocole HTTP + des cookies
Utiliser des cookies consiste à enregistrer dans le navigateur des données. A chaque requête, le navigateur ré-émet ces données vers le serveur. On pourrait, tout en utilisant une redirection HTTP, utiliser un cookie pour passer le message de l'URL d'origine à l'URL de redirection.
Cette méthode présente quelques inconvénients
- les données du cookies ne peuvent pas dépasser 4000 caractères, ce qui peut être insuffisant dans certains cas;
- certains navigateur (Mozilla, Konqueror) n'ont qu'un environnement cookie par utilisateur : si on fait deux connexionx c1, c2 à la même application, c2 recevra les cookies émis par c1 et inversement. Très gênant si on veut faire deux choses à la fois dans la même application, par exemple, ouvrir 2 onglets qui affichent des données différentes.
- certains navigateurs (Internet explorer) gère bien des environnements cookies séparés, mais uniquement si on relance un navigateur mais pas si on ouvre une nouvelle fenêtre. Cela est source de confusion;
- l'utilisation des cookies viole REST voir le chapitre 6.3.4.2 de la thèse de Fielding;
- les fait que le cookies représente un état de la relation navigateur <-> serveur, état indépendant de l'état représenté par la page web elle-même peut entraîner des décorrélations lorsqu'on utilise le bouton "back" de l'historique du navigateur. Il faut évidemment chercher un exemple plus complexe que le simple passage de message entre pages pour illustrer ce problème.
Bref, l'utilisation de cookies présente bien des inconvénients dans le cadre de notre problème d'origine. Voir aussi :la RFC qui définit les cookies.
Solution 4 : utiliser la redrection HTTP + une session
Pour passer le message avec une redirection HTTP, on peut penser à utiliser un mécanisme de session. Cela consiste à stocker le message côté serveur.
Or pour identifier une session entre le navigateur et le serveur, on utilise :
- soit un cookie : dans ce cas là, on retrouve les problèmes cités pour les cookies dans la solution 3 (sauf le problème de taille maximale du cookie);
- soit un paramètre (jsession par exemple) : dans ce cas là, on n'est plus dans le problème initial. Rappel : on veut un URL qui soit un URI. L'introduction dans l'URL d'un &jsession=... ne permet pas de rester dans ce cadre.
Par ailleurs on est complètement en dehors de REST (on n'est plus dans un système sans état) et on est obliger de mettre en place un serveur de session, architecture technique en général complexe.
En résumé, on utilisera préférentiellement la redirection HTTP, si en navigant d'une étape à l'autre, on n'a pas besoin de passer une donnée à vie éphémère, comme par exemple un message de bon déroulement vers l'utilisateur.
Solution 5 : utilisation d'une redirection Javascript
Voici par exemple une fonction en C++ que l'on peut utiliser pour faire cette redirection (on suppose que le message s'il existe est contenu dans la variable error) :
void WEBCTRL::redirect(const char * url)
{
printf("<;!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
"<html><head></head><body>\n"
"<form id=\"redirform\" method=\"POST\" action=\">\n");
printf("%s", url);
printf("\">\n");
if( !error.is_empty() )
{
printf("<input name=\"MSG\" type=\"hidden\" value=\"");
printf("%s", (char *)error);
printf("\">\n");
}
printf("</form>\n"
"<script language=\"javascript\" type=\"text/javascript\">\n"
"function getObj(name)\n"
"{\n"
"if (parent.document.getElementById)\n"
"{\n"
"return document.getElementById(name);\n"
"}\n"
"else if (document.all)\n"
"{\n"
"return document.all[name];\n"
"}\n"
"else if (document.layers)\n"
"{\n"
"return document.layers[name];\n"
"}\n"
"};\n"
"getObj(\"redirform\").submit();\n"
"</script>\n");
printf("</body></html>");
error.clear();
}
Cette méthode fonction très bien. Son inconvénient principal est d'inscrire, pour une seule action, deux URL dans l'historique du navigateur.
Solution 6 : utiliser XmlHttpRequest
Attention : ceci n'est qu'une ébauche, c'est la seule solution que je n'ai pas testé.
L'idée est, lorsque l'on veut valider une modification, de donner la main à une fonction javascript qui effectue les opérations suivantes :
1. Envoi de la demande de modification, tout en restant dans la page d'origine grâce à un objet de type XmlHttpRequest.
2. Récupération de l'URL de redirection dans la réponse du serveur à la requête ci-dessous.
3. Demande au navigateur de l'affichage de la page correspondante
Cette méthode résout le problème de doublon dans l'historique. L'inconvénient principal est de rendre moins visible les interactions Navigateur <-> Serveur
Commentaires
Afficher les commentaires en Vue non groupée | Vue groupée
Sébastien sur :