-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
serve_static.go
132 lines (119 loc) · 4.56 KB
/
serve_static.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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package httpserver
import (
"embed"
"fmt"
"net/http"
"os"
"strings"
"time"
)
// StaticHandler creates a new http.HandlerFunc that serves static files from the specified root directory.
// It does not allow directory listings and optionally supports caching of the served files.
//
// Parameters:
// - publicPath: The URL path prefix from which the static files will be served.
// - root: The http.FileSystem representing the root directory from which files will be served.
// - cacheTTL: The duration for which the client should cache the served files.
//
// Returns:
// An http.HandlerFunc that serves static files with optional caching.
func StaticHandler(publicPath string, root http.FileSystem, cacheTTL time.Duration) http.HandlerFunc {
return serveStaticHandlerFunc(publicPath, root, cacheTTL)
}
// EmbeddedStaticHandler creates a new http.HandlerFunc that serves static files from an embedded file system.
// It uses the embed.FS type to serve files from the specified directory.
//
// Parameters:
// - fs: The embed.FS representing the embedded file system.
// - cacheTTL: The duration for which the client should cache the served files.
//
// Returns:
// An http.HandlerFunc that serves static files with optional caching.
func EmbeddedStaticHandler(fs embed.FS, cacheTTL time.Duration) http.HandlerFunc {
return serveStaticHandlerFunc("", http.FS(fs), cacheTTL)
}
// serveFile serves a single file through HTTP with optional caching.
// It sets appropriate headers for caching based on the cacheTTL parameter.
// If cacheTTL is 0, caching is disabled.
//
// Parameters:
// - w: The http.ResponseWriter to write the response to.
// - r: The *http.Request representing the client's request.
// - file: The http.File representing the file to serve.
// - info: The os.FileInfo containing metadata about the file.
// - cacheTTL: The duration for which the file should be cached by the client.
func serveFile(w http.ResponseWriter, r *http.Request, file http.File, info os.FileInfo, cacheTTL time.Duration) {
if cacheTTL == 0 {
// No caching
http.ServeContent(w, r, info.Name(), info.ModTime(), file)
return
}
// Generate ETag using file info
etag := fmt.Sprintf(`"%x-%x"`, info.ModTime().Unix(), info.Size())
lastModified := info.ModTime().UTC().Format(http.TimeFormat)
// Set headers for caching
w.Header().Set("ETag", etag)
w.Header().Set("Last-Modified", lastModified)
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", int(cacheTTL.Seconds())))
w.Header().Set("Expires", time.Now().Add(cacheTTL).UTC().Format(http.TimeFormat))
w.Header().Set("Pragma", "cache")
// Check if file hasn't been modified since the last request
if match := r.Header.Get("If-None-Match"); match != "" {
if strings.Contains(match, etag) {
w.WriteHeader(http.StatusNotModified)
return
}
}
// Check if file has been modified since the last request based on Last-Modified header
ifModifiedSince := r.Header.Get("If-Modified-Since")
if ifModifiedSince != "" {
if t, err := time.Parse(http.TimeFormat,
ifModifiedSince); err == nil && info.ModTime().Before(t.Add(1*time.Second)) {
w.WriteHeader(http.StatusNotModified)
return
}
}
// Serve the file
http.ServeContent(w, r, info.Name(), info.ModTime(), file)
}
// serveStaticHandlerFunc creates and returns a http.HandlerFunc that serves static files from a specified root directory.
// It does not allow directory listings and optionally supports caching of the served files.
//
// Parameters:
// - publicPath: The URL path prefix from which the static files will be served.
// - root: The http.FileSystem representing the root directory from which files will be served.
// - cacheTTL: The duration for which the client should cache the served files.
//
// Returns:
// An http.HandlerFunc that serves static files with optional caching.
func serveStaticHandlerFunc(publicPath string, root http.FileSystem, cacheTTL time.Duration) http.HandlerFunc {
publicPath = strings.TrimRight(publicPath, "/")
return func(w http.ResponseWriter, r *http.Request) {
fsPath := strings.TrimPrefix(r.URL.Path, publicPath)
file, err := root.Open(fsPath)
if err != nil {
// File not found
http.NotFound(w, r)
return
}
defer func(file http.File) {
if err := file.Close(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}(file)
info, err := file.Stat()
if err != nil {
// Error getting file info
http.NotFound(w, r)
return
}
if info.IsDir() {
// Path is a directory, return 404
http.NotFound(w, r)
return
}
// Serve file with caching
serveFile(w, r, file, info, cacheTTL)
}
}