63 lines
2.1 KiB
Go
63 lines
2.1 KiB
Go
package api
|
||
|
||
import (
|
||
"net/http"
|
||
"os"
|
||
"path"
|
||
"path/filepath"
|
||
"strings"
|
||
)
|
||
|
||
// mediaHandler раздаёт /media/… из dir. Браузеру, который принимает WebP, вместо
|
||
// .jpg/.jpeg/.png отдаётся готовая копия «имя.jpg.webp» рядом с оригиналом, если она есть:
|
||
// пути в базе не меняются, а картинки весят в разы меньше. Копии делает
|
||
// migrations/import/make_webp.py; для новой картинки без копии отдаётся оригинал.
|
||
func mediaHandler(dir string) http.Handler {
|
||
files := http.StripPrefix("/media/", http.FileServer(noDirFS{http.Dir(dir)}))
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
switch strings.ToLower(path.Ext(r.URL.Path)) {
|
||
case ".jpg", ".jpeg", ".png":
|
||
default:
|
||
files.ServeHTTP(w, r)
|
||
return
|
||
}
|
||
w.Header().Add("Vary", "Accept")
|
||
if !strings.Contains(r.Header.Get("Accept"), "image/webp") {
|
||
files.ServeHTTP(w, r)
|
||
return
|
||
}
|
||
// path.Clean от «/…» не выпускает за пределы dir
|
||
rel := strings.TrimPrefix(path.Clean(r.URL.Path), "/media/")
|
||
f, err := os.Open(filepath.Join(dir, filepath.FromSlash(rel)) + ".webp")
|
||
if err != nil {
|
||
files.ServeHTTP(w, r)
|
||
return
|
||
}
|
||
defer f.Close()
|
||
st, err := f.Stat()
|
||
if err != nil || st.IsDir() {
|
||
files.ServeHTTP(w, r)
|
||
return
|
||
}
|
||
w.Header().Set("Content-Type", "image/webp")
|
||
http.ServeContent(w, r, "", st.ModTime(), f)
|
||
})
|
||
}
|
||
|
||
// noDirFS отдаёт только файлы: открытие каталога — «не найдено». Иначе http.FileServer
|
||
// показывает список всех загруженных файлов, включая ещё не опубликованные документы.
|
||
type noDirFS struct{ fs http.FileSystem }
|
||
|
||
func (n noDirFS) Open(name string) (http.File, error) {
|
||
f, err := n.fs.Open(name)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
st, err := f.Stat()
|
||
if err != nil || st.IsDir() {
|
||
f.Close()
|
||
return nil, os.ErrNotExist
|
||
}
|
||
return f, nil
|
||
}
|