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, например, если запрошенный файл не найден?
Ответы
Ответ 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, "/"))