web-dev-qa-db-fra.com

Golang. Quoi utiliser? http.ServeFile (..) ou http.FileServer (..)?

Je suis un peu confuse. La plupart des exemples montrent l'utilisation des deux fonctions: http.ServeFile(..) et http.FileServer(..), mais il semble que leurs fonctionnalités soient très proches. De plus, je n'ai trouvé aucune information sur la manière de définir un gestionnaire NotFound personnalisé. 

// This works and strip "/static/" fragment from path
fs := http.FileServer(http.Dir("static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))

// This works too, but "/static2/" fragment remains and need to be striped manually
http.HandleFunc("/static2/", func(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, r.URL.Path[1:])
})

http.ListenAndServe(":8080", nil)

J'ai essayé de lire le code source et les deux utilisent la fonction serveFile(ResponseWriter, *Request, FileSystem, string, bool) sous-jacente. Cependant, http.FileServer renvoie fileHandler avec sa propre méthode ServeHTTP() et effectue un travail de préparation avant de servir un fichier (par exemple, path.Clean ()).

Alors pourquoi avoir besoin de cette séparation? Quelle méthode préférable d'utiliser? Et comment définir un gestionnaire NotFound personnalisé, par exemple lorsque le fichier demandé n’est pas trouvé?

31

La principale différence est que http.FileServer effectue effectivement le mappage presque 1: 1 d'un préfixe HTTP avec un système de fichiers. En clair, cela sert un chemin de répertoire complet. et tous ses enfants.

Supposons que vous ayez un répertoire appelé /home/bob/static et que vous aviez cette configuration: 

fs := http.FileServer(http.Dir("/home/bob/static"))
http.Handle("/static/", http.StripPrefix("/static", fs))

Votre serveur accepterait des demandes, par exemple, /static/foo/bar et servir ce qui est au /home/bob/static/foo/bar (ou 404)

En revanche, ServeFile est un assistant de niveau inférieur qui peut être utilisé pour implémenter quelque chose de similaire à FileServer, ou pour implémenter votre propre chemin potentiellement en train de se transformer, et n'importe quel nombre de choses. Il prend simplement le fichier local nommé et l'envoie via la connexion HTTP. En soi, il ne servira pas un préfixe de répertoire entier (sauf si vous avez écrit un gestionnaire qui a fait une recherche similaire à FileServer)

NOTEServir naïvement un système de fichiers est une chose potentiellement dangereuse (il existe des moyens de sortir de l'arborescence enracinée). C'est pourquoi je recommande que, si vous vraiment savez ce que vous faites, utilisez http.FileServer et http.Dir car ils incluent des chèques pour s’assurer que les personnes ne peuvent pas sortir du service extérieur, ce que ServeFile ne fait pas.

Addendum Votre question secondaire, comment créer un gestionnaire NotFound personnalisé, ne répond malheureusement pas facilement. Comme cela a été appelé à partir de la fonction interne serveFile, comme vous l'avez remarqué, il n'y a pas d'endroit super facile pour percer dans cela. Il y a potentiellement des choses sournoises comme intercepter la réponse avec votre propre variable ResponseWriter qui intercepte le code de réponse 404, mais je vais vous laisser cet exercice.

57
Crast

Voici un gestionnaire qui envoie une redirection vers "/" si le fichier n’est pas trouvé. Cela est pratique lorsque vous ajoutez un repli pour une application angulaire, comme suggéré ici , qui est servi depuis un service golang.

Remarque: Ce code n'est pas prêt pour la production. Seulement illustratif (au mieux :-)

    package main

    import "net/http"

    type (
        // FallbackResponseWriter wraps an http.Requesthandler and surpresses
        // a 404 status code. In such case a given local file will be served.
        FallbackResponseWriter struct {
            WrappedResponseWriter http.ResponseWriter
            FileNotFound          bool
        }
    )

    // Header returns the header of the wrapped response writer
    func (frw *FallbackResponseWriter) Header() http.Header {
        return frw.WrappedResponseWriter.Header()
    }

    // Write sends bytes to wrapped response writer, in case of FileNotFound
    // It surpresses further writes (concealing the fact though)
    func (frw *FallbackResponseWriter) Write(b []byte) (int, error) {
        if frw.FileNotFound {
            return len(b), nil
        }
        return frw.WrappedResponseWriter.Write(b)
    }

    // WriteHeader sends statusCode to wrapped response writer
    func (frw *FallbackResponseWriter) WriteHeader(statusCode int) {
        Log.Printf("INFO: WriteHeader called with code %d\n", statusCode)
        if statusCode == http.StatusNotFound {
            Log.Printf("INFO: Setting FileNotFound flag\n")
            frw.FileNotFound = true
            return
        }
        frw.WrappedResponseWriter.WriteHeader(statusCode)
    }

    // AddFallbackHandler wraps the handler func in another handler func covering authentication
    func AddFallbackHandler(handler http.HandlerFunc, filename string) http.HandlerFunc {
        Log.Printf("INFO: Creating fallback handler")
        return func(w http.ResponseWriter, r *http.Request) {
            Log.Printf("INFO: Wrapping response writer in fallback response writer")
            frw := FallbackResponseWriter{
                WrappedResponseWriter: w,
                FileNotFound:          false,
            }
            handler(&frw, r)
            if frw.FileNotFound {
                Log.Printf("INFO: Serving fallback")
                http.Redirect(w, r, "/", http.StatusSeeOther)
            }
        }
    }

Il peut être ajouté comme dans cet exemple (en utilisant goji comme mux):

    mux.Handle(pat.Get("/*"),
        AddFallbackHandler(http.FileServer(http.Dir("./html")).ServeHTTP, "/"))

0
Oliver Koeth