Skip to content

Commit 11edfa2

Browse files
M09Icclaude
andcommitted
refactor(mitmproxy/proxy): remove RecordAddon, add built-in Flow.EndTime
- Delete record_addon.go — consumers write their own addon with BaseAddon + NewFlowRecord (trivial boilerplate, not library's job) - Add Flow.EndTime field, set by proxy engine before Response hooks - NewFlowRecord auto-computes Duration from EndTime - StartTime, eliminating the need for consumers to maintain their own timing maps utils/mitmproxy now exports exactly two new things: FlowRecord (type) + NewFlowRecord (constructor) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0636287 commit 11edfa2

5 files changed

Lines changed: 48 additions & 128 deletions

File tree

‎mitmproxy/proxy/flow.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ type Flow struct {
117117
Stream bool
118118
UseSeparateClient bool // use separate http client to send http request
119119
StartTime time.Time
120+
EndTime time.Time // set by proxy engine before Response/RequestError hooks
120121
done chan struct{}
121122
}
122123

‎mitmproxy/proxy/interceptor.go‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net"
99
"net/http"
1010
"strings"
11+
"time"
1112

1213
"github.com/chainreactors/utils/mitmproxy/cert"
1314
"github.com/chainreactors/utils/mitmproxy/helper"
@@ -728,6 +729,7 @@ func (a *interceptor) attack(res http.ResponseWriter, req *http.Request) {
728729
f.Stream = true
729730
} else {
730731
f.Response.Body = resBuf
732+
f.EndTime = time.Now()
731733

732734
// trigger addon event Response
733735
for _, addon := range proxy.Addons {

‎mitmproxy/proxy/record.go‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ func NewFlowRecord(f *Flow, maxBodySnip int) *FlowRecord {
3333
Timestamp: f.StartTime,
3434
}
3535

36+
if !f.EndTime.IsZero() {
37+
r.Duration = f.EndTime.Sub(f.StartTime)
38+
} else if !f.StartTime.IsZero() {
39+
r.Duration = time.Since(f.StartTime)
40+
}
41+
3642
if f.Request != nil {
3743
r.Method = f.Request.Method
3844
if f.Request.URL != nil {

‎mitmproxy/proxy/record_addon.go‎

Lines changed: 0 additions & 55 deletions
This file was deleted.

‎mitmproxy/proxy/record_test.go‎

Lines changed: 39 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,6 @@ func mustParseURL(raw string) *url.URL {
2121
return u
2222
}
2323

24-
func proxyClient(addr string) *http.Client {
25-
proxyURL, _ := url.Parse("http://" + addr)
26-
return &http.Client{
27-
Timeout: 5 * time.Second,
28-
Transport: &http.Transport{
29-
Proxy: http.ProxyURL(proxyURL),
30-
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
31-
},
32-
}
33-
}
34-
3524
func TestNewFlowRecord(t *testing.T) {
3625
f := newFlow()
3726
f.Request = &Request{
@@ -80,25 +69,23 @@ func TestSnipBytes(t *testing.T) {
8069
}
8170
}
8271

83-
func TestRecordAddon_CapturesFlows(t *testing.T) {
72+
func TestNewFlowRecord_Integration(t *testing.T) {
8473
var mu sync.Mutex
8574
var captured []*FlowRecord
8675

87-
addon := NewRecordAddon(func(r *FlowRecord) {
88-
mu.Lock()
89-
captured = append(captured, r)
90-
mu.Unlock()
91-
})
76+
addon := &captureAddon{
77+
onRecord: func(r *FlowRecord) {
78+
mu.Lock()
79+
captured = append(captured, r)
80+
mu.Unlock()
81+
},
82+
}
9283

93-
p, err := NewProxy(&Options{
94-
Addr: "127.0.0.1:0",
95-
SslInsecure: true,
96-
})
84+
p, err := NewProxy(&Options{Addr: "127.0.0.1:0", SslInsecure: true})
9785
if err != nil {
9886
t.Fatal(err)
9987
}
10088
p.AddAddon(addon)
101-
10289
addr, _, err := p.StartAsync()
10390
if err != nil {
10491
t.Fatal(err)
@@ -108,11 +95,18 @@ func TestRecordAddon_CapturesFlows(t *testing.T) {
10895
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
10996
w.Header().Set("Content-Type", "text/plain")
11097
w.WriteHeader(200)
111-
fmt.Fprint(w, "hello from target")
98+
fmt.Fprint(w, "hello")
11299
}))
113100
defer target.Close()
114101

115-
client := proxyClient(addr.String())
102+
proxyURL, _ := url.Parse("http://" + addr.String())
103+
client := &http.Client{
104+
Timeout: 5 * time.Second,
105+
Transport: &http.Transport{
106+
Proxy: http.ProxyURL(proxyURL),
107+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
108+
},
109+
}
116110
resp, err := client.Get(target.URL + "/test")
117111
if err != nil {
118112
t.Fatal(err)
@@ -124,63 +118,35 @@ func TestRecordAddon_CapturesFlows(t *testing.T) {
124118

125119
mu.Lock()
126120
defer mu.Unlock()
127-
128121
if len(captured) != 1 {
129-
t.Fatalf("expected 1 flow, got %d", len(captured))
122+
t.Fatalf("expected 1, got %d", len(captured))
130123
}
131124
r := captured[0]
132-
if r.Method != "GET" {
133-
t.Fatalf("method: expected GET, got %s", r.Method)
134-
}
135-
if r.StatusCode != 200 {
136-
t.Fatalf("status: expected 200, got %d", r.StatusCode)
137-
}
138-
if r.Duration <= 0 {
139-
t.Fatal("expected positive duration")
125+
if r.Method != "GET" || r.StatusCode != 200 || r.Duration <= 0 {
126+
t.Fatalf("unexpected record: method=%s status=%d dur=%v", r.Method, r.StatusCode, r.Duration)
140127
}
141-
if string(r.ResponseBody) != "hello from target" {
142-
t.Fatalf("body: expected 'hello from target', got %q", string(r.ResponseBody))
128+
if string(r.ResponseBody) != "hello" {
129+
t.Fatalf("body: %q", string(r.ResponseBody))
143130
}
144131
}
145132

146-
func TestRecordAddon_BodySnip(t *testing.T) {
147-
var mu sync.Mutex
148-
var captured []*FlowRecord
149-
150-
addon := NewRecordAddon(func(r *FlowRecord) {
151-
mu.Lock()
152-
captured = append(captured, r)
153-
mu.Unlock()
154-
})
155-
addon.MaxBodySnip = 5
156-
157-
p, err := NewProxy(&Options{Addr: "127.0.0.1:0", SslInsecure: true})
158-
if err != nil {
159-
t.Fatal(err)
160-
}
161-
p.AddAddon(addon)
162-
addr, _, _ := p.StartAsync()
163-
defer p.Shutdown(context.Background())
164-
165-
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
166-
fmt.Fprint(w, "1234567890")
167-
}))
168-
defer target.Close()
133+
// captureAddon is a minimal consumer-side addon using BaseAddon + NewFlowRecord.
134+
type captureAddon struct {
135+
BaseAddon
136+
pending sync.Map
137+
onRecord func(*FlowRecord)
138+
}
169139

170-
resp, err := proxyClient(addr.String()).Get(target.URL)
171-
if err != nil {
172-
t.Fatal(err)
173-
}
174-
io.ReadAll(resp.Body)
175-
resp.Body.Close()
176-
time.Sleep(100 * time.Millisecond)
140+
func (a *captureAddon) Requestheaders(f *Flow) {
141+
a.pending.Store(f.Id.String(), time.Now())
142+
}
177143

178-
mu.Lock()
179-
defer mu.Unlock()
180-
if len(captured) != 1 {
181-
t.Fatalf("expected 1 flow, got %d", len(captured))
182-
}
183-
if len(captured[0].ResponseBody) != 5 {
184-
t.Fatalf("expected body snip=5, got %d", len(captured[0].ResponseBody))
144+
func (a *captureAddon) Response(f *Flow) {
145+
var dur time.Duration
146+
if v, ok := a.pending.LoadAndDelete(f.Id.String()); ok {
147+
dur = time.Since(v.(time.Time))
185148
}
149+
r := NewFlowRecord(f, 0)
150+
r.Duration = dur
151+
a.onRecord(r)
186152
}

0 commit comments

Comments
 (0)