Подтвердить что ты не робот

Golang. Что использовать? http.ServeFile(..) или http.FileServer(..)?

Я немного смущен. Многие примеры показывают использование обоих: http.ServeFile(..) и http.FileServer(..), но, похоже, они имеют очень тесную функциональность. Также я не нашел информации о том, как установить пользовательский обработчик NotFound.

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

Я попытался прочитать исходный код, и оба из них используют serveFile(ResponseWriter, *Request, FileSystem, string, bool) базовую функцию. Однако http.FileServer верните fileHandler со своим собственным методом ServeHTTP() и выполните некоторую подготовительную работу перед подачей файла (например, path.Clean()).

Так зачем же это разделение? Какой метод лучше использовать? И как я могу настроить пользовательский обработчик NotFound, например, если запрошенный файл не найден?

4b9b3361

Ответ 1

Основное отличие состоит в том, что http.FileServer действительно эффективно сопоставляет 1:1 префикс HTTP с файловой системой. На простом английском языке он обслуживает весь путь к каталогу. и всех его детей.

Скажем, у вас есть каталог с именем /home/bob/static, и у вас была эта настройка:

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

Ваш сервер будет принимать запросы, например. /static/foo/bar и обслуживать все, что находится на /home/bob/static/foo/bar (или 404)

Напротив, ServeFile является помощником более низкого уровня, который может быть использован для реализации чего-то подобного FileServer или потенциально потенциально может использовать ваш собственный путь и любое количество вещей. Он просто берет именованный локальный файл и отправляет его по HTTP-соединению. Сам по себе он не будет служить целым префиксом каталога (если вы не написали обработчик, который сделал некоторый поиск, похожий на FileServer)

ПРИМЕЧАНИЕ Наименее полезная работа с файловой системой - потенциально опасная вещь (есть потенциальные способы выхода из корневого дерева), поэтому я рекомендую, чтобы, если вы действительно не знаете, что делаете, используйте http.FileServer и http.Dir, поскольку они включают проверки, чтобы убедиться, что люди не могут вырваться из FS, что ServeFile не делает.

Добавление К сожалению, ваш вторичный вопрос, как вы делаете пользовательский обработчик NotFound, нелегко ответить. Потому что это вызвано из внутренней функции ServeFile, как вы заметили, там нет супер легкого места для взлома. Есть потенциально некоторые подлые вещи, такие как перехват ответа с помощью вашего собственного ResponseWriter, который перехватывает код ответа 404, но я оставлю это упражнение вам.

Ответ 2

Здесь обработчик, который отправляет перенаправление на "/", если файл не найден. Это очень удобно при добавлении запасного варианта для приложения Angular, как предлагается здесь, который подается из службы golang.

Примечание. Этот код не готов к работе. Только иллюстративно (в лучшем случае :-)

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

Его можно добавить, как в этом примере (используя goji как mux):

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