-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfileserver.go
85 lines (78 loc) · 1.77 KB
/
fileserver.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package cs
import (
"crypto/sha256"
"embed"
"encoding/base64"
"io"
"io/fs"
"net/http"
"path/filepath"
"strings"
"sync"
)
// EmbedFSServer creates an HTTP static file server on the OS.
//
// The main distinction from http.FileServerFS is that it calculates and caches hashes for the underlying file system.
// An embed.FS is required since it can be assumed immutable and fixed, so hashes are cached indefinitely.
func EmbedFSServer(root embed.FS) http.Handler {
fs := &embedFS{FS: root}
h := http.FileServerFS(fs)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Path
f, err := fs.Open(name)
if err == nil {
if ff, ok := f.(hashedEmbedFile); ok {
w.Header().Set("Etag", `"`+ff.ContentHash()+`"`)
}
f.Close()
}
h.ServeHTTP(w, r)
})
}
type embedFS struct {
embed.FS
hashes sync.Map
}
func (fs *embedFS) Open(name string) (fs.File, error) {
name = filepath.Clean(name)
if name == "/" {
name = "."
} else {
name = strings.TrimPrefix(name, "/")
}
f, err := fs.FS.Open(name)
if err != nil {
return nil, err
}
if ff, ok := f.(io.ReadSeeker); ok {
// This is a file. We can wrap it with something that tracks a hash.
hash, found := fs.hashes.Load(name)
if !found {
h := sha256.New()
if _, err := io.Copy(h, ff); err != nil {
return nil, err
}
hash = base64.RawURLEncoding.EncodeToString(h.Sum(nil))
fs.hashes.Store(name, hash)
ff.Seek(0, io.SeekStart)
}
f = &hashedFile{f.(embedFile), hash.(string)}
}
return f, nil
}
type embedFile interface {
fs.File
io.Seeker // http.File will test for this.
io.ReaderAt
}
type hashedEmbedFile interface {
embedFile
ContentHash() string
}
type hashedFile struct {
embedFile
hash string
}
func (f *hashedFile) ContentHash() string {
return f.hash
}