Created
March 5, 2022 02:38
-
-
Save uzxmx/076d15f7335719d332112847b8cf89a5 to your computer and use it in GitHub Desktop.
Forward http(s) requests from one http(s) proxy to another http(s) proxy. Both http and https are supported.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"crypto/tls" | |
"encoding/base64" | |
"errors" | |
"github.com/elazarl/goproxy" | |
"io" | |
"io/ioutil" | |
"log" | |
"net" | |
"net/http" | |
"net/url" | |
"strings" | |
"sync" | |
) | |
const urlString = "https://username:password@another-proxy-host:another-proxy-port" | |
// const urlString = "http://username:password@another-proxy-host:another-proxy-port" | |
// const urlString = "https://another-proxy-host:another-proxy-port" | |
// const urlString = "http://another-proxy-host:another-proxy-port" | |
func main() { | |
proxyURL, err := url.Parse(urlString) | |
if err != nil { | |
log.Fatal(err) | |
} | |
proxy := goproxy.NewProxyHttpServer() | |
proxy.Tr = &http.Transport{ | |
Proxy: http.ProxyURL(proxyURL), | |
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, | |
} | |
connectDialer := newConnectDialer(proxy, proxyURL) | |
proxy.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { | |
return &goproxy.ConnectAction{ | |
Action: goproxy.ConnectHijack, | |
TLSConfig: goproxy.TLSConfigFromCA(&goproxy.GoproxyCa), | |
Hijack: func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) { | |
targetSiteCon, err := connectDialer("tcp", getAddrFromURL(req.URL)) | |
if err != nil { | |
httpError(client, ctx, err) | |
return | |
} | |
client.Write([]byte("HTTP/1.0 200 OK\r\n\r\n")) | |
targetTCP, targetOK := targetSiteCon.(halfClosable) | |
proxyClientTCP, clientOK := client.(halfClosable) | |
if targetOK && clientOK { | |
go copyAndClose(ctx, targetTCP, proxyClientTCP) | |
go copyAndClose(ctx, proxyClientTCP, targetTCP) | |
} else { | |
go func() { | |
var wg sync.WaitGroup | |
wg.Add(2) | |
go copyOrWarn(ctx, targetSiteCon, client, &wg) | |
go copyOrWarn(ctx, client, targetSiteCon, &wg) | |
wg.Wait() | |
client.Close() | |
targetSiteCon.Close() | |
}() | |
} | |
}, | |
}, host | |
}) | |
proxy.Verbose = true | |
log.Fatal(http.ListenAndServe(":8080", proxy)) | |
} | |
func getAddrFromURL(u *url.URL) string { | |
addr := u.Host | |
if strings.IndexRune(addr, ':') == -1 { | |
if u.Scheme == "" || u.Scheme == "http" { | |
addr += ":80" | |
} else if u.Scheme == "https" { | |
addr += ":443" | |
} | |
} | |
return addr | |
} | |
func newConnectDialer(proxy *goproxy.ProxyHttpServer, u *url.URL) func(network, addr string) (net.Conn, error) { | |
proxyAddr := getAddrFromURL(u) | |
username := u.User.Username() | |
password, _ := u.User.Password() | |
var proxyAuthorization string | |
if len(username) > 0 || len(password) > 0 { | |
proxyAuthorization = "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) | |
} | |
isTLS := u.Scheme == "https" || u.Scheme == "wss" | |
return func(network, addr string) (net.Conn, error) { | |
connectReq := &http.Request{ | |
Method: "CONNECT", | |
URL: &url.URL{Opaque: addr}, | |
Host: addr, | |
Header: make(http.Header), | |
} | |
if len(proxyAuthorization) > 0 { | |
connectReq.Header.Set("Proxy-Authorization", proxyAuthorization) | |
} | |
c, err := net.Dial(network, proxyAddr) | |
if err != nil { | |
return nil, err | |
} | |
if isTLS { | |
c = tls.Client(c, proxy.Tr.TLSClientConfig) | |
} | |
connectReq.Write(c) | |
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 | |
} | |
} | |
func httpError(w io.WriteCloser, ctx *goproxy.ProxyCtx, err error) { | |
if _, err := io.WriteString(w, "HTTP/1.1 502 Bad Gateway\r\n\r\n"); err != nil { | |
ctx.Warnf("Error responding to client: %s", err) | |
} | |
if err := w.Close(); err != nil { | |
ctx.Warnf("Error closing client connection: %s", err) | |
} | |
} | |
type halfClosable interface { | |
net.Conn | |
CloseWrite() error | |
CloseRead() error | |
} | |
func copyAndClose(ctx *goproxy.ProxyCtx, dst, src halfClosable) { | |
if _, err := io.Copy(dst, src); err != nil { | |
ctx.Warnf("Error copying to client: %s", err) | |
} | |
dst.CloseWrite() | |
src.CloseRead() | |
} | |
func copyOrWarn(ctx *goproxy.ProxyCtx, dst io.Writer, src io.Reader, wg *sync.WaitGroup) { | |
if _, err := io.Copy(dst, src); err != nil { | |
ctx.Warnf("Error copying to client: %s", err) | |
} | |
wg.Done() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The original issue was at here.