repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

commit
66ac6b0
parent
acf6290
author
Eric Bower
date
2026-04-22 11:00:24 -0400 EDT
fix(pgs): dupe headers
2 files changed,  +29, -11
M cmd/pgs/cdn/main.go
+15, -3
 1@@ -62,6 +62,14 @@ func newProxyServe(logger *slog.Logger) *proxyServe {
 2 	return &proxyServe{Logger: logger, transport: transport}
 3 }
 4 
 5+// Headers that should be stripped from upstream responses because the CDN's
 6+// cache layer will add its own versions.
 7+var stripHeaders = map[string]bool{
 8+	"age":          true,
 9+	"cache-status": true,
10+	"date":         true,
11+}
12+
13 func (p *proxyServe) ServeHTTP(w http.ResponseWriter, req *http.Request) {
14 	// Dial ash.pgs.sh but keep the original Host header so ash.pgs.sh
15 	// can route the request to the correct subdomain (zmx.sh, etc.).
16@@ -90,10 +98,13 @@ func (p *proxyServe) ServeHTTP(w http.ResponseWriter, req *http.Request) {
17 		_ = resp.Body.Close()
18 	}()
19 
20+	// Copy headers from upstream, but strip cache-related headers that the
21+	// CDN's cache layer will regenerate
22 	for k, vals := range resp.Header {
23-		for _, v := range vals {
24-			w.Header().Set(k, v)
25+		if stripHeaders[strings.ToLower(k)] {
26+			continue
27 		}
28+		w.Header()[k] = vals
29 	}
30 	w.WriteHeader(resp.StatusCode)
31 	_, _ = io.Copy(w, resp.Body)
32@@ -121,11 +132,12 @@ func (c *cachedHttp) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
33 		if err != nil {
34 			c.Logger.Error("check request", "err", err)
35 		}
36-		writer.WriteHeader(resp.StatusCode)
37 		defer func() {
38 			_ = resp.Body.Close()
39 		}()
40+		writer.WriteHeader(resp.StatusCode)
41 		_, _ = io.Copy(writer, resp.Body)
42+		return
43 	}
44 
45 	c.Cache.ServeHTTP(writer, req)
M pkg/httpcache/serve.go
+14, -8
 1@@ -95,9 +95,6 @@ func (c *HttpCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 2 		return
 3 	}
 4 
 5-	log.Info("cache miss, requesting upstream", "err", err)
 6-	c.AddCacheMiss()
 7-
 8 	// RFC 9111 4.2.4 + 4.3.1/4.3.2: stale must-revalidate entries must be
 9 	// revalidated with conditional headers derived from the stored response.
10 	// Preserve original client conditional headers so we can evaluate them
11@@ -120,6 +117,8 @@ func (c *HttpCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
12 		}
13 	}
14 
15+	log.Info("cache miss, requesting upstream", "err", err)
16+	c.AddCacheMiss()
17 	wrapped := &responseWriter{ResponseWriter: w}
18 	c.Upstream.ServeHTTP(wrapped, r)
19 	c.AddUpstreamRequest()
20@@ -128,16 +127,18 @@ func (c *HttpCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
21 	// https://www.rfc-editor.org/rfc/rfc9111.html#section-4.3.4
22 	// A 304 response updates header metadata but preserves the cached body.
23 	if wrapped.StatusCode() == http.StatusNotModified {
24-		log.Info("304 not modified, updating cached headers")
25 		existingData, exists := c.Cache.Get(cacheKey)
26 		if !exists {
27 			// Cache entry vanished; forward the 304 as-is.
28+			log.Info("no cache entry found, forwarding 304 as-is")
29 			wrapped.Send()
30 			return
31 		}
32 
33 		var cacheValue CacheValue
34-		if json.Unmarshal(existingData, &cacheValue) != nil {
35+		err = json.Unmarshal(existingData, &cacheValue)
36+		if err != nil {
37+			log.Error("json unmarshal", "err", err)
38 			wrapped.Send()
39 			return
40 		}
41@@ -153,6 +154,7 @@ func (c *HttpCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
42 		// Revalidation refreshes the entry -- reset CreatedAt so it's fresh again.
43 		cacheValue.CreatedAt = time.Now()
44 		enc, _ := json.Marshal(cacheValue)
45+		log.Info("updating cached headers from 304 response")
46 		c.Cache.Remove(cacheKey)
47 		c.Cache.Add(cacheKey, enc)
48 		c.AddCacheItem(float64(len(enc)))
49@@ -169,12 +171,14 @@ func (c *HttpCache) ServeHTTP(w http.ResponseWriter, r *http.Request) {
50 				hdr.Set("age", strconv.Itoa(int(ageDur.Seconds())+1))
51 				hdr.Set("cache-status", cacheStatusStale(cacheKey, wrapped.StatusCode()))
52 				w.WriteHeader(http.StatusNotModified)
53+				log.Info("client conditional headers match, returning 304")
54 				return
55 			}
56 		}
57 
58 		// Client request was unconditional (or conditional but no longer matches)
59 		// serve the full cached response.
60+		log.Info("serving full cached response to client")
61 		serveCache(w, c.Ttl, cacheKey, &cacheValue)
62 		return
63 	}
64@@ -215,9 +219,11 @@ func serveCache(w http.ResponseWriter, freshness time.Duration, cacheKey string,
65 	age := ageDur.Seconds()
66 	hdr.Set("age", strconv.Itoa(int(age)+1))
67 	hdr.Set("cache-status", cacheStatusHit(cacheKey, freshness.Seconds()))
68-	if cacheValue.StatusCode != 0 && cacheValue.StatusCode != http.StatusOK {
69-		w.WriteHeader(cacheValue.StatusCode)
70+	statusCode := cacheValue.StatusCode
71+	if statusCode == 0 {
72+		statusCode = http.StatusOK
73 	}
74+	w.WriteHeader(statusCode)
75 	_, _ = w.Write(cacheValue.Body)
76 }
77 
78@@ -741,7 +747,7 @@ func stripForbiddenHeaders(w http.ResponseWriter, cacheValue *CacheValue) http.H
79 		if isForbiddenHeader(key) {
80 			continue
81 		}
82-		hdr[key] = values
83+		hdr[http.CanonicalHeaderKey(key)] = values
84 	}
85 	return hdr
86 }