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.
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.
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.
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.
Recentrons la discussion sur les possibilités de base offertes par le toolkit Gorilla à l'aide d'exemples d'implémentation.
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
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).
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
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) } }
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) } }
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)
Le code que nous allons développer va répondre à ces deux adresses :
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) }
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) } }
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:
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 :
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.
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>
Une traduction du blog officiel de golang expliquant le mécanisme de la réflexion en Go. Lire »
Traduction d'un article du blog officiel expliquant comment échanger des données entre deux programmes golang grâce à un format natif Lire »
Exemples d'utilisation du type chan et des go routines stateful et stateless Lire »
Préconisations officielles pour la gestion des erreurs dans un programme golang. Cet article complète les explications sur panic, defer et recover Lire »
Traduction d'une partie des spécifications officielles du langage Go, cet article explique comment développer en Go. Lire »
Soyez le premier à commenter cet article
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.