saucelabs / forwarder

Forwarder is a production-ready, fast MITM proxy with PAC support. It's suitable for debugging, intercepting and manipulating HTTP traffic. It's used as a core component of Sauce Labs Sauce Connect Proxy.
https://forwarder-proxy.io
Mozilla Public License 2.0
202 stars 13 forks source link

martian: cancel request context when connection is closed #800

Open mmatczuk opened 2 months ago

mmatczuk commented 2 months ago

For Martian TCP listener implementation support request context cancelation when downstream connection dies.

Choraden commented 1 month ago

Determining when a connection dies is not a simple task.

For example http.Server starts background read after it consumed all the request body. If the read fails the context will be cancelled.

func (cr *connReader) backgroundRead() {
    n, err := cr.conn.rwc.Read(cr.byteBuf[:])
    cr.lock()
    if n == 1 {
        cr.hasByte = true
    }
    if ne, ok := err.(net.Error); ok && cr.aborted && ne.Timeout() {
        // Ignore this error. It's the expected error from
        // another goroutine calling abortPendingRead.
    } else if err != nil {
        cr.handleReadError(err)
    }
    cr.aborted = false
    cr.inRead = false
    cr.unlock()
    cr.cond.Broadcast()
}

// requestBodyRemains reports whether future calls to Read
// on rc might yield more data.
func requestBodyRemains(rc io.ReadCloser) bool {
    if rc == NoBody {
        return false
    }
    switch v := rc.(type) {
    case *expectContinueReader:
        return requestBodyRemains(v.readCloser)
    case *body:
        return v.bodyRemains()
    default:
        panic("unexpected type " + fmt.Sprintf("%T", rc))
    }
}
Choraden commented 1 month ago

The way it could be implemented is very similar to the http.Server approach. We need:

  1. http request body wrapper that would notify about the body being consumed in a round tripper
  2. conn wrapper that would have the functionality of a backgroundRead()