0880e6ac
feat: decompress gzip responses in middleware save
a73x 2026-03-31 12:44
Commit message
middleware/middleware.go
| Old | New | ||
|---|---|---|---|
| @@ -1,7 +1,10 @@ | |||
| 1 | package middleware | 1 | package middleware |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "bytes" | ||
| 5 | "compress/gzip" | ||
| 4 | "fmt" | 6 | "fmt" |
| 7 | "io" | ||
| 5 | "os" | 8 | "os" |
| 6 | 9 | ||
| 7 | "gopkg.in/yaml.v3" | 10 | "gopkg.in/yaml.v3" |
| @@ -71,7 +74,19 @@ func (m *Middleware) Match(host, path string) *Rule { | |||
| 71 | } | 74 | } |
| 72 | 75 | ||
| 73 | // SaveResponse writes the response body to the rule's destination file, | 76 | // SaveResponse writes the response body to the rule's destination file, |
| 74 | // overwriting any existing content. | 77 | // overwriting any existing content. If contentEncoding is "gzip", the |
| 75 | func (r *Rule) SaveResponse(body []byte) error { | 78 | // body is decompressed before writing. |
| 79 | func (r *Rule) SaveResponse(body []byte, contentEncoding string) error { | ||
| 80 | if contentEncoding == "gzip" { | ||
| 81 | gr, err := gzip.NewReader(bytes.NewReader(body)) | ||
| 82 | if err != nil { | ||
| 83 | return fmt.Errorf("decompressing gzip response: %w", err) | ||
| 84 | } | ||
| 85 | defer gr.Close() | ||
| 86 | body, err = io.ReadAll(gr) | ||
| 87 | if err != nil { | ||
| 88 | return fmt.Errorf("reading decompressed response: %w", err) | ||
| 89 | } | ||
| 90 | } | ||
| 76 | return os.WriteFile(r.Dest, body, 0644) | 91 | return os.WriteFile(r.Dest, body, 0644) |
| 77 | } | 92 | } |
middleware/middleware_test.go
| Old | New | ||
|---|---|---|---|
| @@ -1,6 +1,8 @@ | |||
| 1 | package middleware_test | 1 | package middleware_test |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "bytes" | ||
| 5 | "compress/gzip" | ||
| 4 | "os" | 6 | "os" |
| 5 | "path/filepath" | 7 | "path/filepath" |
| 6 | "testing" | 8 | "testing" |
| @@ -95,7 +97,7 @@ func TestSaveResponseWritesBodyToFile(t *testing.T) { | |||
| 95 | } | 97 | } |
| 96 | 98 | ||
| 97 | body := []byte(`{"tokens": 42}`) | 99 | body := []byte(`{"tokens": 42}`) |
| 98 | err := rule.SaveResponse(body) | 100 | err := rule.SaveResponse(body, "") |
| 99 | if err != nil { | 101 | if err != nil { |
| 100 | t.Fatalf("unexpected error: %v", err) | 102 | t.Fatalf("unexpected error: %v", err) |
| 101 | } | 103 | } |
| @@ -120,10 +122,35 @@ func TestSaveResponseOverwritesExistingFile(t *testing.T) { | |||
| 120 | } | 122 | } |
| 121 | 123 | ||
| 122 | body := []byte(`{"new": true}`) | 124 | body := []byte(`{"new": true}`) |
| 123 | rule.SaveResponse(body) | 125 | rule.SaveResponse(body, "") |
| 124 | 126 | ||
| 125 | got, _ := os.ReadFile(dest) | 127 | got, _ := os.ReadFile(dest) |
| 126 | if string(got) != string(body) { | 128 | if string(got) != string(body) { |
| 127 | t.Errorf("expected %q, got %q", body, got) | 129 | t.Errorf("expected %q, got %q", body, got) |
| 128 | } | 130 | } |
| 129 | } | 131 | } |
| 132 | |||
| 133 | func TestSaveResponseDecompressesGzip(t *testing.T) { | ||
| 134 | dest := filepath.Join(t.TempDir(), "out.json") | ||
| 135 | rule := &middleware.Rule{ | ||
| 136 | Match: "example.com/data", | ||
| 137 | Action: "save_response", | ||
| 138 | Dest: dest, | ||
| 139 | } | ||
| 140 | |||
| 141 | original := `{"tokens": 42}` | ||
| 142 | var buf bytes.Buffer | ||
| 143 | gw := gzip.NewWriter(&buf) | ||
| 144 | gw.Write([]byte(original)) | ||
| 145 | gw.Close() | ||
| 146 | |||
| 147 | err := rule.SaveResponse(buf.Bytes(), "gzip") | ||
| 148 | if err != nil { | ||
| 149 | t.Fatalf("unexpected error: %v", err) | ||
| 150 | } | ||
| 151 | |||
| 152 | got, _ := os.ReadFile(dest) | ||
| 153 | if string(got) != original { | ||
| 154 | t.Errorf("expected %q, got %q", original, got) | ||
| 155 | } | ||
| 156 | } | ||
proxy/proxy.go
| Old | New | ||
|---|---|---|---|
| @@ -254,7 +254,7 @@ func (p *Proxy) handleConnectMITM(w http.ResponseWriter, r *http.Request) { | |||
| 254 | log.Printf("ERROR: middleware failed to read response body from %s: %v", host, err) | 254 | log.Printf("ERROR: middleware failed to read response body from %s: %v", host, err) |
| 255 | upstreamResp.Body = io.NopCloser(bytes.NewReader(nil)) | 255 | upstreamResp.Body = io.NopCloser(bytes.NewReader(nil)) |
| 256 | } else { | 256 | } else { |
| 257 | if err := rule.SaveResponse(body); err != nil { | 257 | if err := rule.SaveResponse(body, upstreamResp.Header.Get("Content-Encoding")); err != nil { |
| 258 | log.Printf("ERROR: middleware failed to save response to %s: %v", rule.Dest, err) | 258 | log.Printf("ERROR: middleware failed to save response to %s: %v", rule.Dest, err) |
| 259 | } else { | 259 | } else { |
| 260 | log.Printf("MIDDLEWARE saved %s%s -> %s", host, req.URL.Path, rule.Dest) | 260 | log.Printf("MIDDLEWARE saved %s%s -> %s", host, req.URL.Path, rule.Dest) |