package main import ( "compress/gzip" "io" "log" "net/http" "strings" "chaosmith-site/blog" ) type gzipResponseWriter struct { io.Writer http.ResponseWriter } func (w gzipResponseWriter) Write(b []byte) (int, error) { return w.Writer.Write(b) } func gzipHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { next.ServeHTTP(w, r) return } w.Header().Set("Content-Encoding", "gzip") w.Header().Del("Content-Length") gz := gzip.NewWriter(w) defer gz.Close() next.ServeHTTP(gzipResponseWriter{Writer: gz, ResponseWriter: w}, r) }) } func cacheHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/src/") || strings.HasPrefix(r.URL.Path, "/content/images/") || strings.HasPrefix(r.URL.Path, "/ABSTRACTS.") || strings.HasPrefix(r.URL.Path, "/blog/og/") { w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") } next.ServeHTTP(w, r) }) } type mimeWriter struct { http.ResponseWriter override string wrote bool } func (m *mimeWriter) WriteHeader(code int) { if !m.wrote { m.wrote = true m.Header().Set("Content-Type", m.override) } m.ResponseWriter.WriteHeader(code) } func (m *mimeWriter) Write(b []byte) (int, error) { if !m.wrote { m.WriteHeader(200) } return m.ResponseWriter.Write(b) } func mimeHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ext := strings.ToLower(r.URL.Path[strings.LastIndex(r.URL.Path, ".")+1:]) var ct string switch ext { case "woff2": ct = "font/woff2" case "woff": ct = "font/woff" case "ttf": ct = "font/ttf" case "js": ct = "text/javascript" case "css": ct = "text/css" case "webp": ct = "image/webp" case "svg": ct = "image/svg+xml" case "ico": ct = "image/x-icon" } if ct != "" { next.ServeHTTP(&mimeWriter{ResponseWriter: w, override: ct}, r) return } next.ServeHTTP(w, r) }) } func main() { mux := http.NewServeMux() mux.HandleFunc("/ABSTRACTS.png", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "ABSTRACTS.png") }) mux.HandleFunc("/ABSTRACTS.webp", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "ABSTRACTS.webp") }) mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "favicon.ico") }) mux.HandleFunc("/favicon.svg", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "favicon.svg") }) mux.Handle("/src/", http.StripPrefix("/src/", http.FileServer(http.Dir("src")))) mux.Handle("/content/images/", http.StripPrefix("/content/images/", http.FileServer(http.Dir("content/images")))) if err := blog.RegisterRoutes(mux, "content/posts"); err != nil { log.Fatalf("blog: %v", err) } // Landing page — served by blog package so htmx can swap between sections mux.HandleFunc("/", blog.ServeLanding) log.Println("listening on :8000") http.ListenAndServe(":8000", mimeHandler(cacheHandler(gzipHandler(mux)))) }