Phollow.me

Un simple serveur de fichiers statiques sous Golang

Mes premiers ‘ébats’ avec ce langage.

Depuis quelques semaines, je me suis mis à l’apprentissage de Go. Et pour accélérer cet apprentissage je me suis mis en tête de refaire mon serveur de fichiers statiques (celui qui sert les pages de ce blog). Il était fait en Ruby avec Sinatra, j’en parlais ici.

Première version

Go possède déjà pas mal de bibliothèques pour tout ce qui est networking et serveurs. Le module http possède une méthode FileServer qui peut-être utilisée en tant que serveur de fichiers statiques.

package main

import "net/http"

func main() {
    http.Handle("/", http.FileServer(http.Dir("_site")))
    err := http.ListenAndServe(":8000", nil)
    if err != nil {
        panic(err)
    }
}

Seconde version

La première version, aussi simple soit-elle, ne me suffisait pas. Le problème étant lié aux pages 404, Go servant la sienne. Je voulais pouvoir mettre une page customisée. La solution consiste à créer ses propres types et à les wrapper autour des natifs. (Ce n’est pas de l’héritage, Go n’étant pas un langage orienté objet. Ce qui est bien.)

Mon type errorHandle contient juste le type habituel, mais grâce à lui je peux faire ma propre méthode ServeHTTP qui elle utilisera mon type errorWriter. Il contient le type de base ResponseWriter avec en plus un boolean. Je set ce boolean à true quand je détecte un header 404 et je sers ma page customisée à la place.

package main

import (
    "os"
    "net/http"
    "html/template"
)

type errorWriter struct {
    http.ResponseWriter
    ignore bool
}

type errorHandle struct {
    http.Handler
}

func (h *errorHandle) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    h.Handler.ServeHTTP(&errorWriter{w, false}, r)
}

func (w *errorWriter) Header() (http.Header) {
    return w.ResponseWriter.Header()
}

func (w *errorWriter) Write(p []byte) (int, error) {
    if w.ignore {
        return len(p), nil
    }

    return w.ResponseWriter.Write(p)
}

func (w *errorWriter) WriteHeader(status int) {
   if status == 404 {
      w.ignore = true
      w.ResponseWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
      w.ResponseWriter.WriteHeader(404)
      t, _ := template.ParseFiles("_site/404.html")
      t.Execute(w.ResponseWriter, nil)
   } else {
      w.ResponseWriter.WriteHeader(status)
   }
}

func main() {
    fs := http.FileServer(http.Dir("_site"))
    http.Handle("/", &errorHandle{fs})
    err := http.ListenAndServe(":"+os.Getenv("PORT"), nil)
    if err != nil {
        panic(err)
    }
}

Ce bout de code est capable de tourner sous Heroku (et peut-être App Engine). J’ai 2, 3 autres articles sous le feu sur ce langage qui me plaît bien.