Compression des retours HTTP

Introduction

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.

Le code du compresseur

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.

Exemple d’usage

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))

Remarque sur le serveur de contenu statique

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 :

Et avec l’app engine ?

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é.


Étiquettes :   compression 
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 »

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 »

Gérer les informations de session avec Gorilla

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 »

Commentaires

Liste des commentaires

DateNomCommentaire
12-12-2013 22:15 Bernard S. "Il suffit alors de remplacer la déclaration du handler HTTP:
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_
12-12-2013 22:35 Benjamin BALET Merci d'avoir lu l'article et d'avoir trouvé la boulette qui est corrigée.
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 :

  • Ajouter la variable du header HTTP "Content-Encoding" à "gzip".

  • Détecter le MIMETYPE originel (avant la compression).

  • Ajouter la variable du header HTTP "Content-Type" avec le MIMETYPE originel. Si on ne fixe pas le type de contenu, alors la plupart des navigateurs vont télécharger... un fichier zip et il ne vont pas l'afficher.


13-12-2013 09:38 Bernard S. Pour le test == inversé c'est une bonne habitude en C et assimilés mais avec Go le compilateur refuse une affectation dans une expression donc aucun risque d'erreur, et à mon avis c'est moins lisible... après les goûts et les couleurs...
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.
24-05-2014 19:08 Tristan C. Bonjour,

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 ?
24-05-2014 20:08 Tristan C. Bonjour Tristan,

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éthode Write.

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 typegzipResponseWriterimplémentegzip.Writer puisqu'il contient une méthode Write laquelle sera appelée par gzip.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...

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é)