github.com/rentongzhang/docker@v1.8.2-rc1/api/client/utils.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "net/http" 12 "net/url" 13 "os" 14 gosignal "os/signal" 15 "runtime" 16 "strconv" 17 "strings" 18 "time" 19 20 "github.com/Sirupsen/logrus" 21 "github.com/docker/docker/api" 22 "github.com/docker/docker/api/types" 23 "github.com/docker/docker/autogen/dockerversion" 24 "github.com/docker/docker/cliconfig" 25 "github.com/docker/docker/pkg/jsonmessage" 26 "github.com/docker/docker/pkg/signal" 27 "github.com/docker/docker/pkg/stdcopy" 28 "github.com/docker/docker/pkg/term" 29 "github.com/docker/docker/registry" 30 ) 31 32 var ( 33 errConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") 34 ) 35 36 type serverResponse struct { 37 body io.ReadCloser 38 header http.Header 39 statusCode int 40 } 41 42 // HTTPClient creates a new HTTP client with the cli's client transport instance. 43 func (cli *DockerCli) HTTPClient() *http.Client { 44 return &http.Client{Transport: cli.transport} 45 } 46 47 func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) { 48 params := bytes.NewBuffer(nil) 49 if data != nil { 50 if err := json.NewEncoder(params).Encode(data); err != nil { 51 return nil, err 52 } 53 } 54 return params, nil 55 } 56 57 func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (*serverResponse, error) { 58 59 serverResp := &serverResponse{ 60 body: nil, 61 statusCode: -1, 62 } 63 64 expectedPayload := (method == "POST" || method == "PUT") 65 if expectedPayload && in == nil { 66 in = bytes.NewReader([]byte{}) 67 } 68 req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), in) 69 if err != nil { 70 return serverResp, err 71 } 72 73 // Add CLI Config's HTTP Headers BEFORE we set the Docker headers 74 // then the user can't change OUR headers 75 for k, v := range cli.configFile.HTTPHeaders { 76 req.Header.Set(k, v) 77 } 78 79 req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION+" ("+runtime.GOOS+")") 80 req.URL.Host = cli.addr 81 req.URL.Scheme = cli.scheme 82 83 if headers != nil { 84 for k, v := range headers { 85 req.Header[k] = v 86 } 87 } 88 89 if expectedPayload && req.Header.Get("Content-Type") == "" { 90 req.Header.Set("Content-Type", "text/plain") 91 } 92 93 resp, err := cli.HTTPClient().Do(req) 94 if resp != nil { 95 serverResp.statusCode = resp.StatusCode 96 } 97 if err != nil { 98 if strings.Contains(err.Error(), "connection refused") { 99 return serverResp, errConnectionRefused 100 } 101 102 if cli.tlsConfig == nil { 103 return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?\n* Is your docker daemon up and running?", err) 104 } 105 if cli.tlsConfig != nil && strings.Contains(err.Error(), "remote error: bad certificate") { 106 return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err) 107 } 108 109 return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err) 110 } 111 112 if serverResp.statusCode < 200 || serverResp.statusCode >= 400 { 113 body, err := ioutil.ReadAll(resp.Body) 114 if err != nil { 115 return serverResp, err 116 } 117 if len(body) == 0 { 118 return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL) 119 } 120 return serverResp, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body)) 121 } 122 123 serverResp.body = resp.Body 124 serverResp.header = resp.Header 125 return serverResp, nil 126 } 127 128 func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) { 129 cmdAttempt := func(authConfig cliconfig.AuthConfig) (io.ReadCloser, int, error) { 130 buf, err := json.Marshal(authConfig) 131 if err != nil { 132 return nil, -1, err 133 } 134 registryAuthHeader := []string{ 135 base64.URLEncoding.EncodeToString(buf), 136 } 137 138 // begin the request 139 serverResp, err := cli.clientRequest(method, path, in, map[string][]string{ 140 "X-Registry-Auth": registryAuthHeader, 141 }) 142 if err == nil && out != nil { 143 // If we are streaming output, complete the stream since 144 // errors may not appear until later. 145 err = cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), true, out, nil) 146 } 147 if err != nil { 148 // Since errors in a stream appear after status 200 has been written, 149 // we may need to change the status code. 150 if strings.Contains(err.Error(), "Authentication is required") || 151 strings.Contains(err.Error(), "Status 401") || 152 strings.Contains(err.Error(), "401 Unauthorized") || 153 strings.Contains(err.Error(), "status code 401") { 154 serverResp.statusCode = http.StatusUnauthorized 155 } 156 } 157 return serverResp.body, serverResp.statusCode, err 158 } 159 160 // Resolve the Auth config relevant for this server 161 authConfig := registry.ResolveAuthConfig(cli.configFile, index) 162 body, statusCode, err := cmdAttempt(authConfig) 163 if statusCode == http.StatusUnauthorized { 164 fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName) 165 if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil { 166 return nil, -1, err 167 } 168 authConfig = registry.ResolveAuthConfig(cli.configFile, index) 169 return cmdAttempt(authConfig) 170 } 171 return body, statusCode, err 172 } 173 174 func (cli *DockerCli) callWrapper(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error) { 175 sr, err := cli.call(method, path, data, headers) 176 return sr.body, sr.header, sr.statusCode, err 177 } 178 179 func (cli *DockerCli) call(method, path string, data interface{}, headers map[string][]string) (*serverResponse, error) { 180 params, err := cli.encodeData(data) 181 if err != nil { 182 sr := &serverResponse{ 183 body: nil, 184 header: nil, 185 statusCode: -1, 186 } 187 return sr, nil 188 } 189 190 if data != nil { 191 if headers == nil { 192 headers = make(map[string][]string) 193 } 194 headers["Content-Type"] = []string{"application/json"} 195 } 196 197 serverResp, err := cli.clientRequest(method, path, params, headers) 198 return serverResp, err 199 } 200 201 type streamOpts struct { 202 rawTerminal bool 203 in io.Reader 204 out io.Writer 205 err io.Writer 206 headers map[string][]string 207 } 208 209 func (cli *DockerCli) stream(method, path string, opts *streamOpts) (*serverResponse, error) { 210 serverResp, err := cli.clientRequest(method, path, opts.in, opts.headers) 211 if err != nil { 212 return serverResp, err 213 } 214 return serverResp, cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), opts.rawTerminal, opts.out, opts.err) 215 } 216 217 func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, rawTerminal bool, stdout, stderr io.Writer) error { 218 defer body.Close() 219 220 if api.MatchesContentType(contentType, "application/json") { 221 return jsonmessage.DisplayJSONMessagesStream(body, stdout, cli.outFd, cli.isTerminalOut) 222 } 223 if stdout != nil || stderr != nil { 224 // When TTY is ON, use regular copy 225 var err error 226 if rawTerminal { 227 _, err = io.Copy(stdout, body) 228 } else { 229 _, err = stdcopy.StdCopy(stdout, stderr, body) 230 } 231 logrus.Debugf("[stream] End of stdout") 232 return err 233 } 234 return nil 235 } 236 237 func (cli *DockerCli) resizeTty(id string, isExec bool) { 238 height, width := cli.getTtySize() 239 if height == 0 && width == 0 { 240 return 241 } 242 v := url.Values{} 243 v.Set("h", strconv.Itoa(height)) 244 v.Set("w", strconv.Itoa(width)) 245 246 path := "" 247 if !isExec { 248 path = "/containers/" + id + "/resize?" 249 } else { 250 path = "/exec/" + id + "/resize?" 251 } 252 253 if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, nil)); err != nil { 254 logrus.Debugf("Error resize: %s", err) 255 } 256 } 257 258 func waitForExit(cli *DockerCli, containerID string) (int, error) { 259 serverResp, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil) 260 if err != nil { 261 return -1, err 262 } 263 264 defer serverResp.body.Close() 265 266 var res types.ContainerWaitResponse 267 if err := json.NewDecoder(serverResp.body).Decode(&res); err != nil { 268 return -1, err 269 } 270 271 return res.StatusCode, nil 272 } 273 274 // getExitCode perform an inspect on the container. It returns 275 // the running state and the exit code. 276 func getExitCode(cli *DockerCli, containerID string) (bool, int, error) { 277 serverResp, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil) 278 if err != nil { 279 // If we can't connect, then the daemon probably died. 280 if err != errConnectionRefused { 281 return false, -1, err 282 } 283 return false, -1, nil 284 } 285 286 defer serverResp.body.Close() 287 288 var c types.ContainerJSON 289 if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil { 290 return false, -1, err 291 } 292 293 return c.State.Running, c.State.ExitCode, nil 294 } 295 296 // getExecExitCode perform an inspect on the exec command. It returns 297 // the running state and the exit code. 298 func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) { 299 serverResp, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil) 300 if err != nil { 301 // If we can't connect, then the daemon probably died. 302 if err != errConnectionRefused { 303 return false, -1, err 304 } 305 return false, -1, nil 306 } 307 308 defer serverResp.body.Close() 309 310 //TODO: Should we reconsider having a type in api/types? 311 //this is a response to exex/id/json not container 312 var c struct { 313 Running bool 314 ExitCode int 315 } 316 317 if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil { 318 return false, -1, err 319 } 320 321 return c.Running, c.ExitCode, nil 322 } 323 324 func (cli *DockerCli) monitorTtySize(id string, isExec bool) error { 325 cli.resizeTty(id, isExec) 326 327 if runtime.GOOS == "windows" { 328 go func() { 329 prevH, prevW := cli.getTtySize() 330 for { 331 time.Sleep(time.Millisecond * 250) 332 h, w := cli.getTtySize() 333 334 if prevW != w || prevH != h { 335 cli.resizeTty(id, isExec) 336 } 337 prevH = h 338 prevW = w 339 } 340 }() 341 } else { 342 sigchan := make(chan os.Signal, 1) 343 gosignal.Notify(sigchan, signal.SIGWINCH) 344 go func() { 345 for range sigchan { 346 cli.resizeTty(id, isExec) 347 } 348 }() 349 } 350 return nil 351 } 352 353 func (cli *DockerCli) getTtySize() (int, int) { 354 if !cli.isTerminalOut { 355 return 0, 0 356 } 357 ws, err := term.GetWinsize(cli.outFd) 358 if err != nil { 359 logrus.Debugf("Error getting size: %s", err) 360 if ws == nil { 361 return 0, 0 362 } 363 } 364 return int(ws.Height), int(ws.Width) 365 } 366 367 func readBody(serverResp *serverResponse, err error) ([]byte, int, error) { 368 if serverResp.body != nil { 369 defer serverResp.body.Close() 370 } 371 if err != nil { 372 return nil, serverResp.statusCode, err 373 } 374 body, err := ioutil.ReadAll(serverResp.body) 375 if err != nil { 376 return nil, -1, err 377 } 378 return body, serverResp.statusCode, nil 379 }