From 77c6d050427f5422a021b31212ed90713b68231e Mon Sep 17 00:00:00 2001 From: Julian Sommer <115516918+juliansommer@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:16:44 +1000 Subject: [PATCH] refactor: replace NYTimes/gziphandler with stdlib compress/gzip Drop the external gziphandler dependency in hiveui. The replacement uses a simple http.ResponseWriter wrapper that checks Content-Type against an allowlist before enabling gzip compression. Same behavior, zero external deps. --- go.mod | 1 - go.sum | 2 - hive/services/hiveui/hiveui.go | 78 +++++++++++++++++++++++++++------- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index e4222e5d2..5de7a49ab 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/mulgadc/hive go 1.26.0 require ( - github.com/NYTimes/gziphandler v1.1.1 github.com/aws/aws-sdk-go v1.55.8 github.com/go-chi/chi/v5 v5.2.4 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index 23999cedf..11ce624c9 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,6 @@ github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= diff --git a/hive/services/hiveui/hiveui.go b/hive/services/hiveui/hiveui.go index a5819d827..2cf07294d 100644 --- a/hive/services/hiveui/hiveui.go +++ b/hive/services/hiveui/hiveui.go @@ -16,7 +16,7 @@ import ( "syscall" "time" - "github.com/NYTimes/gziphandler" + "compress/gzip" "github.com/mulgadc/hive/hive/utils" ) @@ -270,19 +270,65 @@ func buildCSP() string { ) } -func gzipMiddleware(next http.Handler) http.Handler { - g, err := gziphandler.GzipHandlerWithOpts(gziphandler.ContentTypes([]string{ - "text/html", - "text/css", - "application/javascript", - "text/javascript", - "application/json", - "image/svg+xml", - "text/plain", - })) - if err != nil { - slog.Warn("Failed to create gzip middleware, serving uncompressed", "error", err) - return next - } - return g(next) +// gzipContentTypes lists the MIME types eligible for gzip compression. +var gzipContentTypes = map[string]bool{ + "text/html": true, + "text/css": true, + "application/javascript": true, + "text/javascript": true, + "application/json": true, + "image/svg+xml": true, + "text/plain": true, +} + +// gzipResponseWriter wraps http.ResponseWriter to compress eligible responses. +type gzipResponseWriter struct { + http.ResponseWriter + gw *gzip.Writer + wroteHeader bool + compress bool +} + +func (w *gzipResponseWriter) Write(b []byte) (int, error) { + if !w.wroteHeader { + w.WriteHeader(http.StatusOK) + } + if w.compress { + return w.gw.Write(b) + } + return w.ResponseWriter.Write(b) +} + +func (w *gzipResponseWriter) WriteHeader(code int) { + if w.wroteHeader { + return + } + w.wroteHeader = true + + ct := w.ResponseWriter.Header().Get("Content-Type") + // Strip parameters (e.g. "text/html; charset=utf-8" → "text/html") + if idx := strings.IndexByte(ct, ';'); idx != -1 { + ct = strings.TrimSpace(ct[:idx]) + } + if gzipContentTypes[ct] { + w.compress = true + w.ResponseWriter.Header().Set("Content-Encoding", "gzip") + w.ResponseWriter.Header().Del("Content-Length") + } + w.ResponseWriter.WriteHeader(code) +} + +func gzipMiddleware(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 + } + + gz, _ := gzip.NewWriterLevel(w, gzip.DefaultCompression) + defer gz.Close() + + grw := &gzipResponseWriter{ResponseWriter: w, gw: gz} + next.ServeHTTP(grw, r) + }) }