Skip to content

Instantly share code, notes, and snippets.

@uzxmx
Created March 5, 2022 02:38
Show Gist options
  • Save uzxmx/076d15f7335719d332112847b8cf89a5 to your computer and use it in GitHub Desktop.
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.
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()
}
@uzxmx
Copy link
Author

uzxmx commented Mar 5, 2022

The original issue was at here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment