elazarl / goproxy

An HTTP proxy library for Go
BSD 3-Clause "New" or "Revised" License
6.02k stars 1.09k forks source link

Set Transport and Dial function per request #217

Open winteraz opened 7 years ago

winteraz commented 7 years ago

I would like to set a different http.Transport and ConnectDial function based on the request received(e.g. within a DoFunc) . How can I do that? Currently it seems they can be set only globally which makes the whole package useless for my use case.

rahulwa commented 7 years ago

You can do something like

proxy.OnRequest().DoFunc(func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
        proxy.Tr = &http.Transport{...}
        return r, nil
    })
winteraz commented 7 years ago

Isn't that a race condition? What about the other clients that make requests at the same time(each with its own/different transport). Isn't this overwriting their Transport?

rahulwa commented 7 years ago

maybe @elazarl can answer it better.

elazarl commented 7 years ago

-which race condition? I'm not sure I understands

On Thu, Apr 13, 2017, 3:53 PM Rahul Sinha notifications@github.com wrote:

maybe @elazarl https://github.com/elazarl can answer it better.

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/elazarl/goproxy/issues/217#issuecomment-293886844, or mute the thread https://github.com/notifications/unsubscribe-auth/AAP4omg8mwrd_a4-wo4cpjdH46q7i59fks5rvhrOgaJpZM4M8Jl9 .

winteraz commented 7 years ago

ConnectDial and Tr are global values of *ProxyHttpServer. If multiple go routines are reading and writing these fields concurrently as @rahulwa proposed that's a race condition, right? Not to mention that one request could modify ConnectDial and Tr for another request that is running concurrently so you can't really set a different Tr because you have no guarantee that it won't be changed by another go routine before the request is processed. Does it make sense to you?

winteraz commented 7 years ago

In order to fix this I think Transport and ConnectDial should be copied on each request and provided perhaps in *goproxy.ProxyCtx though I'm not very familiar with the code base so I can't say for sure. I

winteraz commented 7 years ago

@elazarl I also see a RoundTripper on *goproxy.ProxyCtx but I don't know when or how is being used because it's not documented. Can I use a custom RoundTripper to process requests? Is ConnectDial or Tr from ProxyHttpServer still used if I use a custom RoundTripper? What I need is to use a different transport for each request. That's because based on the request content(headers) I would like to use a different dialer(i.e. dial through a SOCKS5).

elazarl commented 7 years ago

Yes, this is the way to do that

On Thu, Apr 13, 2017, 5:27 PM winteraz notifications@github.com wrote:

@elazarl https://github.com/elazarl I also see a RoundTripper on *goproxy.ProxyCtx but I don't know when or how is being used because it's not documented. Can I use a custom RoundTripper to process requests? Is ConnectDial or Tr from ProxyHttpServer still used if I use a custom RoundTripper? What I need is to use a different transport for each request. That's because based on the request content(headers) I would like to use a different dialer(i.e. dial through a SOCKS5).

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/elazarl/goproxy/issues/217#issuecomment-293911257, or mute the thread https://github.com/notifications/unsubscribe-auth/AAP4ogD-cwYvcSd1S5GqgU4HUvIiJJGSks5rvjDMgaJpZM4M8Jl9 .

elazarl commented 7 years ago

@qZanity doesn't RoundTripper work for you?

On Tue, Apr 25, 2017 at 9:09 AM, qZanity notifications@github.com wrote:

@winteraz https://github.com/winteraz Did you find a good solution?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/elazarl/goproxy/issues/217#issuecomment-297081247, or mute the thread https://github.com/notifications/unsubscribe-auth/AAP4ovd7aw1Mvgu-KrNL_RWVbo3hGEJaks5rzhrEgaJpZM4M8Jl9 .

mikegleasonjr commented 5 years ago

If anyone comes here, I have an example to use a custom roundtripper for each request:

rt := goproxy.RoundTripperFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Response, error) {
    return custom.RoundTrip(req)
})

rp.OnRequest().HandleConnect(goproxy.AlwaysMitm)
rp.OnRequest().DoFunc(
    func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
        ctx.RoundTripper = rt
        return r, nil
    })
xzycn commented 2 years ago

@mikegleasonjr

If anyone comes here, I have an example to use a custom roundtripper for each request:

rt := goproxy.RoundTripperFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Response, error) {
  return custom.RoundTrip(req)
})

rp.OnRequest().HandleConnect(goproxy.AlwaysMitm)
rp.OnRequest().DoFunc(
  func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
      ctx.RoundTripper = rt
      return r, nil
  })

this way will return a self-sigined certificate created by goproxy , our client must skip tls v2rify.Is there a way to return certificate of target site? I have tested with below code:

proxyServer.ConnectDial = proxyServer.NewConnectDialToProxyWithHandler(https_proxy, func(req *http.Request) {
        SetBasicAuth(username, password, req)
    })

this way can return a certificate of target site, but can only set a proxy once,I want to set a proxy per request :(

c3l3si4n commented 1 year ago

Hello, I have some news on this issue. By adding the following function to https.go, I managed to add a callback that will dinamically set the ConnectDial proxy.

I had to implement this, because AlwaysMITM is too slow and made my CPU usage spike to 100% on thousands of requests. This is a more light implementation if you don't need MITM or dont want to break the TLS.

func (proxy *ProxyHttpServer) CustomHTTPDialer(https_proxy func(req *http.Request) string, connectReqHandler func(req *http.Request)) func(network, addr string) (net.Conn, error) {

    return func(network, addr string) (net.Conn, error) {
        connectReq := &http.Request{
            Method: "CONNECT",
            URL:    &url.URL{Opaque: addr},
            Host:   addr,
            Header: make(http.Header),
        }
        u, err := url.Parse(https_proxy(connectReq))
        if err != nil {
            log.Fatal(err)
        }
        if connectReqHandler != nil {
            connectReqHandler(connectReq)
        }
        c, err := proxy.dial(network, u.Host)
        if err != nil {

            return nil, err
        }
        connectReq.Write(c)
        // Read response.
        // Okay to use and discard buffered reader here, because
        // TLS server will not speak until spoken to.
        br := bufio.NewReader(c)
        resp, err := http.ReadResponse(br, connectReq)
        if err != nil {
            c.Close()
            return nil, err
        }
        defer resp.Body.Close()
        if resp.StatusCode != 200 {
            resp, err := ioutil.ReadAll(resp.Body)
            if err != nil {
                return nil, err
            }
            c.Close()
            return nil, errors.New("proxy refused connection" + string(resp))
        }
        return c, nil
    }

}

You can implement it on your program by doing:

    proxy.ConnectDial = proxy.CustomHTTPDialer(yourOwnProxyCallback, nil)

I have tested it and it seems to be working!