-
Notifications
You must be signed in to change notification settings - Fork 68
feature: Add support for HTTP_PROXY, HTTPS_PROXY, and NO_PROXY #1260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9c89bc5
8cac467
b6ce714
6a1fe69
ba2973c
a18649f
aa3a316
20709ff
af44dd2
7f24ce7
b201294
ef63d4d
9b66453
3272fbc
1bbcc58
c91176d
05a9f01
f124df6
aca46f1
3820f62
13bab52
7ff4bd6
7a10ad3
99f475a
35b8f7c
2957e62
3e8c72d
70070ab
70b1f0f
77ffe16
41c65e6
b9aa6dc
7831cc9
73fb824
872fedf
2445d4e
7e37fb9
6aa57c0
9ca8fe1
7a74eb7
531566b
8a5477e
cd0bd79
99ad843
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,8 @@ | ||
| package api | ||
|
|
||
| import ( | ||
| "bufio" | ||
| "context" | ||
| "crypto/tls" | ||
| "encoding/base64" | ||
| "fmt" | ||
| "net" | ||
| "net/http" | ||
| "net/url" | ||
|
|
@@ -15,22 +12,22 @@ import ( | |
| // | ||
| // Note: baseTransport is considered to be a clone created with transport.Clone() | ||
| // | ||
| // - If a the proxyPath is not empty, a unix socket proxy is created. | ||
| // - Otherwise, the proxyURL is used to determine if we should proxy socks5 / http connections | ||
| // - If proxyPath is not empty, a unix socket proxy is created. | ||
| // - Otherwise, proxyURL is used to determine if we should proxy socks5 / http connections | ||
| func withProxyTransport(baseTransport *http.Transport, proxyURL *url.URL, proxyPath string) *http.Transport { | ||
| handshakeTLS := func(ctx context.Context, conn net.Conn, addr string) (net.Conn, error) { | ||
| // Extract the hostname (without the port) for TLS SNI | ||
| host, _, err := net.SplitHostPort(addr) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| tlsConn := tls.Client(conn, &tls.Config{ | ||
| ServerName: host, | ||
| // Pull InsecureSkipVerify from the target host transport | ||
| // so that insecure-skip-verify flag settings are honored for the proxy server | ||
| InsecureSkipVerify: baseTransport.TLSClientConfig.InsecureSkipVerify, | ||
| }) | ||
| cfg := baseTransport.TLSClientConfig.Clone() | ||
| if cfg.ServerName == "" { | ||
| cfg.ServerName = host | ||
| } | ||
| tlsConn := tls.Client(conn, cfg) | ||
| if err := tlsConn.HandshakeContext(ctx); err != nil { | ||
| tlsConn.Close() | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is probably important - closing the connection on error. Should be closed when the program terminates, but explicitly closing now is more safe. |
||
| return nil, err | ||
peterguy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| return tlsConn, nil | ||
|
|
@@ -53,82 +50,7 @@ func withProxyTransport(baseTransport *http.Transport, proxyURL *url.URL, proxyP | |
| // clear out any system proxy settings | ||
| baseTransport.Proxy = nil | ||
| } else if proxyURL != nil { | ||
| switch proxyURL.Scheme { | ||
| case "socks5", "socks5h": | ||
| // SOCKS proxies work out of the box - no need to manually dial | ||
| baseTransport.Proxy = http.ProxyURL(proxyURL) | ||
| case "http", "https": | ||
| dial := func(ctx context.Context, network, addr string) (net.Conn, error) { | ||
| // Dial the proxy | ||
| d := net.Dialer{} | ||
| conn, err := d.DialContext(ctx, "tcp", proxyURL.Host) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| // this is the whole point of manually dialing the HTTP(S) proxy: | ||
| // being able to force HTTP/1. | ||
| // When relying on Transport.Proxy, the protocol is always HTTP/2, | ||
| // but many proxy servers don't support HTTP/2. | ||
| // We don't want to disable HTTP/2 in general because we want to use it when | ||
| // connecting to the Sourcegraph API, using HTTP/1 for the proxy connection only. | ||
| protocol := "HTTP/1.1" | ||
|
|
||
| // CONNECT is the HTTP method used to set up a tunneling connection with a proxy | ||
| method := "CONNECT" | ||
|
|
||
| // Manually writing out the HTTP commands because it's not complicated, | ||
| // and http.Request has some janky behavior: | ||
| // - ignores the Proto field and hard-codes the protocol to HTTP/1.1 | ||
| // - ignores the Host Header (Header.Set("Host", host)) and uses URL.Host instead. | ||
| // - When the Host field is set, overrides the URL field | ||
| connectReq := fmt.Sprintf("%s %s %s\r\n", method, addr, protocol) | ||
|
|
||
| // A Host header is required per RFC 2616, section 14.23 | ||
| connectReq += fmt.Sprintf("Host: %s\r\n", addr) | ||
|
|
||
| // use authentication if proxy credentials are present | ||
| if proxyURL.User != nil { | ||
| password, _ := proxyURL.User.Password() | ||
| auth := base64.StdEncoding.EncodeToString([]byte(proxyURL.User.Username() + ":" + password)) | ||
| connectReq += fmt.Sprintf("Proxy-Authorization: Basic %s\r\n", auth) | ||
| } | ||
|
|
||
| // finish up with an extra carriage return + newline, as per RFC 7230, section 3 | ||
| connectReq += "\r\n" | ||
|
|
||
| // Send the CONNECT request to the proxy to establish the tunnel | ||
| if _, err := conn.Write([]byte(connectReq)); err != nil { | ||
| conn.Close() | ||
| return nil, err | ||
| } | ||
|
|
||
| // Read and check the response from the proxy | ||
| resp, err := http.ReadResponse(bufio.NewReader(conn), nil) | ||
| if err != nil { | ||
| conn.Close() | ||
| return nil, err | ||
| } | ||
| if resp.StatusCode != http.StatusOK { | ||
| conn.Close() | ||
| return nil, fmt.Errorf("failed to connect to proxy %v: %v", proxyURL, resp.Status) | ||
| } | ||
| resp.Body.Close() | ||
| return conn, nil | ||
| } | ||
| dialTLS := func(ctx context.Context, network, addr string) (net.Conn, error) { | ||
| // Dial the underlying connection through the proxy | ||
| conn, err := dial(ctx, network, addr) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return handshakeTLS(ctx, conn, addr) | ||
| } | ||
| baseTransport.DialContext = dial | ||
| baseTransport.DialTLSContext = dialTLS | ||
| // clear out any system proxy settings | ||
| baseTransport.Proxy = nil | ||
| } | ||
| baseTransport.Proxy = http.ProxyURL(proxyURL) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Turns out, the whole custom dialer was breaking the ability to connect through TLS-enabled proxies. Simply removing it and just adding the proxy to the transport cleared everything up. |
||
| } | ||
|
|
||
| return baseTransport | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not entirely necessary, but cloning the client config preserves all of the settings, like insecureskipverify, automatically.