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
133
134
135
136
137
138
139
140
141
142
143
144
145
|
diff --git a/pkg/apps/pgs/web_asset_handler.go b/pkg/apps/pgs/web_asset_handler.go
index 44b0532..d134936 100644
--- a/pkg/apps/pgs/web_asset_handler.go
+++ b/pkg/apps/pgs/web_asset_handler.go
@@ -14,10 +14,17 @@ import (
"net/http/httputil"
_ "net/http/pprof"
+ "github.com/hashicorp/golang-lru/v2/expirable"
+ "github.com/picosh/pico/pkg/cache"
sst "github.com/picosh/pico/pkg/pobj/storage"
"github.com/picosh/pico/pkg/shared/storage"
)
+var (
+ redirectsCache = expirable.NewLRU[string, []*RedirectRule](2048, nil, cache.CacheTimeout)
+ headersCache = expirable.NewLRU[string, []*HeaderRule](2048, nil, cache.CacheTimeout)
+)
+
type ApiAssetHandler struct {
*WebRouter
Logger *slog.Logger
@@ -41,28 +48,36 @@ func hasProtocol(url string) bool {
func (h *ApiAssetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
logger := h.Logger
var redirects []*RedirectRule
- redirectFp, redirectInfo, err := h.Cfg.Storage.GetObject(h.Bucket, filepath.Join(h.ProjectDir, "_redirects"))
- if err == nil {
- defer redirectFp.Close()
- if redirectInfo != nil && redirectInfo.Size > h.Cfg.MaxSpecialFileSize {
- errMsg := fmt.Sprintf("_redirects file is too large (%d > %d)", redirectInfo.Size, h.Cfg.MaxSpecialFileSize)
- logger.Error(errMsg)
- http.Error(w, errMsg, http.StatusInternalServerError)
- return
- }
- buf := new(strings.Builder)
- lr := io.LimitReader(redirectFp, h.Cfg.MaxSpecialFileSize)
- _, err := io.Copy(buf, lr)
- if err != nil {
- logger.Error("io copy", "err", err.Error())
- http.Error(w, "cannot read _redirects file", http.StatusInternalServerError)
- return
- }
- redirects, err = parseRedirectText(buf.String())
- if err != nil {
- logger.Error("could not parse redirect text", "err", err.Error())
+ redirectsCacheKey := filepath.Join(h.Bucket.Name, h.ProjectDir, "_redirects")
+ if cachedRedirects, found := redirectsCache.Get(redirectsCacheKey); found {
+ redirects = cachedRedirects
+ } else {
+ redirectFp, redirectInfo, err := h.Cfg.Storage.GetObject(h.Bucket, filepath.Join(h.ProjectDir, "_redirects"))
+ if err == nil {
+ defer redirectFp.Close()
+ if redirectInfo != nil && redirectInfo.Size > h.Cfg.MaxSpecialFileSize {
+ errMsg := fmt.Sprintf("_redirects file is too large (%d > %d)", redirectInfo.Size, h.Cfg.MaxSpecialFileSize)
+ logger.Error(errMsg)
+ http.Error(w, errMsg, http.StatusInternalServerError)
+ return
+ }
+ buf := new(strings.Builder)
+ lr := io.LimitReader(redirectFp, h.Cfg.MaxSpecialFileSize)
+ _, err := io.Copy(buf, lr)
+ if err != nil {
+ logger.Error("io copy", "err", err.Error())
+ http.Error(w, "cannot read _redirects file", http.StatusInternalServerError)
+ return
+ }
+
+ redirects, err = parseRedirectText(buf.String())
+ if err != nil {
+ logger.Error("could not parse redirect text", "err", err.Error())
+ }
}
+
+ redirectsCache.Add(redirectsCacheKey, redirects)
}
routes := calcRoutes(h.ProjectDir, h.Filepath, redirects)
@@ -163,28 +178,36 @@ func (h *ApiAssetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer contents.Close()
var headers []*HeaderRule
- headersFp, headersInfo, err := h.Cfg.Storage.GetObject(h.Bucket, filepath.Join(h.ProjectDir, "_headers"))
- if err == nil {
- defer headersFp.Close()
- if headersInfo != nil && headersInfo.Size > h.Cfg.MaxSpecialFileSize {
- errMsg := fmt.Sprintf("_headers file is too large (%d > %d)", headersInfo.Size, h.Cfg.MaxSpecialFileSize)
- logger.Error(errMsg)
- http.Error(w, errMsg, http.StatusInternalServerError)
- return
- }
- buf := new(strings.Builder)
- lr := io.LimitReader(headersFp, h.Cfg.MaxSpecialFileSize)
- _, err := io.Copy(buf, lr)
- if err != nil {
- logger.Error("io copy", "err", err.Error())
- http.Error(w, "cannot read _headers file", http.StatusInternalServerError)
- return
- }
- headers, err = parseHeaderText(buf.String())
- if err != nil {
- logger.Error("could not parse header text", "err", err.Error())
+ headersCacheKey := filepath.Join(h.Bucket.Name, h.ProjectDir, "_headers")
+ if cachedHeaders, found := headersCache.Get(headersCacheKey); found {
+ headers = cachedHeaders
+ } else {
+ headersFp, headersInfo, err := h.Cfg.Storage.GetObject(h.Bucket, filepath.Join(h.ProjectDir, "_headers"))
+ if err == nil {
+ defer headersFp.Close()
+ if headersInfo != nil && headersInfo.Size > h.Cfg.MaxSpecialFileSize {
+ errMsg := fmt.Sprintf("_headers file is too large (%d > %d)", headersInfo.Size, h.Cfg.MaxSpecialFileSize)
+ logger.Error(errMsg)
+ http.Error(w, errMsg, http.StatusInternalServerError)
+ return
+ }
+ buf := new(strings.Builder)
+ lr := io.LimitReader(headersFp, h.Cfg.MaxSpecialFileSize)
+ _, err := io.Copy(buf, lr)
+ if err != nil {
+ logger.Error("io copy", "err", err.Error())
+ http.Error(w, "cannot read _headers file", http.StatusInternalServerError)
+ return
+ }
+
+ headers, err = parseHeaderText(buf.String())
+ if err != nil {
+ logger.Error("could not parse header text", "err", err.Error())
+ }
}
+
+ headersCache.Add(headersCacheKey, headers)
}
userHeaders := []*HeaderLine{}
@@ -236,7 +259,7 @@ func (h *ApiAssetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
w.WriteHeader(status)
- _, err = io.Copy(w, contents)
+ _, err := io.Copy(w, contents)
if err != nil {
logger.Error("io copy", "err", err.Error())
|