goproxy icon indicating copy to clipboard operation
goproxy copied to clipboard

is there any way to change upstream proxy each request

Open vlike opened this issue 4 years ago • 13 comments

i have try this below but not worked:

proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (request *http.Request, response *http.Response) {

    proxy.Tr = &http.Transport{Proxy: func(request *http.Request) (url *url.URL, e error) {
        ip := GetNewProxy()
        fmt.Println("======New ip:", ip)
        return url.Parse(ip)
    }}

    return req, nil
})

vlike avatar Oct 04 '19 07:10 vlike

You can set a transport per request IIRC

elazarl avatar Oct 04 '19 13:10 elazarl

You can set a transport per request IIRC

Hey there, do you have an example for this? I've been banging my head against my keyboard trying to figure it out and can't seem to find a way to assign a transport to a specific request. I can attach it to the server, but with concurrent connections that seems like it's asking for trouble.

Thanks!

Pilfer avatar Oct 25 '19 16:10 Pilfer

You could write a Custom Transport: https://github.com/elazarl/goproxy/blob/aa519ddbe484d5dddfd1a4056f90aa2b6cbc99cf/proxy.go#L28

With a custom proxy function which parses a different proxy IP each time an HTTP request is executed

azak-azkaran avatar Nov 27 '19 08:11 azak-azkaran

You can use ProxyCtx.RoundTripper for this:

proxy.OnRequest().DoFunc(func (req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
    ctx.RoundTripper = goproxy.RoundTripperFunc(func (req *http.Request, ctx *goproxy.ProxyCtx) (*http.Response, error) {
        // create transport and RoundTrip here
    })
    return req, nil
})

Beware that this will work for HTTP connections but not for HTTPS!

To make this work for HTTPS, you'll need to create a custom ConnectDial implementation. I'm currently working on this in a private project.

axelrindle avatar Oct 18 '23 15:10 axelrindle

@axelrindle can you share what you do in your project? needed that feature! Thank you.

chekun avatar Nov 28 '23 11:11 chekun

@chekun Have a look at this file: https://github.com/axelrindle/proxyguy/blob/main/server/server.go

This is currently at a very early stage of implementation.

axelrindle avatar Nov 28 '23 12:11 axelrindle

@axelrindle Thank you.

FYI, ConnectDialWithReq function solved my problem.

chekun avatar Nov 30 '23 03:11 chekun

@axelrindle Thank you.

FYI, ConnectDialWithReq function solved my problem.

Could you please provide an example for how you used ConnectDialWithReq? I'm also stuck on this.

rosahaj avatar Jan 07 '24 21:01 rosahaj

Here you go @rosahaj

proxyServer.ConnectDialWithReq = func(req *http.Request, network, addr string) (net.Conn, error) {
  if strings.Contains(req.URL.Path, "somethingyouwant") {
	  return proxyServer.NewConnectDialToProxyWithHandler("http://proxy1.com", func(req *http.Request) {
	  })(network, addr)
  } else {
	  return proxyServer.NewConnectDialToProxyWithHandler("http://proxy2.com", func(req *http.Request) {})(network, addr)
  }
}

chekun avatar Jan 08 '24 08:01 chekun

Here you go @rosahaj

proxyServer.ConnectDialWithReq = func(req *http.Request, network, addr string) (net.Conn, error) {
  if strings.Contains(req.URL.Path, "somethingyouwant") {
	  return proxyServer.NewConnectDialToProxyWithHandler("http://proxy1.com", func(req *http.Request) {
	  })(network, addr)
  } else {
	  return proxyServer.NewConnectDialToProxyWithHandler("http://proxy2.com", func(req *http.Request) {})(network, addr)
  }
}

Thanks for sharing your example. Unfortunately I still can't get it to work, seemingly ConnectDialWithReq simply isn't called:

package main

import (
	"log"
	"net"
	"net/http"
	"net/url"
	"regexp"

	"github.com/elazarl/goproxy"
)

func main() {
	proxyServer := goproxy.NewProxyHttpServer()

	secondProxyServer := goproxy.NewProxyHttpServer()
	secondProxyServer.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
		println("Second proxy received request")

		return req, nil
	})
	go http.ListenAndServe(":8090", secondProxyServer)

	proxyServer.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))).
		HandleConnect(goproxy.AlwaysMitm)

	proxyServer.Tr.Proxy = func(req *http.Request) (*url.URL, error) {
		println("Tr.Proxy was called")

		return url.Parse("http://localhost:8090/")
	}

	proxyServer.ConnectDialWithReq = func(req *http.Request, network, addr string) (net.Conn, error) {
		println("ConnectDialWithReq was called")

		return proxyServer.NewConnectDialToProxyWithHandler("http://localhost:8090/", func(req *http.Request) {})(network, addr)
	}

	log.Fatal(http.ListenAndServe(":8080", proxyServer))
}

Only Tr.Proxy was called gets printed. Requests are never passed to the second proxy.

Edit:

Leaving out this line:

proxyServer.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))).
		HandleConnect(goproxy.AlwaysMitm)

results in ConnectDialWithReq being called, but req.URL is nil and the second proxy still isn't used.

rosahaj avatar Jan 08 '24 10:01 rosahaj

@rosahaj Since you turn on Mitm, you don't need ConnectDialWithReq anymore ,just put your logic in proxyServer.Tr.Proxy

proxyServer.Tr.Proxy = func(req *http.Request) (*url.URL, error) {
		println("Tr.Proxy was called")
                if (some condition) {
                    return url.Parse("http://localhost:8290/")
                }
		return url.Parse("http://localhost:8090/")
	}

chekun avatar Jan 09 '24 07:01 chekun

@chekun Unfortunately setting Tr.Proxy doesn't ensure the request goes through the returned proxy URL:

package main

import (
	"fmt"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strings"

	"github.com/elazarl/goproxy"
)

func logOnNewConnection(proxyName string) {
	fmt.Printf("New connection on proxy: %s", proxyName)
}

func main() {
	entryProxyServer := goproxy.NewProxyHttpServer()
	firstChoiceProxyServer := goproxy.NewProxyHttpServer()
	secondChoiceProxyServer := goproxy.NewProxyHttpServer()

	firstChoiceProxyServer.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
		logOnNewConnection("First choice")
		return req, nil
	})
	secondChoiceProxyServer.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
		logOnNewConnection("First choice")
		return req, nil
	})

	go http.ListenAndServe(":8081", firstChoiceProxyServer)
	go http.ListenAndServe(":8082", secondChoiceProxyServer)

	// Enable MITM
	entryProxyServer.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))).
		HandleConnect(goproxy.AlwaysMitm)

	entryProxyServer.Tr.Proxy = func(req *http.Request) (*url.URL, error) {
		println("Tr.Proxy was called")

		if strings.Contains(req.URL.Path, "somethingyouwant") {
			println("Proxying to first choice")
			return url.Parse("http://localhost:8081/")
		} else {
			println("Proxying to second choice")
			return url.Parse("http://localhost:8082/")
		}
	}

	log.Fatal(http.ListenAndServe(":8080", entryProxyServer))
}

Output:

Tr.Proxy was called
Proxying to first choice

The only way I was able to get it to work at all is by implementing a custom RoundTripper, but this defeats the benefits of using goproxy in the first place.

rosahaj avatar Jan 10 '24 00:01 rosahaj

I'm not sure, in my case it all works fine.

chekun avatar Jan 10 '24 08:01 chekun