This commit is contained in:
106
app/web/main.go
106
app/web/main.go
@@ -1,12 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -89,7 +91,8 @@ func main() {
|
||||
port = "8080"
|
||||
}
|
||||
|
||||
http.Handle("/static/", withLogging(http.StripPrefix("/static/", http.FileServer(http.Dir("./static")))))
|
||||
// Static files under /static with long-lived caching and gzip for text assets
|
||||
http.Handle("/static/", withLogging(withCacheGzip(http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))), true)))
|
||||
http.Handle("/healthz", withLogging(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
@@ -105,10 +108,35 @@ func main() {
|
||||
proxyToNominatimGET(w, r, nominatimBase+"/reverse", nominatimUA)
|
||||
})))
|
||||
http.Handle("/", withLogging(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Serve index
|
||||
if r.URL.Path == "/" {
|
||||
http.ServeFile(w, r, "./static/index.html")
|
||||
// Short cache for HTML entrypoint
|
||||
setShortCache(w)
|
||||
maybeGzipAndServeFile(w, r, "./static/index.html")
|
||||
return
|
||||
}
|
||||
// Serve other assets from ./static with long cache for versioned files
|
||||
// Decide cache and compression based on extension
|
||||
ext := strings.ToLower(filepath.Ext(r.URL.Path))
|
||||
if isTextLikeExt(ext) {
|
||||
setLongCache(w)
|
||||
w.Header().Add("Vary", "Accept-Encoding")
|
||||
// Use gzip when supported
|
||||
if acceptsGzip(r) {
|
||||
gz := gzip.NewWriter(w)
|
||||
defer gz.Close()
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
// Avoid Content-Length when gzipping
|
||||
w.Header().Del("Content-Length")
|
||||
gzw := &gzipResponseWriter{ResponseWriter: w, gw: gz}
|
||||
http.ServeFile(gzw, r, "./static/"+r.URL.Path[1:])
|
||||
return
|
||||
}
|
||||
http.ServeFile(w, r, "./static/"+r.URL.Path[1:])
|
||||
return
|
||||
}
|
||||
// Non-text assets: just set long cache
|
||||
setLongCache(w)
|
||||
http.ServeFile(w, r, "./static/"+r.URL.Path[1:])
|
||||
})))
|
||||
logf(levelInfo, "Listening on :%s", port)
|
||||
@@ -246,4 +274,78 @@ func clientIP(r *http.Request) string {
|
||||
return xff
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
// ---- Performance helpers: caching and gzip compression ----
|
||||
|
||||
func setShortCache(w http.ResponseWriter) {
|
||||
// Short cache e.g., for HTML entrypoint
|
||||
w.Header().Set("Cache-Control", "public, max-age=300")
|
||||
}
|
||||
|
||||
func setLongCache(w http.ResponseWriter) {
|
||||
// Long-lived cache for versioned static assets
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
}
|
||||
|
||||
func isTextLikeExt(ext string) bool {
|
||||
switch ext {
|
||||
case ".html", ".css", ".js", ".mjs", ".json", ".svg", ".xml", ".txt", ".map", ".webmanifest":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func acceptsGzip(r *http.Request) bool {
|
||||
ae := r.Header.Get("Accept-Encoding")
|
||||
return strings.Contains(ae, "gzip")
|
||||
}
|
||||
|
||||
type gzipResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
gw *gzip.Writer
|
||||
}
|
||||
|
||||
func (g *gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
return g.gw.Write(b)
|
||||
}
|
||||
|
||||
// withCacheGzip wraps a handler to set cache headers and gzip text-like responses
|
||||
func withCacheGzip(next http.Handler, longCache bool) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if longCache {
|
||||
setLongCache(w)
|
||||
} else {
|
||||
setShortCache(w)
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(r.URL.Path))
|
||||
if isTextLikeExt(ext) && acceptsGzip(r) {
|
||||
w.Header().Add("Vary", "Accept-Encoding")
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
w.Header().Del("Content-Length")
|
||||
gz := gzip.NewWriter(w)
|
||||
defer gz.Close()
|
||||
gzw := &gzipResponseWriter{ResponseWriter: w, gw: gz}
|
||||
next.ServeHTTP(gzw, r)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func maybeGzipAndServeFile(w http.ResponseWriter, r *http.Request, path string) {
|
||||
// For text-like files, gzip when supported
|
||||
ext := strings.ToLower(filepath.Ext(path))
|
||||
if isTextLikeExt(ext) && acceptsGzip(r) {
|
||||
w.Header().Add("Vary", "Accept-Encoding")
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
w.Header().Del("Content-Length")
|
||||
gz := gzip.NewWriter(w)
|
||||
defer gz.Close()
|
||||
gzw := &gzipResponseWriter{ResponseWriter: w, gw: gz}
|
||||
http.ServeFile(gzw, r, path)
|
||||
return
|
||||
}
|
||||
http.ServeFile(w, r, path)
|
||||
}
|
||||
Reference in New Issue
Block a user