Skip to content

Commit 6850769

Browse files
committed
sockets: add TCPProxyFromEnvironment to keep pre-go1.16 behavior
TCPProxyFromEnvironment wraps http.ProxyFromEnvironment, to preserve the pre-go1.16 behavior for URLs using the 'tcp://' scheme. For other schemes, golang's standard behavior is preserved (and depends on the Go version used). Prior to go1.16, `https://` schemes would use HTTPS_PROXY, and any other scheme would use HTTP_PROXY. However, golang/net@7b1cca2 (per a request in golang/go#40909) changed this behavior to only use HTTP_PROXY for `http://` schemes, no longer using a proxy for any other scheme. Docker uses the `tcp://` scheme as a default for API connections, to indicate that the API is not "purely" HTTP. Various parts in the code also *require* this scheme to be used. While we could change the default and allow http(s) schemes to be used, doing so will take time, taking into account that there are many installs in existence that have tcp:// configured as DOCKER_HOST. This function detects if the `tcp://` scheme is used; if it is, it creates a shallow copy of req, containing just the URL, and overrides the scheme with 'http', which should be sufficient to perform proxy detection. For other (non-'tcp://') schemes, http.ProxyFromEnvironment is called without altering the request. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent 58542c7 commit 6850769

File tree

2 files changed

+135
-1
lines changed

2 files changed

+135
-1
lines changed

sockets/sockets.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package sockets
44
import (
55
"errors"
66
"net/http"
7+
"net/url"
78
)
89

910
// ErrProtocolNotAvailable is returned when a given transport protocol is not provided by the operating system.
@@ -20,7 +21,39 @@ func ConfigureTransport(tr *http.Transport, proto, addr string) error {
2021
case "npipe":
2122
return configureNpipeTransport(tr, proto, addr)
2223
default:
23-
tr.Proxy = http.ProxyFromEnvironment
24+
tr.Proxy = TCPProxyFromEnvironment
2425
}
2526
return nil
2627
}
28+
29+
// TCPProxyFromEnvironment wraps http.ProxyFromEnvironment, to preserve the
30+
// pre-go1.16 behavior for URLs using the 'tcp://' scheme. For other schemes,
31+
// golang's standard behavior is preserved (and depends on the Go version used).
32+
//
33+
// Prior to go1.16, `https://` schemes would use HTTPS_PROXY, and any other
34+
// scheme would use HTTP_PROXY. However, https://github.com/golang/net/commit/7b1cca2348c07eb09fef635269c8e01611260f9f
35+
// (per a request in golang/go#40909) changed this behavior to only use
36+
// HTTP_PROXY for `http://` schemes, no longer using a proxy for any other
37+
// scheme.
38+
//
39+
// Docker uses the `tcp://` scheme as a default for API connections, to indicate
40+
// that the API is not "purely" HTTP. Various parts in the code also *require*
41+
// this scheme to be used. While we could change the default and allow http(s)
42+
// schemes to be used, doing so will take time, taking into account that there
43+
// are many installs in existence that have tcp:// configured as DOCKER_HOST.
44+
//
45+
// This function detects if the `tcp://` scheme is used; if it is, it creates
46+
// a shallow copy of req, containing just the URL, and overrides the scheme with
47+
// 'http', which should be sufficient to perform proxy detection.
48+
// For other (non-'tcp://') schemes, http.ProxyFromEnvironment is called without
49+
// altering the request.
50+
func TCPProxyFromEnvironment(req *http.Request) (*url.URL, error) {
51+
if req.URL.Scheme != "tcp" {
52+
return http.ProxyFromEnvironment(req)
53+
}
54+
u := req.URL
55+
if u.Scheme == "tcp" {
56+
u.Scheme = "http"
57+
}
58+
return http.ProxyFromEnvironment(&http.Request{URL: u})
59+
}

sockets/sockets_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package sockets
2+
3+
import (
4+
"net/http"
5+
"net/url"
6+
"os"
7+
"testing"
8+
)
9+
10+
var (
11+
httpProxy = "http://proxy.example.com"
12+
httpsProxy = "https://proxy.example.com"
13+
)
14+
15+
func TestConfigureTransportProxy(t *testing.T) {
16+
// roughly based on defaultHTTPClient in the docker client
17+
u := &url.URL{
18+
Scheme: "tcp",
19+
Host: "docker.acme.example.com",
20+
}
21+
transport := new(http.Transport)
22+
err := ConfigureTransport(transport, u.Scheme, u.Host)
23+
if err != nil {
24+
t.Fatal(err)
25+
}
26+
if err := os.Setenv("HTTP_PROXY", httpProxy); err != nil {
27+
t.Fatal(err)
28+
}
29+
if err := os.Setenv("HTTPS_PROXY", httpsProxy); err != nil {
30+
t.Fatal(err)
31+
}
32+
defer func() {
33+
_ = os.Unsetenv("HTTP_PROXY")
34+
_ = os.Unsetenv("HTTPS_PROXY")
35+
}()
36+
37+
request, err := http.NewRequest(http.MethodGet, "tcp://docker.acme.example.com:2376", nil)
38+
if err != nil {
39+
t.Fatal(err)
40+
}
41+
proxyURL, err := transport.Proxy(request)
42+
if err != nil {
43+
t.Fatal(err)
44+
}
45+
if proxyURL.String() != httpProxy {
46+
t.Fatalf("expected %s, got %s", httpProxy, proxyURL)
47+
}
48+
}
49+
50+
func TestTCPProxyFromEnvironment(t *testing.T) {
51+
if err := os.Setenv("HTTP_PROXY", httpProxy); err != nil {
52+
t.Fatal(err)
53+
}
54+
if err := os.Setenv("HTTPS_PROXY", httpsProxy); err != nil {
55+
t.Fatal(err)
56+
}
57+
defer func() {
58+
_ = os.Unsetenv("HTTP_PROXY")
59+
_ = os.Unsetenv("HTTPS_PROXY")
60+
}()
61+
62+
tests := []struct {
63+
url string
64+
expected *string
65+
}{
66+
{
67+
url: "tcp://example.com:2376",
68+
expected: &httpProxy,
69+
},
70+
{
71+
url: "http://example.com:2375",
72+
expected: &httpProxy,
73+
},
74+
{
75+
url: "https://example.com:2376",
76+
expected: &httpsProxy,
77+
},
78+
}
79+
80+
for _, tc := range tests {
81+
tc := tc
82+
t.Run(tc.url, func(t *testing.T) {
83+
request, err := http.NewRequest(http.MethodGet, tc.url, nil)
84+
if err != nil {
85+
t.Fatal(err)
86+
}
87+
88+
proxyURL, err := TCPProxyFromEnvironment(request)
89+
if err != nil {
90+
t.Fatal(err)
91+
}
92+
if tc.expected == nil {
93+
if proxyURL != nil {
94+
t.Fatalf("expected no proxy, got %s", proxyURL)
95+
}
96+
} else if proxyURL.String() != *tc.expected {
97+
t.Fatalf("expected %s, got %s", *tc.expected, proxyURL)
98+
}
99+
})
100+
}
101+
}

0 commit comments

Comments
 (0)