Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 125680f

Browse files
committed
Cache bin size too
1 parent 359b2da commit 125680f

File tree

1 file changed

+69
-44
lines changed

1 file changed

+69
-44
lines changed

site/site.go

+69-44
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func New(opts *Options) *Handler {
109109
}
110110

111111
mux := http.NewServeMux()
112-
mux.Handle("/bin/", binHandler(opts.BinFS, newBinHashCache(opts.BinHashes)))
112+
mux.Handle("/bin/", binHandler(opts.BinFS, newBinMetadataCache(opts.BinFS, opts.BinHashes)))
113113
mux.Handle("/", http.FileServer(
114114
http.FS(
115115
// OnlyFiles is a wrapper around the file system that prevents directory
@@ -134,7 +134,7 @@ func New(opts *Options) *Handler {
134134
return handler
135135
}
136136

137-
func binHandler(binFS http.FileSystem, binHashCache *binHashCache) http.Handler {
137+
func binHandler(binFS http.FileSystem, binMetadataCache *binMetadataCache) http.Handler {
138138
return http.StripPrefix("/bin", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
139139
// Convert underscores in the filename to hyphens. We eventually want to
140140
// change our hyphen-based filenames to underscores, but we need to
@@ -156,7 +156,7 @@ func binHandler(binFS http.FileSystem, binHashCache *binHashCache) http.Handler
156156
return
157157
}
158158

159-
f, err := binFS.Open(name)
159+
metadata, err := binMetadataCache.getMetadata(name)
160160
if xerrors.Is(err, os.ErrNotExist) {
161161
http.NotFound(rw, r)
162162
return
@@ -165,7 +165,6 @@ func binHandler(binFS http.FileSystem, binHashCache *binHashCache) http.Handler
165165
http.Error(rw, err.Error(), http.StatusInternalServerError)
166166
return
167167
}
168-
defer f.Close()
169168

170169
// http.FileServer will not set Content-Length when performing chunked
171170
// transport encoding, which is used for large files like our binaries
@@ -175,23 +174,13 @@ func binHandler(binFS http.FileSystem, binHashCache *binHashCache) http.Handler
175174
// value of this header with the amount of bytes written to disk after
176175
// decompression to show progress. Without this, they cannot show
177176
// progress without disabling compression.
178-
stat, err := f.Stat()
179-
if err != nil {
180-
http.Error(rw, err.Error(), http.StatusInternalServerError)
181-
return
182-
}
177+
//
183178
// There isn't really a spec for a length header for the "inner" content
184179
// size, but some nginx modules use this header.
185-
rw.Header().Set("X-Original-Content-Length", fmt.Sprintf("%d", stat.Size()))
180+
rw.Header().Set("X-Original-Content-Length", fmt.Sprintf("%d", metadata.sizeBytes))
186181

187-
// Get and set ETag header.
188-
hash, err := binHashCache.getHash(name, f)
189-
if err != nil {
190-
http.Error(rw, err.Error(), http.StatusInternalServerError)
191-
return
192-
}
193-
// ETag header needs to be quoted.
194-
rw.Header().Set("ETag", fmt.Sprintf(`%q`, hash))
182+
// Get and set ETag header. Must be quoted.
183+
rw.Header().Set("ETag", fmt.Sprintf(`%q`, metadata.sha1Hash))
195184

196185
// http.FileServer will see the ETag header and automatically handle
197186
// If-Match and If-None-Match headers on the request properly.
@@ -979,59 +968,95 @@ func RenderStaticErrorPage(rw http.ResponseWriter, r *http.Request, data ErrorPa
979968
}
980969
}
981970

982-
type binHashCache struct {
983-
hashes map[string]string
984-
mut sync.RWMutex
985-
sf singleflight.Group
986-
sem chan struct{}
971+
type binMetadata struct {
972+
sizeBytes int64 // -1 if not known yet
973+
// SHA1 was chosen because it's fast to compute and reasonable for
974+
// determining if a file has changed. The ETag is not used a security
975+
// measure.
976+
sha1Hash string // always set if in the cache
977+
}
978+
979+
type binMetadataCache struct {
980+
binFS http.FileSystem
981+
originalHashes map[string]string
982+
983+
metadata map[string]binMetadata
984+
mut sync.RWMutex
985+
sf singleflight.Group
986+
sem chan struct{}
987987
}
988988

989-
func newBinHashCache(binHashes map[string]string) *binHashCache {
990-
b := &binHashCache{
991-
hashes: make(map[string]string, len(binHashes)),
992-
mut: sync.RWMutex{},
993-
sf: singleflight.Group{},
994-
sem: make(chan struct{}, 4),
989+
func newBinMetadataCache(binFS http.FileSystem, binSha1Hashes map[string]string) *binMetadataCache {
990+
b := &binMetadataCache{
991+
binFS: binFS,
992+
originalHashes: make(map[string]string, len(binSha1Hashes)),
993+
994+
metadata: make(map[string]binMetadata, len(binSha1Hashes)),
995+
mut: sync.RWMutex{},
996+
sf: singleflight.Group{},
997+
sem: make(chan struct{}, 4),
995998
}
996-
// Make a copy since we're gonna be mutating it.
997-
for k, v := range binHashes {
998-
b.hashes[k] = v
999+
1000+
// Previously we copied binSha1Hashes to the cache immediately. Since we now
1001+
// read other information like size from the file, we can't do that. Instead
1002+
// we copy the hashes to a different map that will be used to populate the
1003+
// cache on the first request.
1004+
for k, v := range binSha1Hashes {
1005+
b.originalHashes[k] = v
9991006
}
10001007

10011008
return b
10021009
}
10031010

1004-
func (b *binHashCache) getHash(name string, f http.File) (string, error) {
1011+
func (b *binMetadataCache) getMetadata(name string) (binMetadata, error) {
10051012
b.mut.RLock()
1006-
hash, ok := b.hashes[name]
1013+
metadata, ok := b.metadata[name]
10071014
b.mut.RUnlock()
10081015
if ok {
1009-
return hash, nil
1016+
return metadata, nil
10101017
}
10111018

10121019
// Avoid DOS by using a pool, and only doing work once per file.
1013-
v, err, _ := b.sf.Do(name, func() (interface{}, error) {
1020+
v, err, _ := b.sf.Do(name, func() (any, error) {
10141021
b.sem <- struct{}{}
10151022
defer func() { <-b.sem }()
10161023

1017-
h := sha1.New() //#nosec // Not used for cryptography.
1018-
_, err := io.Copy(h, f)
1024+
f, err := b.binFS.Open(name)
10191025
if err != nil {
1020-
return "", err
1026+
return binMetadata{}, err
1027+
}
1028+
defer f.Close()
1029+
1030+
var metadata binMetadata
1031+
1032+
stat, err := f.Stat()
1033+
if err != nil {
1034+
return binMetadata{}, err
1035+
}
1036+
metadata.sizeBytes = stat.Size()
1037+
1038+
if hash, ok := b.originalHashes[name]; ok {
1039+
metadata.sha1Hash = hash
1040+
} else {
1041+
h := sha1.New() //#nosec // Not used for cryptography.
1042+
_, err := io.Copy(h, f)
1043+
if err != nil {
1044+
return binMetadata{}, err
1045+
}
1046+
metadata.sha1Hash = hex.EncodeToString(h.Sum(nil))
10211047
}
10221048

1023-
hash := hex.EncodeToString(h.Sum(nil))
10241049
b.mut.Lock()
1025-
b.hashes[name] = hash
1050+
b.metadata[name] = metadata
10261051
b.mut.Unlock()
1027-
return hash, nil
1052+
return metadata, nil
10281053
})
10291054
if err != nil {
1030-
return "", err
1055+
return binMetadata{}, err
10311056
}
10321057

10331058
//nolint:forcetypeassert
1034-
return strings.ToLower(v.(string)), nil
1059+
return v.(binMetadata), nil
10351060
}
10361061

10371062
func applicationNameOrDefault(cfg codersdk.AppearanceConfig) string {

0 commit comments

Comments
 (0)