f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
220 lines
4.6 KiB
Go
220 lines
4.6 KiB
Go
package server
|
|
|
|
import (
|
|
"compress/gzip"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
const gzipMinLength = 512
|
|
|
|
func gzipMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if !shouldGzip(r) {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
gw := &gzipResponseWriter{
|
|
ResponseWriter: w,
|
|
minLength: gzipMinLength,
|
|
}
|
|
defer gw.close()
|
|
next.ServeHTTP(gw, r)
|
|
})
|
|
}
|
|
|
|
func shouldGzip(r *http.Request) bool {
|
|
if r.Method == http.MethodHead {
|
|
return false
|
|
}
|
|
if !strings.HasPrefix(r.URL.Path, "/api/") {
|
|
return false
|
|
}
|
|
if r.Header.Get("Range") != "" {
|
|
return false
|
|
}
|
|
if !headerContainsToken(r.Header.Get("Accept-Encoding"), "gzip") {
|
|
return false
|
|
}
|
|
if headerContainsToken(r.Header.Get("Accept"), "text/event-stream") {
|
|
return false
|
|
}
|
|
if isStreamingPath(r.URL.Path) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
type gzipResponseWriter struct {
|
|
http.ResponseWriter
|
|
writer *gzip.Writer
|
|
minLength int
|
|
status int
|
|
buffer []byte
|
|
wroteHeader bool
|
|
sentHeader bool
|
|
compressing bool
|
|
plain bool
|
|
}
|
|
|
|
func (w *gzipResponseWriter) Header() http.Header {
|
|
return w.ResponseWriter.Header()
|
|
}
|
|
|
|
func (w *gzipResponseWriter) WriteHeader(status int) {
|
|
if w.wroteHeader {
|
|
return
|
|
}
|
|
w.status = status
|
|
w.wroteHeader = true
|
|
if !canGzipStatus(status) || w.Header().Get("Content-Encoding") != "" {
|
|
w.ResponseWriter.WriteHeader(status)
|
|
w.sentHeader = true
|
|
return
|
|
}
|
|
}
|
|
|
|
func (w *gzipResponseWriter) Write(p []byte) (int, error) {
|
|
if !w.wroteHeader {
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
if w.compressing {
|
|
_, err := w.writer.Write(p)
|
|
return len(p), err
|
|
}
|
|
if w.plain || !canGzipStatus(w.status) ||
|
|
w.Header().Get("Content-Encoding") != "" ||
|
|
isEventStreamContentType(w.Header().Get("Content-Type")) {
|
|
w.writePlainHeader()
|
|
_, err := w.ResponseWriter.Write(p)
|
|
return len(p), err
|
|
}
|
|
|
|
w.buffer = append(w.buffer, p...)
|
|
if len(w.buffer) < w.minLength {
|
|
return len(p), nil
|
|
}
|
|
if err := w.startGzip(); err != nil {
|
|
return 0, err
|
|
}
|
|
_, err := w.writer.Write(w.buffer)
|
|
w.buffer = nil
|
|
return len(p), err
|
|
}
|
|
|
|
func (w *gzipResponseWriter) Flush() {
|
|
if !w.wroteHeader {
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
if w.compressing {
|
|
_ = w.writer.Flush()
|
|
if f, ok := w.ResponseWriter.(http.Flusher); ok {
|
|
f.Flush()
|
|
}
|
|
return
|
|
}
|
|
if len(w.buffer) > 0 {
|
|
w.writePlainHeader()
|
|
_, _ = w.ResponseWriter.Write(w.buffer)
|
|
w.buffer = nil
|
|
} else if !w.sentHeader {
|
|
w.writePlainHeader()
|
|
}
|
|
if f, ok := w.ResponseWriter.(http.Flusher); ok {
|
|
f.Flush()
|
|
}
|
|
}
|
|
|
|
func (w *gzipResponseWriter) close() {
|
|
if !w.wroteHeader {
|
|
return
|
|
}
|
|
if w.compressing {
|
|
_ = w.writer.Close()
|
|
return
|
|
}
|
|
if len(w.buffer) > 0 {
|
|
w.writePlainHeader()
|
|
_, _ = w.ResponseWriter.Write(w.buffer)
|
|
w.buffer = nil
|
|
} else if !w.sentHeader {
|
|
w.writePlainHeader()
|
|
}
|
|
}
|
|
|
|
func (w *gzipResponseWriter) startGzip() error {
|
|
if w.plain || w.sentHeader ||
|
|
isEventStreamContentType(w.Header().Get("Content-Type")) {
|
|
return nil
|
|
}
|
|
if w.Header().Get("Content-Type") == "" && len(w.buffer) > 0 {
|
|
w.Header().Set("Content-Type", http.DetectContentType(w.buffer))
|
|
}
|
|
w.Header().Del("Content-Length")
|
|
w.Header().Set("Content-Encoding", "gzip")
|
|
addVaryHeader(w.Header(), "Accept-Encoding")
|
|
w.writer = gzip.NewWriter(w.ResponseWriter)
|
|
w.compressing = true
|
|
w.ResponseWriter.WriteHeader(w.status)
|
|
w.sentHeader = true
|
|
return nil
|
|
}
|
|
|
|
func (w *gzipResponseWriter) writePlainHeader() {
|
|
if w.sentHeader {
|
|
w.plain = true
|
|
return
|
|
}
|
|
w.ResponseWriter.WriteHeader(w.status)
|
|
w.sentHeader = true
|
|
w.plain = true
|
|
}
|
|
|
|
func canGzipStatus(status int) bool {
|
|
return status != http.StatusNoContent &&
|
|
status != http.StatusNotModified &&
|
|
status >= 200
|
|
}
|
|
|
|
func headerContainsToken(value, token string) bool {
|
|
for part := range strings.SplitSeq(value, ",") {
|
|
if strings.EqualFold(strings.TrimSpace(strings.Split(part, ";")[0]), token) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func addVaryHeader(h http.Header, value string) {
|
|
vary := h.Values("Vary")
|
|
for _, line := range vary {
|
|
if headerContainsToken(line, value) {
|
|
return
|
|
}
|
|
}
|
|
h.Add("Vary", value)
|
|
}
|
|
|
|
func isEventStreamContentType(value string) bool {
|
|
mediaType, _, _ := strings.Cut(value, ";")
|
|
return strings.EqualFold(strings.TrimSpace(mediaType), "text/event-stream")
|
|
}
|
|
|
|
func isStreamingPath(path string) bool {
|
|
switch {
|
|
case path == "/api/v1/events",
|
|
path == "/api/v1/sync",
|
|
path == "/api/v1/resync",
|
|
path == "/api/v1/secrets/scan",
|
|
path == "/api/v1/insights/generate":
|
|
return true
|
|
case strings.HasSuffix(path, "/watch"):
|
|
return true
|
|
case strings.HasPrefix(path, "/api/v1/import/"):
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|