
Templates et rendu côté serveur
Maîtrisez les templates Go pour le rendu côté serveur. Découvrez syntaxe, parsing, exécution, data binding, layouts, fonctions personnalisées et bonnes pratiques pour des applications web dynamiques.
Introduction aux templates Go et au rendu côté serveur : HTML dynamique et interactif
Dans le développement web, le rendu côté serveur (server-side rendering - SSR) est une technique fondamentale pour générer dynamiquement le contenu HTML des pages web directement sur le serveur, avant de l'envoyer au navigateur client. Go, avec ses packages text/template et html/template, offre des outils puissants et intégrés pour réaliser le rendu côté serveur en utilisant des templates Go. Les templates Go permettent de combiner du HTML statique avec du code Go dynamique, créant ainsi des pages web interactives et personnalisées.
Imaginez un template Go comme un modèle ou un patron de page web HTML, contenant des placeholders (espaces réservés) qui seront remplis dynamiquement avec des données provenant de votre application Go. Le moteur de template Go (template engine) prend ce template et ces données en entrée, et produit en sortie un document HTML complet et personnalisé, prêt à être envoyé au navigateur client. Le rendu côté serveur avec les templates Go permet de générer des pages web dynamiques, d'afficher des données variables, de gérer la logique de présentation, et de séparer clairement la logique métier (code Go) de la présentation (HTML).
Ce chapitre vous propose un guide complet sur les templates Go et le rendu côté serveur. Nous allons explorer en détail la syntaxe des templates Go, comment parser et exécuter des templates, comment passer des données aux templates (data binding), comment utiliser les layouts et les templates partiels, comment définir des fonctions personnalisées pour les templates, et les bonnes pratiques pour construire des applications web dynamiques et performantes avec les templates Go. Que vous souhaitiez créer des pages web HTML classiques, des interfaces utilisateur web interactives, ou des applications web complètes avec rendu côté serveur, ce guide vous fournira les bases nécessaires pour maîtriser les templates Go et le rendu côté serveur.
Syntaxe de base des templates Go : Actions, pipelines et data binding
La syntaxe des templates Go est basée sur un mélange de HTML statique et de actions (délimitées par des doubles accolades {{ ... }}) qui permettent d'insérer du code Go dynamique dans le template. Les actions permettent d'afficher des données, d'exécuter des structures de contrôle (boucles, conditions), d'appeler des fonctions, et de réaliser d'autres opérations dynamiques dans le template.
Eléments de syntaxe de base des templates Go :
- Texte brut (HTML statique) : Tout texte en dehors des actions
{{ ... }}est considéré comme du HTML statique et est reproduit tel quel dans la sortie du template. - Actions
{{ ... }}: Délimitent le code Go dynamique à exécuter dans le template. Les actions peuvent être de différents types :- Affichage de valeurs :
{{ .NomVariable }}: Affiche la valeur d'une variable (champ du struct de données, variable locale du template, etc.). Le point.(dot) représente le contexte de données courant (par défaut, le struct de données passé à l'exécution du template). - Pipelines :
{{ .NomVariable | Fonction1 | Fonction2 ... }}: Permettent de chaîner des fonctions (ou des méthodes) pour transformer ou manipuler les données avant de les afficher. La sortie de la fonction précédente devient l'entrée de la fonction suivante dans le pipeline. - Structures de contrôle :
{{ if ... }} {{ else }} {{ end }},{{ range ... }} {{ end }},{{ with ... }} {{ end }}: Permettent d'ajouter de la logique conditionnelle (if/else) et des boucles (range) dans le template, pour afficher du contenu dynamiquement en fonction des données. - Commentaires :
{{/* commentaire */}}: Permettent d'ajouter des commentaires dans le template (ignorés lors de l'exécution). - Définition de variables :
{{ $nomVariable := .NomChamp }}: Permet de définir des variables locales dans le template pour simplifier l'accès aux données ou pour stocker des résultats intermédiaires. - Appel de fonctions :
{{ Fonction "argument" .NomVariable }}: Permet d'appeler des fonctions Go (fonctions prédéfinies des templates Go ou fonctions personnalisées que vous fournissez) dans le template.
- Affichage de valeurs :
- Data Binding (liaison de données) : Le contexte de données
Le data binding (liaison de données) est le mécanisme qui permet de passer des données de votre application Go vers le template, afin que le template puisse afficher ces données dynamiquement. Lors de l'exécution d'un template, vous passez généralement un struct de données Go comme contexte de données au template. A l'intérieur du template, vous accédez aux champs de ce struct de données en utilisant le point . (dot) suivi du nom du champ (par exemple, {{ .NomUtilisateur }} pour accéder au champ NomUtilisateur du struct de données). Vous pouvez également utiliser des pipelines et des structures de contrôle pour manipuler et afficher ces données de manière dynamique.
Exemple de syntaxe de base des templates Go :
package main
import (
"html/template"
"net/http"
"log"
)
// Structure de données à passer au template
type PageAccueil struct {
Titre string
Message string
Utilisateur string
Date string
}
func handlerAccueil(w http.ResponseWriter, r *http.Request) {
// Données dynamiques pour le template
data := PageAccueil{
Titre: "Page d'accueil",
Message: "Bienvenue sur mon site web Go !",
Utilisateur: "John Doe",
Date: "2024-01-25",
}
// Parsing du template depuis une chaîne littérale (pour cet exemple)
tmpl, err := template.New("accueil").Parse(
`
{{ .Titre }}
{{ .Message }}
Bonjour, {{ .Utilisateur }} !
Date du jour : {{ .Date }}
`,
)
if err != nil {
http.Error(w, "Erreur serveur", http.StatusInternalServerError)
log.Println("Erreur parsing template:", err)
return
}
// Exécution du template en passant les données et écriture de la sortie dans http.ResponseWriter
err = tmpl.Execute(w, data)
if err != nil {
http.Error(w, "Erreur serveur", http.StatusInternalServerError)
log.Println("Erreur execution template:", err)
}
}
func main() {
http.HandleFunc("/", handlerAccueil)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Dans cet exemple, le template Go utilise des actions {{ .Titre }}, {{ .Message }}, {{ .Utilisateur }}, {{ .Date }} pour afficher les valeurs des champs correspondants du struct PageAccueil passé comme contexte de données lors de l'exécution du template (tmpl.Execute(w, data)).
Parsing et exécution de templates : Charger et rendre les templates HTML
Pour utiliser les templates Go dans vos applications web, vous devez passer par deux étapes principales : le parsing (analyse et compilation du template) et l'exécution (rendu du template avec des données). Go propose des fonctions et des mécanismes pour charger, parser, mettre en cache et exécuter efficacement les templates HTML.
Parsing de templates : template.ParseFiles et template.ParseGlob
Le package html/template (et text/template) fournit des fonctions pour parser les templates et les transformer en structures Go prêtes à être exécutées. Les fonctions de parsing les plus couramment utilisées sont :
template.ParseFiles(filenames ...string) (*Template, error): Parse un ou plusieurs fichiers templates spécifiés par leur nom de fichier. Retourne un*template.Templatereprésentant le template parsé (ou un ensemble de templates si plusieurs fichiers sont spécifiés), et une erreurerroren cas d'échec du parsing. Si plusieurs fichiers sont spécifiés, le template retourné sera le premier template rencontré (généralement le premier fichier spécifié).template.ParseGlob(pattern string) (*Template, error): Parse tous les fichiers templates correspondant à un pattern glob (motif de correspondance de fichiers, comme"templates/*.html"). Retourne un*template.Templatereprésentant le premier template rencontré (par ordre alphabétique des noms de fichiers), et une erreurerroren cas d'échec du parsing.ParseGlobest utile pour charger tous les templates d'un répertoire en une seule opération.
Exécution de templates : template.Execute et template.ExecuteTemplate
Une fois qu'un template est parsé (et représenté par un *template.Template), vous pouvez l'exécuter pour générer la sortie HTML en combinant le template avec des données. Les fonctions d'exécution les plus couramment utilisées sont :
template.Execute(wr io.Writer, data interface{}) error: Exécute le template (*template.Template) et écrit la sortie HTML générée dans un writerio.Writer(par exemple, unhttp.ResponseWriterpour envoyer la réponse HTTP au client). Prend en arguments un writerio.Writeret un contexte de donnéesdata interface{}(les données à passer au template). Retourne une erreurerroren cas d'échec de l'exécution.template.ExecuteTemplate(wr io.Writer, name string, data interface{}) error: Exécute un template spécifique (identifié par son nomname) au sein d'un ensemble de templates parsés (*template.Template) et écrit la sortie HTML générée dans un writerio.Writer. Permet de choisir un template spécifique à exécuter parmi un ensemble de templates parsés (par exemple, lors de l'utilisation de layouts et de templates partiels).
Exemple de parsing et d'exécution de templates depuis des fichiers :
1. Structure du projet (fichiers templates dans un répertoire templates) :
monprojet/
├── main.go
└── templates/
├── accueil.html
└── layout.html
2. Fichier template templates/accueil.html (template de contenu) :
{{define "content"}}
{{ .Message }}
Bonjour, {{ .Utilisateur }} !
Date du jour : {{ .Date }}
{{end}}
3. Fichier template templates/layout.html (template de layout) :
{{ .Titre }}
...
{{ template "content" . }}
4. Code Go (parsing des templates depuis les fichiers et exécution) :
package main
import (
"html/template"
"net/http"
"log"
)
// ... (Struct PageAccueil comme dans l'exemple précédent) ...
var templates *template.Template // Variable globale pour stocker les templates parsés (mis en cache)
func init() {
// Parsing des templates au démarrage de l'application (une seule fois)
templates = template.Must(template.ParseGlob("templates/*.html")) // Parse tous les fichiers *.html du répertoire 'templates'
}
func handlerAccueil(w http.ResponseWriter, r *http.Request) {
data := PageAccueil{ /* ... données ... */ }
// Exécution du template 'layout.html' en spécifiant le template de contenu 'accueil.html'
err := templates.ExecuteTemplate(w, "layout.html", data) // Exécution de 'layout.html', qui inclut 'accueil.html'
if err != nil {
http.Error(w, "Erreur serveur", http.StatusInternalServerError)
log.Println("Erreur execution template:", err)
}
}
func main() {
http.HandleFunc("/", handlerAccueil)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Cet exemple illustre le parsing des templates HTML depuis des fichiers (avec template.ParseGlob et template.ParseFiles), la mise en cache des templates parsés dans une variable globale (pour éviter de reparser les templates à chaque requête), et l'exécution d'un template spécifique (layout.html) en incluant un template de contenu (accueil.html) via l'action {{ template "content" . }}. Le parsing des templates est généralement effectué une seule fois au démarrage de l'application (dans la fonction init), et les templates parsés sont ensuite réutilisés pour traiter chaque requête, optimisant ainsi la performance du rendu côté serveur.
Layouts et templates partiels : Modularité et réutilisation des templates
Pour construire des applications web avec des interfaces utilisateur cohérentes et pour éviter la duplication de code HTML dans vos templates Go, l'utilisation de layouts et de templates partiels est une pratique essentielle. Les layouts et les templates partiels permettent de modulariser et de réutiliser des portions de code HTML communes à plusieurs pages web, facilitant la maintenance, la cohérence et la réutilisabilité des templates.
Layouts (templates de mise en page) : Structure HTML globale réutilisable
Un layout (template de mise en page) est un template HTML qui définit la structure HTML globale d'une page web (, , , , , , , etc.) et qui contient un ou plusieurs points d'insertion (placeholders) où le contenu spécifique de chaque page (les templates de contenu) sera inséré dynamiquement.
Dans l'exemple précédent, le fichier templates/layout.html est un template de layout. L'action {{ template "content" . }} dans le layout.html est un point d'insertion où le template de contenu templates/accueil.html (nommé "content" via {{define "content"}}) sera inséré lors de l'exécution du template layout.html.
Templates partiels (templates réutilisables) : Fragments HTML réutilisables
Les templates partiels (partial templates) sont des fragments HTML réutilisables qui peuvent être inclus dans d'autres templates (layouts ou templates de contenu). Les templates partiels permettent de factoriser et de réutiliser des portions de code HTML communes à plusieurs templates (headers, footers, barres de navigation, formulaires, composants UI, etc.), évitant ainsi la duplication de code et améliorant la maintenabilité.
Pour définir un template partiel, vous utilisez également l'action {{define "nomDuTemplate"}} ... {{end}}, en choisissant un nom pour le template partiel ("nomDuTemplate"). Pour inclure un template partiel dans un autre template, vous utilisez l'action {{ template "nomDuTemplate" . }}.
Exemple de Layout et de Templates Partiels :
1. Fichier template templates/layout.html (layout, inchangé par rapport à l'exemple précédent)
2. Fichier template templates/accueil.html (template de contenu, inchangé)
3. Fichier template templates/header.html (template partiel pour le header) :
4. Fichier template templates/footer.html (template partiel pour le footer) :
5. Fichier template templates/produits.html (autre template de contenu utilisant le layout et les partiels) :
{{define "content"}}
Liste des produits
{{ range .Produits }}
- {{ .Nom }} - {{ .Prix }} €
{{ end }}
{{end}}
6. Code Go (parsing des templates et utilisation du layout et des partiels) :
package main
import (
"html/template"
"net/http"
"log"
"time"
)
// ... (Struct PageAccueil comme précédemment) ...
// Structure de données pour la page Produits
type PageProduits struct {
Titre string
Produits []struct {
Nom string
Prix float64
}
Annee int // Pour le footer (template partiel)
}
var templates *template.Template // Variable globale pour les templates parsés
func init() {
// Parsing de tous les templates du répertoire 'templates' (layout, accueil, header, footer, produits)
templates = template.Must(template.ParseGlob("templates/*.html"))
}
func handlerAccueil(w http.ResponseWriter, r *http.Request) {
data := PageAccueil{/* ... */}
err := templates.ExecuteTemplate(w, "layout.html", data) // Exécution du layout 'layout.html' pour la page d'accueil
/* ... */
}
func handlerProduits(w http.ResponseWriter, r *http.Request) {
data := PageProduits{
Titre: "Page Produits",
Produits: []struct {
Nom string
Prix float64
}{
{"Produit A", 19.99},
{"Produit B", 49.99},
{"Produit C", 99.99},
},
Annee: time.Now().Year(), // Donnée pour le footer (template partiel)
}
err := templates.ExecuteTemplate(w, "layout.html", data) // Exécution du layout 'layout.html' pour la page produits
/* ... */
}
func main() {
http.HandleFunc("/", handlerAccueil)
http.HandleFunc("/produits", handlerProduits)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Dans cet exemple, nous utilisons un layout layout.html pour la structure HTML globale, et des templates partiels header.html et footer.html pour les parties communes de l'interface utilisateur. Les templates de contenu (accueil.html, produits.html) sont insérés dans le layout via l'action {{ template "content" . }}. Cela permet de factoriser le code HTML commun, de maintenir une interface utilisateur cohérente, et de simplifier la gestion des templates dans les applications web Go.
Fonctions personnalisées dans les templates : Etendre les capacités
Les templates Go offrent la possibilité d'étendre leurs fonctionnalités en définissant et en utilisant des fonctions personnalisées (template functions). Les fonctions personnalisées vous permettent d'ajouter une logique de traitement spécifique, de formater des données, d'effectuer des opérations complexes, ou d'accéder à des données externes directement depuis vos templates Go.
Définition d'un FuncMap (mapping de fonctions personnalisées) :
Pour utiliser des fonctions personnalisées dans vos templates Go, vous devez créer un template.FuncMap, un type Go qui est un mapping (map) entre les noms des fonctions (strings, qui seront utilisés pour appeler les fonctions dans les templates) et les fonctions Go correspondantes (avec la signature interface{}).
var fonctionsPerso template.FuncMap = template.FuncMap{
"formatDate": formatDateFonction,
"tronquerTexte": tronquerTexteFonction,
// ... autres fonctions personnalisées ...
}
Fonctions personnalisées : Signature et implémentation
Les fonctions Go que vous utilisez comme fonctions personnalisées dans les templates doivent avoir une signature compatible avec template.FuncMap (retourner une seule valeur interface{} ou plusieurs valeurs dont la dernière est une error). Vous pouvez définir vos propres fonctions Go avec la logique souhaitée.
Ajout du FuncMap lors du parsing du template : template.New(...).Funcs(FuncMap).Parse(...)
Pour associer votre template.FuncMap de fonctions personnalisées à un template, vous devez appeler la méthode Funcs(FuncMap) lors du parsing du template, avant d'appeler Parse, ParseFiles, ou ParseGlob.
templates = template.Must(template.New("layout.html").Funcs(fonctionsPerso).ParseFiles("templates/*.html"))
Appel des fonctions personnalisées dans les templates :
Une fois que vous avez enregistré votre FuncMap lors du parsing, vous pouvez appeler les fonctions personnalisées dans vos templates en utilisant leur nom (la clé que vous avez définie dans le FuncMap) comme une action de template, en les intégrant dans des pipelines ou en les appelant directement.
{{ .DateCreation | formatDate "Janvier 2006" }} // Appel de la fonction personnalisée 'formatDate' avec la valeur '.DateCreation' comme argument
{{ if tronquerTexte .Description 100 }} ... {{ end }} // Appel de la fonction personnalisée 'tronquerTexte' dans une structure conditionnelle
Exemple d'utilisation de fonctions personnalisées dans les templates :
package main
import (
"fmt"
"html/template"
"log"
"net/http"
"strings"
"time"
)
// ... (Struct PageAccueil comme précédemment) ...
// Fonction personnalisée pour formater une date
func formatDateFonction(t time.Time, format string) string {
return t.Format(format)
}
// Fonction personnalisée pour tronquer un texte
func tronquerTexteFonction(texte string, longueurMax int) string {
if len(texte) <= longueurMax {
return texte
}
return texte[:longueurMax] + "..."
}
var fonctionsPerso template.FuncMap = template.FuncMap{
"formatDate": formatDateFonction,
"tronquerTexte": tronquerTexteFonction,
}
var templates *template.Template
func init() {
templates = template.Must(template.New("layout.html").Funcs(fonctionsPerso).ParseGlob("templates/*.html")) // Ajout de Funcs() lors du parsing
}
func handlerAccueil(w http.ResponseWriter, r *http.Request) {
data := PageAccueil{
Titre: "Page d'accueil avec fonctions perso",
Message: "Bienvenue sur mon site web Go avec fonctions personnalisées dans les templates !",
Utilisateur: "Jane Doe",
Date: time.Now().Format(time.RFC3339), // Date au format RFC3339
}
err := templates.ExecuteTemplate(w, "layout.html", data)
/* ... */
}
// ... (handlerProduits et main inchangés par rapport à l'exemple précédent) ...
Dans cet exemple, nous définissons un FuncMap fonctionsPerso contenant deux fonctions personnalisées : formatDate (pour formater une date) et tronquerTexte (pour tronquer un texte). Nous associons ce FuncMap aux templates lors du parsing avec .Funcs(fonctionsPerso). Dans le template templates/accueil.html (non montré ici, mais similaire à l'exemple précédent), nous pouvons maintenant appeler ces fonctions personnalisées via les actions {{ .Date | formatDate "Janvier 2006" }} et {{ tronquerTexte .Description 100 }}, étendant ainsi les capacités des templates Go avec notre propre logique de traitement et de formattage des données.
Bonnes pratiques pour l'utilisation des templates Go et le rendu côté serveur
Pour utiliser efficacement les templates Go et le rendu côté serveur dans vos applications web, et écrire du code de qualité, voici quelques bonnes pratiques à suivre :
- Séparer clairement la logique métier (Go) de la présentation (templates) : Respectez le principe de séparation des préoccupations (separation of concerns) en séparant clairement la logique métier de votre application (gestion des données, logique applicative, etc., en Go) de la présentation (génération du HTML, affichage des données, mise en page, dans les templates). Les templates doivent se concentrer uniquement sur la présentation et l'affichage des données, et ne pas contenir de logique métier complexe.
- Utiliser des layouts et des templates partiels pour la réutilisabilité : Exploitez pleinement les layouts et les templates partiels pour modulariser et réutiliser le code HTML commun à plusieurs pages web (structure globale, headers, footers, composants UI, etc.). Les layouts et les templates partiels améliorent la cohérence de l'interface utilisateur, réduisent la duplication de code, et facilitent la maintenance des templates.
- Parser les templates une seule fois au démarrage de l'application et les mettre en cache : Effectuez le parsing des templates HTML une seule fois au démarrage de votre application (généralement dans la fonction
init) et stockez les templates parsés dans une variable globale (ou un cache). La reparsage des templates à chaque requête est inefficace et inutile. La mise en cache des templates parsés améliore significativement la performance du rendu côté serveur. - Utiliser des fonctions personnalisées avec parcimonie et à bon escient : Les fonctions personnalisées étendent les capacités des templates Go, mais utilisez-les avec parcimonie et uniquement lorsque cela est réellement nécessaire pour des opérations de formattage ou de logique de présentation spécifiques au template. Evitez de surcharger les templates avec trop de logique complexe ou de code métier, car cela nuirait à la clarté et à la maintenabilité des templates.
- Protéger contre les failles de sécurité (XSS) avec
html/template(auto-échappement) : Utilisez le packagehtml/template(et nontext/template) pour parser et exécuter les templates HTML.html/templateoffre une protection intégrée contre les failles de sécurité XSS (Cross-Site Scripting) en échappant automatiquement les données affichées dans les templates (par défaut). Echappez manuellement les données HTML non fiables uniquement si vous avez une raison spécifique de ne pas utiliser l'échappement automatique, et assurez-vous de le faire correctement pour éviter les vulnérabilités XSS. - Valider et échapper les données utilisateur avant de les afficher dans les templates : Avant d'afficher des données utilisateur (provenant des requêtes client, des bases de données, etc.) dans vos templates, validez et échappez ces données pour vous protéger contre les injections de code malveillant (XSS, injections SQL, etc.). Go offre des fonctions pour l'échappement HTML (
html.EscapeString,template.HTMLEscapeString) et d'autres types d'échappement (URL, JavaScript, CSS). - Tester rigoureusement vos templates et votre rendu côté serveur : Testez rigoureusement vos templates Go et votre logique de rendu côté serveur avec des tests unitaires et des tests d'intégration. Vérifiez que les templates sont correctement parsés et exécutés, que les données sont affichées correctement, que les fonctions personnalisées fonctionnent comme prévu, et que la gestion des erreurs est robuste. Testez les cas nominaux et les cas d'erreur, et assurez-vous que vos templates sont protégés contre les failles de sécurité (XSS).
En appliquant ces bonnes pratiques, vous utiliserez les templates Go et le rendu côté serveur de manière efficace, sûre et idiomatique, en construisant des applications web dynamiques, performantes, maintenables et agréables à utiliser.