Stocker les variables de session avec Gorilla

Dans cet article (le premier d'une longue série consacré au toolkit Gorilla), nous examinerons différentes stratégies pour gérer les variables de sessions ainsi que des exemples concrets de mise en oeuvre.

Quelle stratégie de stockage ?

La bibliothèque standard de golang ne contient pas de quoi gérer les variables de sessions côté serveur à l'aide d'un cookie. Il existe différents toolkits pour le web et écrits en go gérant ce problème. Nous utiliserons le toolkit Gorilla), mais les principes expliqués ici s'appliqueront, quel que soit votre choix technique.

Stockage des sessions dans un répertoire du serveur

Les variables de chaque session sont stockées dans un fichier sur le disque dur du serveur. Cela se rapproche du fonctionnement des sessions PHP par exemple. Un fichier sera créé pour chaque nouvelle session et il contiendra les variables de la session. Ce schéma illustre le principe de fonctionnement :

  • Stockage des variables de session sur le serveur

L'inconvénient de cette approche est son manque de montée en charge. Difficile dans ce cas d'implémenter un répartiteur de charge. En effet, si on ajoute un serveur supplémentaire, il faudra obligatoirement mettre en place un procédé technique permettant d'accéder aux variables de session quel que soit le serveur. Il n'est en outre pas possible d'utiliser directement cette technique dans le cas d'une application hébergée sur Google App Engine.

Stockage de la session dans un cookie

Le stockage des variables de sessions est assuré par le navigateur des visiteurs. Il est en fait stocké dans un cookie de session. Avec la majorité des framework go, vous n'avez pas à vous inquiéter, car le contenu de ce cookie est chiffré, ainsi personne ne peut facilement en modifier le contenu. Ce qui pourrait altérer le fonctionnement de l'application ou constituer une faille de sécurité.

  • Stockage des variables de session délégué au navigateur

Cette approche permet de monter en charge sans se soucier de l'emplacement du stockage des variables de sessions. On comprend que cette approche correspondrait mieux à une application hébergée sur Google App Engine. Bien entendu, il existe d'autres stratégies de stockage des variables de sessions. On pourrait les stocker dans la base de données, à l'aide de memcache ou utiliser cette solution spécifique à GAE.

Principe de fonctionnement

Recentrons la discussion sur les possibilités de base offertes par le toolkit Gorilla à l'aide d'exemples d'implémentation.

Installation

Installer la partie du toolkit qui gère les sessions (il contient également un routeur évolué et d'autres composants sympathiques) :

go get github.com/gorilla/sessions

Remarques importantes

Concernant le nom de la session

D'une manière générale, il ne doit pas y avoir d'espaces dans le nom des sessions. Je ne sais pas si c'est un bug ou si c'est intentionnel, mais dans ce cas, une session sera créée pour chaque affichage de page (on perdrait donc tout l'intérêt des variables de sessions).

Pensez à convertir les types

Il faut penser à convertir les variables de sessions. Par exemple, dans le cas d'une chaîne de caractères :

sessionFS.Values["variableFS"].(string)

Sous peine d'obtenir l'erreur suivante :

cannot use sessionFS.Values["variableFS"] (type interface {}) as type string in field value: need type assertion

Stockage des sessions dans un répertoire du serveur

Commencez par créer un répertoire sur le serveur. Les sessions seront stockées dans ce répertoire. Au moment de l'initialisation du magasin de type fichier, vous passerez le chemin de ce répertoire et un mot de passe servant à chiffrer les informations contenues dans ce fichier :

	var storeFS = sessions.NewFilesystemStore("sessions/", []byte("secret"))

On peut ensuite utiliser les variables de session contenues dans ce fichier :

func handlerVoirCookies(w http.ResponseWriter, r *http.Request) {
    sessionFS, _ := storeFS.Get(r, "sessionFS")
    sessionFS.Values["variableFS"] = "test d'une variable de session stockée dans le système de fichier"
    sessionFS.Save(r, w)	
	var p = Page{VarSessServer: sessionFS.Values["variableFS"].(string)}
	err := templates.ExecuteTemplate(w, "sessions.html", p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

Stockage de la session dans un cookie

Dans ce cas où vous confiez le stockage des données au navigateur, il n'y a rien à faire côté serveur si ce n'est l'initialisation du magasin idoine :

	var storeC = sessions.NewCookieStore([]byte("secret"))

On peut ensuite utiliser les variables de session qui sont, cette fois-çi, échangées avec le navigateur à chaque requête :

func handlerVoirCookies(w http.ResponseWriter, r *http.Request) {
	sessionC, _ := storeC.Get(r, "sessionCookie")
	sessionC.Values["variableC"] = "test d'une variable de session stockée dans un cookie"
	sessionC.Save(r, w)
	
	var p = Page{VarSessCookie: sessionC.Values["variableC"].(string)}
	err := templates.ExecuteTemplate(w, "sessions.html", p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

Les autres fonctionnalités

Messages flash

D'après la documentation (qui est ambiguë à cause de l'exemple de code), on pourrait penser qu'une fois lu, un message flash disparaît. Ce n'est pas forcément vrai. Si vous exécutez l'exemple de code fourni dans la documentation :

    session, _ := store.Get(r, "session-name")
    if flashes := session.Flashes(); len(flashes) > 0 {
        fmt.Fprint(w, "%v", flashes)
    }

Vous vous rendrez compte qu'avec cet exemple de code, les messages flahs (ajoutés avec session.AddFlash) s'accumulent sans fin. C'est parce que la méthode session.Flashes (qui lit tous les messages) n'efface pas les messages flash. Du coup, l'intérêt des messages devient discutable (sauf si ce comportement satisfait votre mélisation).

Pour mettre en place le mécanisme des messages qui disparaissent lorsqu'ils sont lus, il faut associer une clé lors de l'écriture du message :

	session.AddFlash("Salut, tu t'es fait redirigé!", "key")

Puis consommer le message associé à cette clé (rappel : dans notre exemple, nous ne manipulation que des chaînes de caractère, d'où le cast vers un type string) :

	messageFlash := session.Flashes("key")[0].(string)

Exemple de mise en oeuvre des messages flash

Le code que nous allons développer va répondre à ces deux adresses :

  1. http://localhost:9999/page : Ajoute un message flash et redirige vers /redirection.
  2. http://localhost:9999/redirection : Consomme le message flash et l'affiche.

Handler HTTP insérant un message flash

Voici le code du contrôleur qui ajoute un message flash et l'associe à une clé. Une erreur fréquente consiste à penser que la méthode session.AddFlash enregistrerait automatiquement la session. Ce qui est faux et pourrait provoquer la panique du runtime golang. Pensez à sauver la session, sinon les messages flash seront perdus!

func handlerPremier(w http.ResponseWriter, r *http.Request) {
	session, _ := storeFS.Get(r, "sessionFS")
	session.AddFlash("Salut, tu t'es fait redirigé!", "key")
	session.Save(r, w)
	http.Redirect(w, r, "/redirection", http.StatusMovedPermanently)
}

Handler HTTP consommant un message flash

Voici le code du contrôleur qui lit un message flash associé à une clé. Se faisant, il efface le message consommé de la liste des messages flash :

func handlerRedirection(w http.ResponseWriter, r *http.Request) {
	session, _ := storeFS.Get(r, "sessionFS")
	messageFlash := session.Flashes("key")[0].(string)

	var p = Page{VarFlash: messageFlash}
	err := templates.ExecuteTemplate(w, "sessions.html", p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

Options des cookies

Par défaut, les cookies de session durent un mois. Mais il est possible de configurer les sessions ou le magasin de manière à modifier cette durée ou d'autres paramètres à l'aide de session.Options ou store.Options. Les options sont un sous-ensemble du type Cookie. Par exemple, pour changer la durée de vie du cookie à une semaine, on écrirait :

session.Options = &sessions.Options{
    Path:   "/",
    MaxAge: 86400 * 7,
}

On note que:

  1. Si MaxAge = 0 détruit le cookie immédiatement.
  2. Si MaxAge< 0 détruit le cookie immédiatement.
  3. Si MaxAge > 0 Le cookie a une durée de vie exprimée en secondes.

Stockage de données complexes

On peut avoir besoin de stocker des types de données plus complexes dans les variables de sessions (une structure, par exemple). Il faut pour cela utiliser le package encoding/gob afin de sérializer ces données complexes. Voici un exemple complet commenté plus bas.

package main

import (
	"net/http"
	"html/template"
	"github.com/gorilla/sessions"
	"encoding/gob"
)

type Page struct {
	VarSessServer	string
	VarSessCookie	string
	VarFlash		string
}

type Personne struct {
    Prenom    string
    Nom     string
}

func init() {
    gob.Register(Personne{})
}

func main() {
	http.HandleFunc("/complexe", handlerComplexe)
	http.ListenAndServe(":9999", nil)
}

var templates = template.Must(template.ParseFiles("sessions.html"))
var storeFS = sessions.NewFilesystemStore("sessions/", []byte("secret"))

func handlerComplexe(w http.ResponseWriter, r *http.Request) {
    sessionFS, _ := storeFS.Get(r, "sessionFS")
	sessionFS.Values["Personne"] = Personne{Prenom: "Benjamin", Nom: "BALET"}
    sessionFS.Save(r, w)

	var p = Page{VarSessServer: sessionFS.Values["Personne"].(Personne).Prenom}
	err := templates.ExecuteTemplate(w, "sessions.html", p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

Quelques commentaires sur ce code :

  1. La fonction init permet d'enregistrer un nouveau type qui sera sérializable avec le package encoding/gob
  2. On peut maintenant stocker un type complexe dans une variable de session, ici sessionFS.Values["Personne"] = Personne{Prenom: "Benjamin", Nom: "BALET"}.
  3. Puis réaliser la désérialization, le cast et la lecture en une seule instruction : sessionFS.Values["Personne"].(Personne).Prenom.

La rotation des clés de chiffrement

On pourrait vouloir modifier les clés de chiffrement durant la durée de vie de l'application sans casser les sessions existantes. Le magasin CookieStore offre une fonctionnalité de rotation des clés. Pour l'utiliser il suffit de l'initialiser avec de multiples paires de clés d'authentification et de chiffrement. Ces paires seront testées dans le même ordre lors de la tentative d'accès au magasin.

var store = sessions.NewCookieStore(
    []byte("new-authentication-key"),
    []byte("new-encryption-key"),
    []byte("old-authentication-key"),
    []byte("old-encryption-key"),
)

Les nouvelles sessions seront chiffrées en utilisant la première paire. Les sessions plus anciennes pourront toujours être lues puisque la première tentative de déchiffrement aura échoué, mais que d'autres paires peuvent être essayées. Note: pour toutes les paires, la clé de chiffrement (encryption key) est optionnelle ; si vous passez un paramètre nil, elle sera ignorée.

Template HTML utilisé dans les exemples

Pour simplifier vos tests, vous trouverez ci-dessous un exemple de template :

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
	<title>Tests autour des variables de sessions</title>
  </head>
  <body>
	<ul>
		<li><b>Contenu de la variable de session stockée sur le serveur :</b> {{.VarSessServer}}</li>
		<li><b>Contenu de la variable de session stockée dans un cookie :</b> {{.VarSessCookie}}</li>
		<li><b>Contenu du flash :</b> {{.VarFlash}}</li>
	</ul>
  </body>
</html>

Étiquettes :   webapp   session   thirdparty 
Portrait de Benjamin BALET
Benjamin BALET
Consultant APM

Retrouvez mes cooordonées

Benjamin BALET sur viadeo






Vous aimerez aussi

Les lois de la réflexion

Une traduction du blog officiel de golang expliquant le mécanisme de la réflexion en Go.   Lire »

Gobs le format natif d'échange de données en Go

Traduction d'un article du blog officiel expliquant comment échanger des données entre deux programmes golang grâce à un format natif   Lire »

Canaux et go routines avec ou sans état

Exemples d'utilisation du type chan et des go routines stateful et stateless   Lire »

Comment gérer efficacement les erreurs en golang ?

Préconisations officielles pour la gestion des erreurs dans un programme golang. Cet article complète les explications sur panic, defer et recover   Lire »

Comment écrire du code Go ?

Traduction d'une partie des spécifications officielles du langage Go, cet article explique comment développer en Go.   Lire »

Commentaires

Soyez le premier à commenter cet article

Publier un commentaires

Tous les commentaires sont soumis à modération. Les balises HTML sont pour la plupart autorisées. Les liens contextuels et intéressants sont en follow.

(requis)
(requis)
(requis, mais non publié)
(recommandé si vous souhaitez être publié)