diff --git a/fasthttpproxy/proxy_env.go b/fasthttpproxy/proxy_env.go new file mode 100644 index 0000000..c5abe4a --- /dev/null +++ b/fasthttpproxy/proxy_env.go @@ -0,0 +1,108 @@ +package fasthttpproxy + +import ( + "bufio" + "encoding/base64" + "fmt" + "net" + "net/url" + "sync" + "time" + + "golang.org/x/net/http/httpproxy" + + "github.com/valyala/fasthttp" +) + +// FasthttpProxyHTTPDialer returns a fasthttp.DialFunc that dials using +// the the env(HTTP_PROXY, HTTPS_PROXY and NO_PROXY) configured HTTP proxy. +// +// Example usage: +// c := &fasthttp.Client{ +// Dial: FasthttpProxyHTTPDialer(), +// } +func FasthttpProxyHTTPDialer() fasthttp.DialFunc { + return FasthttpProxyHTTPDialerTimeout(0) +} + +// FasthttpProxyHTTPDialer returns a fasthttp.DialFunc that dials using +// the env(HTTP_PROXY, HTTPS_PROXY and NO_PROXY) configured HTTP proxy using the given timeout. +// +// Example usage: +// c := &fasthttp.Client{ +// Dial: FasthttpProxyHTTPDialerTimeout(time.Second * 2), +// } +func FasthttpProxyHTTPDialerTimeout(timeout time.Duration) fasthttp.DialFunc { + proxier := httpproxy.FromEnvironment().ProxyFunc() + + // map on proxy URL and its encoded auth barrier + authBarriers := map[*url.URL]string{} + authBarriersLock := sync.RWMutex{} + + return func(addr string) (net.Conn, error) { + + proxyURL, err := proxier(&url.URL{Host: addr}) + if err != nil { + return nil, err + } + + if proxyURL == nil { + if timeout == 0 { + return fasthttp.Dial(addr) + } + return fasthttp.DialTimeout(addr, timeout) + } + + var conn net.Conn + if timeout == 0 { + conn, err = fasthttp.Dial(proxyURL.String()) + } else { + conn, err = fasthttp.DialTimeout(proxyURL.String(), timeout) + } + if err != nil { + return nil, err + } + + req := "CONNECT " + addr + " HTTP/1.1\r\n" + if proxyURL.User != nil { + authBarriersLock.RLock() + barrier, ok := authBarriers[proxyURL] + authBarriersLock.RUnlock() + + if !ok { + barrier = base64.StdEncoding.EncodeToString([]byte(proxyURL.User.String())) + authBarriersLock.Lock() + authBarriers[proxyURL] = barrier + authBarriersLock.Unlock() + } + + req += "Proxy-Authorization: Basic " + barrier + "\r\n" + } + req += "\r\n" + + if _, err := conn.Write([]byte(req)); err != nil { + return nil, err + } + + res := fasthttp.AcquireResponse() + defer fasthttp.ReleaseResponse(res) + + res.SkipBody = true + + if err := res.Read(bufio.NewReader(conn)); err != nil { + if connErr := conn.Close(); connErr != nil { + return nil, fmt.Errorf("conn close err %v followed by read conn err %w", connErr, err) + } + return nil, err + } + if res.Header.StatusCode() != 200 { + if connErr := conn.Close(); connErr != nil { + return nil, fmt.Errorf( + "conn close err %v followed by connect to proxy: code: %d body %s", + connErr, res.StatusCode(), string(res.Body())) + } + return nil, fmt.Errorf("could not connect to proxy: code: %d body %s", res.StatusCode(), string(res.Body())) + } + return conn, nil + } +} diff --git a/go.mod b/go.mod index 8140dd6..5908bb6 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/klauspost/compress v1.10.7 github.com/valyala/bytebufferpool v1.0.0 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a - golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 - golang.org/x/net v0.0.0-20200602114024-627f9648deb9 - golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 + golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f ) diff --git a/go.sum b/go.sum index b743c0c..86b6314 100644 --- a/go.sum +++ b/go.sum @@ -8,10 +8,17 @@ github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6Jc github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 h1:5kGOVHlq0euqwzgTC9Vu15p6fV1Wi0ArVi8da2urnVg= +golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=