diff options
Diffstat (limited to 'internal/proxy')
| -rw-r--r-- | internal/proxy/capture.go | 14 | ||||
| -rw-r--r-- | internal/proxy/proxy.go | 14 | ||||
| -rw-r--r-- | internal/proxy/repeat.go | 5 |
3 files changed, 24 insertions, 9 deletions
diff --git a/internal/proxy/capture.go b/internal/proxy/capture.go index ccc95e9..ec4dc8d 100644 --- a/internal/proxy/capture.go +++ b/internal/proxy/capture.go @@ -59,14 +59,12 @@ func captureRequest(r *http.Request, tee *teeConn, bodyCap *cappedTee) (raw []by return buf.Bytes(), false } -// captureResponse mirrors captureRequest for the upstream leg: exact -// wire bytes when tee is non-nil (upstream negotiated HTTP/1.1), -// otherwise a reconstruction. -func captureResponse(resp *http.Response, tee *teeConn, bodyCap *cappedTee) (raw []byte, exact bool) { - if tee != nil { - return tee.Take(), true - } - +// captureResponse reconstructs raw response bytes from the parsed +// response for the HTTP/2 upstream case - the exact-capture path +// (HTTP/1.1 upstream) is handled directly in forward() via the +// teeConn's own Take(), which is the only reason this one doesn't also +// need a *teeConn parameter. +func captureResponse(resp *http.Response, bodyCap *cappedTee) (raw []byte, exact bool) { dump := *resp if bodyCap != nil { dump.Body = io.NopCloser(bytes.NewReader(bodyCap.buf.Bytes())) diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index fcf8c14..014c601 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -38,6 +38,10 @@ import ( "mitmux/internal/store" ) +// upstreamTimeout bounds the write-request/read-response phase of an +// upstream exchange, once dialing has already succeeded. +const upstreamTimeout = 60 * time.Second + // hopByHopHeaders are stripped before forwarding a request or response, // per RFC 7230 6.1 - they are meaningful only between a client and its // immediate next hop, not end-to-end. @@ -259,6 +263,14 @@ func (s *Server) forward(dial dialer, scheme, hostname string, w http.ResponseWr return } defer conn.Close() + // The dial itself is bounded (net.Dialer.Timeout / HandshakeContext); + // without this, a server that accepts the connection and then never + // writes or never finishes writing would hang the request forever - + // there's no other timeout covering the write-request/read-response + // phase. Bounds the whole exchange, so a legitimately slow multi- + // minute transfer would also get cut off; a fixed default is enough + // for now, not worth a config surface yet. + conn.SetDeadline(time.Now().Add(upstreamTimeout)) var resp *http.Response var upstreamTee *teeConn @@ -299,7 +311,7 @@ func (s *Server) forward(dial dialer, scheme, hostname string, w http.ResponseWr if upstreamTee != nil { respRaw, respExact = upstreamTee.Take(), true } else { - respRaw, respExact = captureResponse(resp, nil, respBodyCap) + respRaw, respExact = captureResponse(resp, respBodyCap) } s.record(started, duration, scheme, hostname, r, reqRaw, reqExact, respRaw, respExact, resp.StatusCode, "") diff --git a/internal/proxy/repeat.go b/internal/proxy/repeat.go index 6373ee1..cecf481 100644 --- a/internal/proxy/repeat.go +++ b/internal/proxy/repeat.go @@ -33,6 +33,11 @@ func (s *Server) Repeat(ctx context.Context, scheme, host string, raw []byte) (* return s.recordRepeat(started, time.Since(started), scheme, host, method, path, raw, nil, 0, err.Error()) } defer conn.Close() + // See the matching comment in forward(): without this, a hung + // server - or a user-edited request malformed enough that nothing + // ever replies - blocks this Repeat call, and the IPC connection + // handling it, forever. + conn.SetDeadline(time.Now().Add(upstreamTimeout)) if _, err := conn.Write(raw); err != nil { return s.recordRepeat(started, time.Since(started), scheme, host, method, path, raw, nil, 0, err.Error()) |