Browse code

client: deterministically resolve http scheme

The docker client has historically used Transport.TLSClientConfig to set
the scheme for the API client. A recent moved the resolution to use the
http.Transport directly, rather than save the TLSClientConfig state on a
client struct. This caused issues when mutliple calls made with a single
client would have this field set in the http package on pre-1.7
installations. This fix detects the presence of the TLSClientConfig once
and sets the scheme accordingly.

We still don't know why this issue doesn't happen with Go 1.7 but it
must be more deterministic in the newer version.

Signed-off-by: Stephen J Day <stephen.day@docker.com>

Stephen J Day authored on 2016/10/12 07:53:14
Showing 3 changed files
... ...
@@ -63,6 +63,8 @@ const DefaultVersion string = "1.23"
63 63
 // Client is the API client that performs all operations
64 64
 // against a docker server.
65 65
 type Client struct {
66
+	// scheme sets the scheme for the client
67
+	scheme string
66 68
 	// host holds the server address to connect to
67 69
 	host string
68 70
 	// proto holds the client protocol i.e. unix.
... ...
@@ -143,7 +145,19 @@ func NewClient(host string, version string, client *http.Client, httpHeaders map
143 143
 		}
144 144
 	}
145 145
 
146
+	scheme := "http"
147
+	tlsConfig := resolveTLSConfig(client.Transport)
148
+	if tlsConfig != nil {
149
+		// TODO(stevvooe): This isn't really the right way to write clients in Go.
150
+		// `NewClient` should probably only take an `*http.Client` and work from there.
151
+		// Unfortunately, the model of having a host-ish/url-thingy as the connection
152
+		// string has us confusing protocol and transport layers. We continue doing
153
+		// this to avoid breaking existing clients but this should be addressed.
154
+		scheme = "https"
155
+	}
156
+
146 157
 	return &Client{
158
+		scheme:            scheme,
147 159
 		host:              host,
148 160
 		proto:             proto,
149 161
 		addr:              addr,
... ...
@@ -100,10 +100,8 @@ func (cli *Client) sendClientRequest(ctx context.Context, method, path string, q
100 100
 		req.Host = "docker"
101 101
 	}
102 102
 
103
-	scheme := resolveScheme(cli.client.Transport)
104
-
105 103
 	req.URL.Host = cli.addr
106
-	req.URL.Scheme = scheme
104
+	req.URL.Scheme = cli.scheme
107 105
 
108 106
 	if expectedPayload && req.Header.Get("Content-Type") == "" {
109 107
 		req.Header.Set("Content-Type", "text/plain")
... ...
@@ -111,11 +109,11 @@ func (cli *Client) sendClientRequest(ctx context.Context, method, path string, q
111 111
 
112 112
 	resp, err := ctxhttp.Do(ctx, cli.client, req)
113 113
 	if err != nil {
114
-		if scheme != "https" && strings.Contains(err.Error(), "malformed HTTP response") {
114
+		if cli.scheme != "https" && strings.Contains(err.Error(), "malformed HTTP response") {
115 115
 			return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)
116 116
 		}
117 117
 
118
-		if scheme == "https" && strings.Contains(err.Error(), "bad certificate") {
118
+		if cli.scheme == "https" && strings.Contains(err.Error(), "bad certificate") {
119 119
 			return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
120 120
 		}
121 121
 
... ...
@@ -26,20 +26,3 @@ func resolveTLSConfig(transport http.RoundTripper) *tls.Config {
26 26
 		return nil
27 27
 	}
28 28
 }
29
-
30
-// resolveScheme detects a tls config on the transport and returns the
31
-// appropriate http scheme.
32
-//
33
-// TODO(stevvooe): This isn't really the right way to write clients in Go.
34
-// `NewClient` should probably only take an `*http.Client` and work from there.
35
-// Unfortunately, the model of having a host-ish/url-thingy as the connection
36
-// string has us confusing protocol and transport layers. We continue doing
37
-// this to avoid breaking existing clients but this should be addressed.
38
-func resolveScheme(transport http.RoundTripper) string {
39
-	c := resolveTLSConfig(transport)
40
-	if c != nil {
41
-		return "https"
42
-	}
43
-
44
-	return "http"
45
-}