Skip to content

Commit 0add4dd

Browse files
7heotheo
authored andcommitted
Support CONNECT-only proxies with HTTP hosts
1 parent f59b0d2 commit 0add4dd

2 files changed

Lines changed: 80 additions & 1 deletion

File tree

http/download.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package http
22

33
import (
4+
"bufio"
45
"context"
56
"fmt"
67
"io"
@@ -33,11 +34,87 @@ type downloaderImpl struct {
3334
client *http.Client
3435
}
3536

37+
func ForceProxyConnectDialContext(ctx context.Context, network, addr string) (net.Conn, error) {
38+
39+
// Get the proxy URL
40+
dialURL, err := url.Parse(os.Getenv("HTTP_PROXY"))
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
// Connect to the proxy
46+
proxyTunnel, err := net.Dial("tcp", dialURL.Hostname()+":"+dialURL.Port())
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
// Send a CONNECT request
52+
connectReq := fmt.Sprintf("CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", addr, addr)
53+
if _, err := proxyTunnel.Write([]byte(connectReq)); err != nil {
54+
err = proxyTunnel.Close()
55+
if err != nil {
56+
return nil, err
57+
}
58+
return nil, err
59+
}
60+
61+
// Read the response to the CONNECT request
62+
br := bufio.NewReader(proxyTunnel)
63+
statusLine, err := br.ReadString('\n')
64+
if err != nil {
65+
err = proxyTunnel.Close()
66+
if err != nil {
67+
return nil, err
68+
}
69+
return nil, err
70+
}
71+
72+
// Verify the successful (HTTP 200) response
73+
if !strings.Contains(statusLine, "200") {
74+
err = proxyTunnel.Close()
75+
if err != nil {
76+
return nil, err
77+
}
78+
return nil, fmt.Errorf("proxy CONNECT failed: %s", statusLine)
79+
}
80+
81+
// Consume the headers until blank line
82+
for {
83+
line, err := br.ReadString('\n')
84+
if err != nil {
85+
err = proxyTunnel.Close()
86+
if err != nil {
87+
return nil, err
88+
}
89+
return nil, err
90+
}
91+
if line == "\r\n" {
92+
break
93+
}
94+
}
95+
96+
return proxyTunnel, nil
97+
}
98+
3699
// NewDownloader creates new instance of Downloader which specified number
37100
// of threads and download limit in bytes/sec
38101
func NewDownloader(downLimit int64, maxTries int, progress aptly.Progress) aptly.Downloader {
39102
transport := http.Transport{}
40103
transport.Proxy = http.DefaultTransport.(*http.Transport).Proxy
104+
if os.Getenv("HTTP_PROXY") != "" && strings.EqualFold(os.Getenv("HTTP_PROXY_FORCE_CONNECT"), "true") {
105+
// NOTE When defining both HTTP_PROXY and HTTP_PROXY_FORCE_CONNECT,
106+
// the Proxy function always returns a nil URL, thus disabling
107+
// Golang's internal Proxy facilities, even with HTTPS.
108+
//
109+
// However, the ForceProxyConnectDialContext works
110+
// independently of the destination's URL's scheme.
111+
//
112+
// In any case, it is probably better to only define
113+
// HTTP_PROXY_FORCE_CONNECT when using an http:// URL and not
114+
// with https:// ones.
115+
transport.DialContext = ForceProxyConnectDialContext
116+
transport.Proxy = func(*http.Request) (*url.URL, error) { return nil, nil }
117+
}
41118
transport.ResponseHeaderTimeout = 30 * time.Second
42119
transport.TLSHandshakeTimeout = http.DefaultTransport.(*http.Transport).TLSHandshakeTimeout
43120
transport.ExpectContinueTimeout = http.DefaultTransport.(*http.Transport).ExpectContinueTimeout

http/download_go17.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import (
88
)
99

1010
func initTransport(transport *http.Transport) {
11-
transport.DialContext = http.DefaultTransport.(*http.Transport).DialContext
11+
if transport.DialContext == nil {
12+
transport.DialContext = http.DefaultTransport.(*http.Transport).DialContext
13+
}
1214
transport.MaxIdleConns = http.DefaultTransport.(*http.Transport).MaxIdleConns
1315
transport.IdleConnTimeout = http.DefaultTransport.(*http.Transport).IdleConnTimeout
1416
}

0 commit comments

Comments
 (0)