From 83a734e9e64dd41432625f316b598dfba78fcb83 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Fri, 23 Oct 2026 11:08:54 -0700 Subject: [PATCH] Gzip large JSON API responses to cut transcript download time --- internal/server/compress.go | 64 +++++++++++++++++++++++++++++++++++++ internal/server/server.go | 2 +- 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 internal/server/compress.go diff --git a/internal/server/compress.go b/internal/server/compress.go new file mode 100644 index 0000000..a4d5acb --- /dev/null +++ b/internal/server/compress.go @@ -0,0 +1,64 @@ +package server + +import ( + "compress/gzip" + "log/slog" + "net/http" + "strings" +) + +const gzipMin = 8 << 10 + +type gzipWriter struct { + http.ResponseWriter + held int + gz *gzip.Writer +} + +func (w *gzipWriter) WriteHeader(status int) { + if status == http.StatusOK && w.Header().Get("Content-Type") == "application/json" { + w.held = status + return + } + w.ResponseWriter.WriteHeader(status) +} + +func (w *gzipWriter) Write(b []byte) (int, error) { + if w.held != 0 { + if len(b) >= gzipMin { + w.Header().Set("Content-Encoding", "gzip") + w.Header().Del("Content-Length") + w.gz = gzip.NewWriter(w.ResponseWriter) + } + w.ResponseWriter.WriteHeader(w.held) + w.held = 0 + } + if w.gz != nil { + return w.gz.Write(b) + } + return w.ResponseWriter.Write(b) +} + +func (w *gzipWriter) Unwrap() http.ResponseWriter { + return w.ResponseWriter +} + +func compress(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 + } + gw := &gzipWriter{ResponseWriter: w} + next.ServeHTTP(gw, r) + if gw.held != 0 { + w.WriteHeader(gw.held) + } + if gw.gz != nil { + err := gw.gz.Close() + if err != nil { + slog.Warn("write response", "err", err) + } + } + }) +} diff --git a/internal/server/server.go b/internal/server/server.go index 3986341..a68a986 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -135,7 +135,7 @@ func (s *Server) Handler() http.Handler { writeError(w, http.StatusBadRequest, err) }, }) - return timing(http.NewCrossOriginProtection().Handler(mux)) + return timing(compress(http.NewCrossOriginProtection().Handler(mux))) } func (s *Server) LoopbackHandler() http.Handler {