La compression des flux http ( http://en.wikipedia.org/wiki/HTTP_compression ) permet d’améliorer la performance ressentie des utilisateurs de vos applications web écrites en golang. La compression réduit en effet les volumes échangés et donc les temps de téléchargement des réponses du serveur. C’est important vu le tournant que prennent les applications. Elles s’orientent en effet, vers des applications « lourdes » ou la partie MVC est faite sur le navigateur et le serveur ne sert principalement qu’à fournir de la donnée via des flux JSON ou XML. C’est le cas avec les frameworks javascript Angular, Backbone ou encore Dojo. Bien que l’on puisse mener une réflexion sur le contenu des données échangées (par exemple en passant du XML au JSON ou appliquant une transposition des données), comprimer ces flux textes de plus en plus volumineux permet d’améliorer la réactivité de vos applications.
Voici un code go permettant de compresser le retour d’un handler HTTP :
func makeHandler(fn func(http.ResponseWriter, *http.Request)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { fn(w, r) return } w.Header().Set("Content-Encoding", "gzip") gz := gzip.NewWriter(w) defer gz.Close() gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w} fn(gzr, r) } } type gzipResponseWriter struct { io.Writer http.ResponseWriter } func (w gzipResponseWriter) Write(b []byte) (int, error) { if "" == w.Header().Get("Content-Type") { // If no content type, apply sniffing algorithm to un-gzipped body. w.Header().Set("Content-Type", http.DetectContentType(b)) } return w.Writer.Write(b) }
Il y a un garde-fou dans le code du compresseur : il teste si le développeur a oublié de préciser le type de contenu – dans le handler – et le détecte le cas échéant. Pensez donc à préciser le type de contenu – dans le contrôleur – afin d’accélérer le code, par exemple :
w.Header().Set("Content-Type", "text/html; charset=utf-8")
Pourquoi un tel mécanisme ? Parce que si le type de contenu n’est pas précisé, le navigateur n’affichera pas la page et téléchargera un fichier ZIP.
Il suffit alors de remplacer la déclaration du handler HTTP:
http.HandleFunc("/home",homeHandler)
Par celle-ci afin d'activer la compression:
http.HandleFunc("/home", makeHandler(homeHandler))
Cette méthode peut être employée avec des toolkits tels que Gorilla :
r := mux.NewRouter() r.HandleFunc("/home", makeHandler(homeHandler))
Si vous utilisez le serveur de contenu statique :
http.Handle("/", http.FileServer(http.Dir("static/")))
Sachez qu’il ne compresse aucun de ses flux de retour. Cela serait un peu compliqué. Car compresser toutes les ressources statiques serait contre-productif. En effet, la compression d’une ressource déjà compressée (comme certaines images ou des fichiers déjà compressés) produit souvent un résultat plus volumineux que l’original.
Il faudrait aussi laisser au développeur la liberté de choisir les types de ressource qu’il souhaite compresser (par exemple les fichiers JavaScript, le texte, etc.).
Libre à vous de développer votre propre gestionnaire des ressources statiques en vous inspirant du code du handle FileServer ou en mettant en place un proxy tel que HAPROXY and NGNIX (NGNIX est préférable dans le cas d’une répartition de charge). Il existe plusieurs projets Go de Proxy et même falcore qui permet de développer un serveur HTTP plus modulaire que celui de la bibliothèque standard :
Par défaut, toutes les ressources statiques de type texte sont compressées. Pour compresser un flux de retour de votre application, il faut préciser le type de contenu (dans chacune des fonctions concernées) avec la fonction suivante (à adapter au MIMETYPE que vous retournez) :
w.Header().Set("Content-Type", "text/html; charset=utf-8")
Le contenu avec un MIMETYPE de type texte sera compressé.
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 »
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 »
La bibliothèque standard de go ne gère pas les variables de session d'une application. Il existe une solution avec le toolkit Gorilla Lire »
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.
http.HandleFunc("/home", makeHandler(homeHandler))"
Je pense que tu voulais écrire : http.HandleFunc("/home", homeHandler)
Enfin j'ai regardé un peu le code par curiosité et j'ai apporté quelques modifications.
http://play.golang.org/p/7hmjl-e3T_
Par rapport à ton code (http://play.golang.org/p/7hmjl-e3T_) :
C'est vrai que les "bonnes habitudes" ont la vie dure et que l'on ne risque pas d'affecter un get :)
Cependant, la détection du type de contenu n'est pas hors-sujet, car voici la séquence effectuée par le compresseur :
En effet pour le content-type ça m'avait échappé car dans mon framework il est systématiquement mis.
Enfin pour info j'utilise Go en production depuis la version 1.0 et c'est très solide.
Autant vous prévenir, je suis débutant sur Go.
Je n'arrive pas à comprendre de quelle manière
func (w gzipResponseWriter) Write(b []byte) (int, error)
est appelé...Pourriez-vous m'éclairer ?
Go est un langage objet, mais dont l'implémentation ne ressemble pas au C++ ou à Java. Les concepteurs du language parlent de « composition ». En jetant un coup d'œil à la déclaration :
func (w gzipResponseWriter) Write(b []byte) (int, error) {
On se rend compte qu'il ne s'agit pas d'une déclaration de fonction habituelle (sinon on aurait écrit
func Write(b []byte) (int, error) {
. En fait, on compose un objet en étendant les possibilités du typegzipResponseWriter
qui a maintenant une méthodeWrite
.D'un autre côté, le compresseur gzip de la bibliothèque standard contient ce que l'on appelle une interface, par exemple :
type Writer interface {
Write(p []byte) (n int, err error)
}
Et le type
gzipResponseWriter
implémentegzip.Writer
puisqu'il contient une méthodeWrite
laquelle sera appelée pargzip.Writer
.Pour plus d'information, tu pourrais lire le paragraphe Types et interfaces de cet article : Les lois de la réflexion. Bien que cela nécessiterait un article plus long...