0880e6ac
feat: decompress gzip responses in middleware save
a73x 2026-03-31 12:44
SaveResponse now checks Content-Encoding and decompresses gzip bodies before writing to disk. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
diff --git a/middleware/middleware.go b/middleware/middleware.go index 1e8b10e..7004aa7 100644 --- a/middleware/middleware.go +++ b/middleware/middleware.go @@ -1,7 +1,10 @@ package middleware import ( "bytes" "compress/gzip" "fmt" "io" "os" "gopkg.in/yaml.v3" @@ -71,7 +74,19 @@ func (m *Middleware) Match(host, path string) *Rule { } // SaveResponse writes the response body to the rule's destination file, // overwriting any existing content. func (r *Rule) SaveResponse(body []byte) error { // overwriting any existing content. If contentEncoding is "gzip", the // body is decompressed before writing. func (r *Rule) SaveResponse(body []byte, contentEncoding string) error { if contentEncoding == "gzip" { gr, err := gzip.NewReader(bytes.NewReader(body)) if err != nil { return fmt.Errorf("decompressing gzip response: %w", err) } defer gr.Close() body, err = io.ReadAll(gr) if err != nil { return fmt.Errorf("reading decompressed response: %w", err) } } return os.WriteFile(r.Dest, body, 0644) } diff --git a/middleware/middleware_test.go b/middleware/middleware_test.go index 5c2b170..952926c 100644 --- a/middleware/middleware_test.go +++ b/middleware/middleware_test.go @@ -1,6 +1,8 @@ package middleware_test import ( "bytes" "compress/gzip" "os" "path/filepath" "testing" @@ -95,7 +97,7 @@ func TestSaveResponseWritesBodyToFile(t *testing.T) { } body := []byte(`{"tokens": 42}`) err := rule.SaveResponse(body) err := rule.SaveResponse(body, "") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -120,10 +122,35 @@ func TestSaveResponseOverwritesExistingFile(t *testing.T) { } body := []byte(`{"new": true}`) rule.SaveResponse(body) rule.SaveResponse(body, "") got, _ := os.ReadFile(dest) if string(got) != string(body) { t.Errorf("expected %q, got %q", body, got) } } func TestSaveResponseDecompressesGzip(t *testing.T) { dest := filepath.Join(t.TempDir(), "out.json") rule := &middleware.Rule{ Match: "example.com/data", Action: "save_response", Dest: dest, } original := `{"tokens": 42}` var buf bytes.Buffer gw := gzip.NewWriter(&buf) gw.Write([]byte(original)) gw.Close() err := rule.SaveResponse(buf.Bytes(), "gzip") if err != nil { t.Fatalf("unexpected error: %v", err) } got, _ := os.ReadFile(dest) if string(got) != original { t.Errorf("expected %q, got %q", original, got) } } diff --git a/proxy/proxy.go b/proxy/proxy.go index 0b6ad7c..8dc4d25 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -254,7 +254,7 @@ func (p *Proxy) handleConnectMITM(w http.ResponseWriter, r *http.Request) { log.Printf("ERROR: middleware failed to read response body from %s: %v", host, err) upstreamResp.Body = io.NopCloser(bytes.NewReader(nil)) } else { if err := rule.SaveResponse(body); err != nil { if err := rule.SaveResponse(body, upstreamResp.Header.Get("Content-Encoding")); err != nil { log.Printf("ERROR: middleware failed to save response to %s: %v", rule.Dest, err) } else { log.Printf("MIDDLEWARE saved %s%s -> %s", host, req.URL.Path, rule.Dest)