github.com/rawahars/moby@v24.0.4+incompatible/api/server/router/container/container_routes.go (about) 1 package container // import "github.com/docker/docker/api/server/router/container" 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "runtime" 10 "strconv" 11 12 "github.com/containerd/containerd/platforms" 13 "github.com/docker/docker/api/server/httpstatus" 14 "github.com/docker/docker/api/server/httputils" 15 "github.com/docker/docker/api/types" 16 "github.com/docker/docker/api/types/backend" 17 "github.com/docker/docker/api/types/container" 18 "github.com/docker/docker/api/types/filters" 19 "github.com/docker/docker/api/types/mount" 20 "github.com/docker/docker/api/types/versions" 21 containerpkg "github.com/docker/docker/container" 22 "github.com/docker/docker/errdefs" 23 "github.com/docker/docker/pkg/ioutils" 24 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 25 "github.com/pkg/errors" 26 "github.com/sirupsen/logrus" 27 "golang.org/x/net/websocket" 28 ) 29 30 func (s *containerRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 31 if err := httputils.ParseForm(r); err != nil { 32 return err 33 } 34 35 if err := httputils.CheckForJSON(r); err != nil { 36 return err 37 } 38 39 // TODO: remove pause arg, and always pause in backend 40 pause := httputils.BoolValue(r, "pause") 41 version := httputils.VersionFromContext(ctx) 42 if r.FormValue("pause") == "" && versions.GreaterThanOrEqualTo(version, "1.13") { 43 pause = true 44 } 45 46 config, _, _, err := s.decoder.DecodeConfig(r.Body) 47 if err != nil && !errors.Is(err, io.EOF) { // Do not fail if body is empty. 48 return err 49 } 50 51 ref, err := httputils.RepoTagReference(r.Form.Get("repo"), r.Form.Get("tag")) 52 if err != nil { 53 return errdefs.InvalidParameter(err) 54 } 55 56 imgID, err := s.backend.CreateImageFromContainer(ctx, r.Form.Get("container"), &backend.CreateImageConfig{ 57 Pause: pause, 58 Tag: ref, 59 Author: r.Form.Get("author"), 60 Comment: r.Form.Get("comment"), 61 Config: config, 62 Changes: r.Form["changes"], 63 }) 64 if err != nil { 65 return err 66 } 67 68 return httputils.WriteJSON(w, http.StatusCreated, &types.IDResponse{ID: imgID}) 69 } 70 71 func (s *containerRouter) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 72 if err := httputils.ParseForm(r); err != nil { 73 return err 74 } 75 filter, err := filters.FromJSON(r.Form.Get("filters")) 76 if err != nil { 77 return err 78 } 79 80 config := &types.ContainerListOptions{ 81 All: httputils.BoolValue(r, "all"), 82 Size: httputils.BoolValue(r, "size"), 83 Since: r.Form.Get("since"), 84 Before: r.Form.Get("before"), 85 Filters: filter, 86 } 87 88 if tmpLimit := r.Form.Get("limit"); tmpLimit != "" { 89 limit, err := strconv.Atoi(tmpLimit) 90 if err != nil { 91 return err 92 } 93 config.Limit = limit 94 } 95 96 containers, err := s.backend.Containers(ctx, config) 97 if err != nil { 98 return err 99 } 100 101 return httputils.WriteJSON(w, http.StatusOK, containers) 102 } 103 104 func (s *containerRouter) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 105 if err := httputils.ParseForm(r); err != nil { 106 return err 107 } 108 109 stream := httputils.BoolValueOrDefault(r, "stream", true) 110 if !stream { 111 w.Header().Set("Content-Type", "application/json") 112 } 113 var oneShot bool 114 if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.41") { 115 oneShot = httputils.BoolValueOrDefault(r, "one-shot", false) 116 } 117 118 config := &backend.ContainerStatsConfig{ 119 Stream: stream, 120 OneShot: oneShot, 121 OutStream: w, 122 Version: httputils.VersionFromContext(ctx), 123 } 124 125 return s.backend.ContainerStats(ctx, vars["name"], config) 126 } 127 128 func (s *containerRouter) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 129 if err := httputils.ParseForm(r); err != nil { 130 return err 131 } 132 133 // Args are validated before the stream starts because when it starts we're 134 // sending HTTP 200 by writing an empty chunk of data to tell the client that 135 // daemon is going to stream. By sending this initial HTTP 200 we can't report 136 // any error after the stream starts (i.e. container not found, wrong parameters) 137 // with the appropriate status code. 138 stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr") 139 if !(stdout || stderr) { 140 return errdefs.InvalidParameter(errors.New("Bad parameters: you must choose at least one stream")) 141 } 142 143 containerName := vars["name"] 144 logsConfig := &types.ContainerLogsOptions{ 145 Follow: httputils.BoolValue(r, "follow"), 146 Timestamps: httputils.BoolValue(r, "timestamps"), 147 Since: r.Form.Get("since"), 148 Until: r.Form.Get("until"), 149 Tail: r.Form.Get("tail"), 150 ShowStdout: stdout, 151 ShowStderr: stderr, 152 Details: httputils.BoolValue(r, "details"), 153 } 154 155 msgs, tty, err := s.backend.ContainerLogs(ctx, containerName, logsConfig) 156 if err != nil { 157 return err 158 } 159 160 contentType := types.MediaTypeRawStream 161 if !tty && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") { 162 contentType = types.MediaTypeMultiplexedStream 163 } 164 w.Header().Set("Content-Type", contentType) 165 166 // if has a tty, we're not muxing streams. if it doesn't, we are. simple. 167 // this is the point of no return for writing a response. once we call 168 // WriteLogStream, the response has been started and errors will be 169 // returned in band by WriteLogStream 170 httputils.WriteLogStream(ctx, w, msgs, logsConfig, !tty) 171 return nil 172 } 173 174 func (s *containerRouter) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 175 return s.backend.ContainerExport(ctx, vars["name"], w) 176 } 177 178 type bodyOnStartError struct{} 179 180 func (bodyOnStartError) Error() string { 181 return "starting container with non-empty request body was deprecated since API v1.22 and removed in v1.24" 182 } 183 184 func (bodyOnStartError) InvalidParameter() {} 185 186 func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 187 // If contentLength is -1, we can assumed chunked encoding 188 // or more technically that the length is unknown 189 // https://golang.org/src/pkg/net/http/request.go#L139 190 // net/http otherwise seems to swallow any headers related to chunked encoding 191 // including r.TransferEncoding 192 // allow a nil body for backwards compatibility 193 194 version := httputils.VersionFromContext(ctx) 195 var hostConfig *container.HostConfig 196 // A non-nil json object is at least 7 characters. 197 if r.ContentLength > 7 || r.ContentLength == -1 { 198 if versions.GreaterThanOrEqualTo(version, "1.24") { 199 return bodyOnStartError{} 200 } 201 202 if err := httputils.CheckForJSON(r); err != nil { 203 return err 204 } 205 206 c, err := s.decoder.DecodeHostConfig(r.Body) 207 if err != nil { 208 return err 209 } 210 hostConfig = c 211 } 212 213 if err := httputils.ParseForm(r); err != nil { 214 return err 215 } 216 217 checkpoint := r.Form.Get("checkpoint") 218 checkpointDir := r.Form.Get("checkpoint-dir") 219 if err := s.backend.ContainerStart(ctx, vars["name"], hostConfig, checkpoint, checkpointDir); err != nil { 220 return err 221 } 222 223 w.WriteHeader(http.StatusNoContent) 224 return nil 225 } 226 227 func (s *containerRouter) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 228 if err := httputils.ParseForm(r); err != nil { 229 return err 230 } 231 232 var ( 233 options container.StopOptions 234 version = httputils.VersionFromContext(ctx) 235 ) 236 if versions.GreaterThanOrEqualTo(version, "1.42") { 237 options.Signal = r.Form.Get("signal") 238 } 239 if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" { 240 valSeconds, err := strconv.Atoi(tmpSeconds) 241 if err != nil { 242 return err 243 } 244 options.Timeout = &valSeconds 245 } 246 247 if err := s.backend.ContainerStop(ctx, vars["name"], options); err != nil { 248 return err 249 } 250 251 w.WriteHeader(http.StatusNoContent) 252 return nil 253 } 254 255 func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 256 if err := httputils.ParseForm(r); err != nil { 257 return err 258 } 259 260 name := vars["name"] 261 if err := s.backend.ContainerKill(name, r.Form.Get("signal")); err != nil { 262 var isStopped bool 263 if errdefs.IsConflict(err) { 264 isStopped = true 265 } 266 267 // Return error that's not caused because the container is stopped. 268 // Return error if the container is not running and the api is >= 1.20 269 // to keep backwards compatibility. 270 version := httputils.VersionFromContext(ctx) 271 if versions.GreaterThanOrEqualTo(version, "1.20") || !isStopped { 272 return errors.Wrapf(err, "Cannot kill container: %s", name) 273 } 274 } 275 276 w.WriteHeader(http.StatusNoContent) 277 return nil 278 } 279 280 func (s *containerRouter) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 281 if err := httputils.ParseForm(r); err != nil { 282 return err 283 } 284 285 var ( 286 options container.StopOptions 287 version = httputils.VersionFromContext(ctx) 288 ) 289 if versions.GreaterThanOrEqualTo(version, "1.42") { 290 options.Signal = r.Form.Get("signal") 291 } 292 if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" { 293 valSeconds, err := strconv.Atoi(tmpSeconds) 294 if err != nil { 295 return err 296 } 297 options.Timeout = &valSeconds 298 } 299 300 if err := s.backend.ContainerRestart(ctx, vars["name"], options); err != nil { 301 return err 302 } 303 304 w.WriteHeader(http.StatusNoContent) 305 return nil 306 } 307 308 func (s *containerRouter) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 309 if err := httputils.ParseForm(r); err != nil { 310 return err 311 } 312 313 if err := s.backend.ContainerPause(vars["name"]); err != nil { 314 return err 315 } 316 317 w.WriteHeader(http.StatusNoContent) 318 319 return nil 320 } 321 322 func (s *containerRouter) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 323 if err := httputils.ParseForm(r); err != nil { 324 return err 325 } 326 327 if err := s.backend.ContainerUnpause(vars["name"]); err != nil { 328 return err 329 } 330 331 w.WriteHeader(http.StatusNoContent) 332 333 return nil 334 } 335 336 func (s *containerRouter) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 337 // Behavior changed in version 1.30 to handle wait condition and to 338 // return headers immediately. 339 version := httputils.VersionFromContext(ctx) 340 legacyBehaviorPre130 := versions.LessThan(version, "1.30") 341 legacyRemovalWaitPre134 := false 342 343 // The wait condition defaults to "not-running". 344 waitCondition := containerpkg.WaitConditionNotRunning 345 if !legacyBehaviorPre130 { 346 if err := httputils.ParseForm(r); err != nil { 347 return err 348 } 349 if v := r.Form.Get("condition"); v != "" { 350 switch container.WaitCondition(v) { 351 case container.WaitConditionNotRunning: 352 waitCondition = containerpkg.WaitConditionNotRunning 353 case container.WaitConditionNextExit: 354 waitCondition = containerpkg.WaitConditionNextExit 355 case container.WaitConditionRemoved: 356 waitCondition = containerpkg.WaitConditionRemoved 357 legacyRemovalWaitPre134 = versions.LessThan(version, "1.34") 358 default: 359 return errdefs.InvalidParameter(errors.Errorf("invalid condition: %q", v)) 360 } 361 } 362 } 363 364 waitC, err := s.backend.ContainerWait(ctx, vars["name"], waitCondition) 365 if err != nil { 366 return err 367 } 368 369 w.Header().Set("Content-Type", "application/json") 370 371 if !legacyBehaviorPre130 { 372 // Write response header immediately. 373 w.WriteHeader(http.StatusOK) 374 if flusher, ok := w.(http.Flusher); ok { 375 flusher.Flush() 376 } 377 } 378 379 // Block on the result of the wait operation. 380 status := <-waitC 381 382 // With API < 1.34, wait on WaitConditionRemoved did not return 383 // in case container removal failed. The only way to report an 384 // error back to the client is to not write anything (i.e. send 385 // an empty response which will be treated as an error). 386 if legacyRemovalWaitPre134 && status.Err() != nil { 387 return nil 388 } 389 390 var waitError *container.WaitExitError 391 if status.Err() != nil { 392 waitError = &container.WaitExitError{Message: status.Err().Error()} 393 } 394 395 return json.NewEncoder(w).Encode(&container.WaitResponse{ 396 StatusCode: int64(status.ExitCode()), 397 Error: waitError, 398 }) 399 } 400 401 func (s *containerRouter) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 402 changes, err := s.backend.ContainerChanges(ctx, vars["name"]) 403 if err != nil { 404 return err 405 } 406 407 return httputils.WriteJSON(w, http.StatusOK, changes) 408 } 409 410 func (s *containerRouter) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 411 if err := httputils.ParseForm(r); err != nil { 412 return err 413 } 414 415 procList, err := s.backend.ContainerTop(vars["name"], r.Form.Get("ps_args")) 416 if err != nil { 417 return err 418 } 419 420 return httputils.WriteJSON(w, http.StatusOK, procList) 421 } 422 423 func (s *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 424 if err := httputils.ParseForm(r); err != nil { 425 return err 426 } 427 428 name := vars["name"] 429 newName := r.Form.Get("name") 430 if err := s.backend.ContainerRename(name, newName); err != nil { 431 return err 432 } 433 w.WriteHeader(http.StatusNoContent) 434 return nil 435 } 436 437 func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 438 if err := httputils.ParseForm(r); err != nil { 439 return err 440 } 441 442 var updateConfig container.UpdateConfig 443 if err := httputils.ReadJSON(r, &updateConfig); err != nil { 444 return err 445 } 446 if versions.LessThan(httputils.VersionFromContext(ctx), "1.40") { 447 updateConfig.PidsLimit = nil 448 } 449 450 if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") { 451 // Ignore KernelMemory removed in API 1.42. 452 updateConfig.KernelMemory = 0 453 } 454 455 if updateConfig.PidsLimit != nil && *updateConfig.PidsLimit <= 0 { 456 // Both `0` and `-1` are accepted to set "unlimited" when updating. 457 // Historically, any negative value was accepted, so treat them as 458 // "unlimited" as well. 459 var unlimited int64 460 updateConfig.PidsLimit = &unlimited 461 } 462 463 hostConfig := &container.HostConfig{ 464 Resources: updateConfig.Resources, 465 RestartPolicy: updateConfig.RestartPolicy, 466 } 467 468 name := vars["name"] 469 resp, err := s.backend.ContainerUpdate(name, hostConfig) 470 if err != nil { 471 return err 472 } 473 474 return httputils.WriteJSON(w, http.StatusOK, resp) 475 } 476 477 func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 478 if err := httputils.ParseForm(r); err != nil { 479 return err 480 } 481 if err := httputils.CheckForJSON(r); err != nil { 482 return err 483 } 484 485 name := r.Form.Get("name") 486 487 config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body) 488 if err != nil { 489 if errors.Is(err, io.EOF) { 490 return errdefs.InvalidParameter(errors.New("invalid JSON: got EOF while reading request body")) 491 } 492 return err 493 } 494 version := httputils.VersionFromContext(ctx) 495 adjustCPUShares := versions.LessThan(version, "1.19") 496 497 // When using API 1.24 and under, the client is responsible for removing the container 498 if hostConfig != nil && versions.LessThan(version, "1.25") { 499 hostConfig.AutoRemove = false 500 } 501 502 if hostConfig != nil && versions.LessThan(version, "1.40") { 503 // Ignore BindOptions.NonRecursive because it was added in API 1.40. 504 for _, m := range hostConfig.Mounts { 505 if bo := m.BindOptions; bo != nil { 506 bo.NonRecursive = false 507 } 508 } 509 // Ignore KernelMemoryTCP because it was added in API 1.40. 510 hostConfig.KernelMemoryTCP = 0 511 512 // Older clients (API < 1.40) expects the default to be shareable, make them happy 513 if hostConfig.IpcMode.IsEmpty() { 514 hostConfig.IpcMode = container.IPCModeShareable 515 } 516 } 517 if hostConfig != nil && versions.LessThan(version, "1.41") && !s.cgroup2 { 518 // Older clients expect the default to be "host" on cgroup v1 hosts 519 if hostConfig.CgroupnsMode.IsEmpty() { 520 hostConfig.CgroupnsMode = container.CgroupnsModeHost 521 } 522 } 523 524 if hostConfig != nil && versions.LessThan(version, "1.42") { 525 for _, m := range hostConfig.Mounts { 526 // Ignore BindOptions.CreateMountpoint because it was added in API 1.42. 527 if bo := m.BindOptions; bo != nil { 528 bo.CreateMountpoint = false 529 } 530 531 // These combinations are invalid, but weren't validated in API < 1.42. 532 // We reset them here, so that validation doesn't produce an error. 533 if o := m.VolumeOptions; o != nil && m.Type != mount.TypeVolume { 534 m.VolumeOptions = nil 535 } 536 if o := m.TmpfsOptions; o != nil && m.Type != mount.TypeTmpfs { 537 m.TmpfsOptions = nil 538 } 539 if bo := m.BindOptions; bo != nil { 540 // Ignore BindOptions.CreateMountpoint because it was added in API 1.42. 541 bo.CreateMountpoint = false 542 } 543 } 544 } 545 546 if hostConfig != nil && versions.GreaterThanOrEqualTo(version, "1.42") { 547 // Ignore KernelMemory removed in API 1.42. 548 hostConfig.KernelMemory = 0 549 for _, m := range hostConfig.Mounts { 550 if o := m.VolumeOptions; o != nil && m.Type != mount.TypeVolume { 551 return errdefs.InvalidParameter(fmt.Errorf("VolumeOptions must not be specified on mount type %q", m.Type)) 552 } 553 if o := m.BindOptions; o != nil && m.Type != mount.TypeBind { 554 return errdefs.InvalidParameter(fmt.Errorf("BindOptions must not be specified on mount type %q", m.Type)) 555 } 556 if o := m.TmpfsOptions; o != nil && m.Type != mount.TypeTmpfs { 557 return errdefs.InvalidParameter(fmt.Errorf("TmpfsOptions must not be specified on mount type %q", m.Type)) 558 } 559 } 560 } 561 562 if hostConfig != nil && runtime.GOOS == "linux" && versions.LessThan(version, "1.42") { 563 // ConsoleSize is not respected by Linux daemon before API 1.42 564 hostConfig.ConsoleSize = [2]uint{0, 0} 565 } 566 567 if hostConfig != nil && versions.LessThan(version, "1.43") { 568 // Ignore Annotations because it was added in API v1.43. 569 hostConfig.Annotations = nil 570 } 571 572 var platform *ocispec.Platform 573 if versions.GreaterThanOrEqualTo(version, "1.41") { 574 if v := r.Form.Get("platform"); v != "" { 575 p, err := platforms.Parse(v) 576 if err != nil { 577 return errdefs.InvalidParameter(err) 578 } 579 platform = &p 580 } 581 } 582 583 if hostConfig != nil && hostConfig.PidsLimit != nil && *hostConfig.PidsLimit <= 0 { 584 // Don't set a limit if either no limit was specified, or "unlimited" was 585 // explicitly set. 586 // Both `0` and `-1` are accepted as "unlimited", and historically any 587 // negative value was accepted, so treat those as "unlimited" as well. 588 hostConfig.PidsLimit = nil 589 } 590 591 ccr, err := s.backend.ContainerCreate(ctx, types.ContainerCreateConfig{ 592 Name: name, 593 Config: config, 594 HostConfig: hostConfig, 595 NetworkingConfig: networkingConfig, 596 AdjustCPUShares: adjustCPUShares, 597 Platform: platform, 598 }) 599 if err != nil { 600 return err 601 } 602 603 return httputils.WriteJSON(w, http.StatusCreated, ccr) 604 } 605 606 func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 607 if err := httputils.ParseForm(r); err != nil { 608 return err 609 } 610 611 name := vars["name"] 612 config := &types.ContainerRmConfig{ 613 ForceRemove: httputils.BoolValue(r, "force"), 614 RemoveVolume: httputils.BoolValue(r, "v"), 615 RemoveLink: httputils.BoolValue(r, "link"), 616 } 617 618 if err := s.backend.ContainerRm(name, config); err != nil { 619 return err 620 } 621 622 w.WriteHeader(http.StatusNoContent) 623 624 return nil 625 } 626 627 func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 628 if err := httputils.ParseForm(r); err != nil { 629 return err 630 } 631 632 height, err := strconv.Atoi(r.Form.Get("h")) 633 if err != nil { 634 return errdefs.InvalidParameter(err) 635 } 636 width, err := strconv.Atoi(r.Form.Get("w")) 637 if err != nil { 638 return errdefs.InvalidParameter(err) 639 } 640 641 return s.backend.ContainerResize(vars["name"], height, width) 642 } 643 644 func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 645 err := httputils.ParseForm(r) 646 if err != nil { 647 return err 648 } 649 containerName := vars["name"] 650 651 _, upgrade := r.Header["Upgrade"] 652 detachKeys := r.FormValue("detachKeys") 653 654 hijacker, ok := w.(http.Hijacker) 655 if !ok { 656 return errdefs.InvalidParameter(errors.Errorf("error attaching to container %s, hijack connection missing", containerName)) 657 } 658 659 contentType := types.MediaTypeRawStream 660 setupStreams := func(multiplexed bool) (io.ReadCloser, io.Writer, io.Writer, error) { 661 conn, _, err := hijacker.Hijack() 662 if err != nil { 663 return nil, nil, nil, err 664 } 665 666 // set raw mode 667 conn.Write([]byte{}) 668 669 if upgrade { 670 if multiplexed && versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.42") { 671 contentType = types.MediaTypeMultiplexedStream 672 } 673 fmt.Fprintf(conn, "HTTP/1.1 101 UPGRADED\r\nContent-Type: "+contentType+"\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") 674 } else { 675 fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") 676 } 677 678 closer := func() error { 679 httputils.CloseStreams(conn) 680 return nil 681 } 682 return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil 683 } 684 685 attachConfig := &backend.ContainerAttachConfig{ 686 GetStreams: setupStreams, 687 UseStdin: httputils.BoolValue(r, "stdin"), 688 UseStdout: httputils.BoolValue(r, "stdout"), 689 UseStderr: httputils.BoolValue(r, "stderr"), 690 Logs: httputils.BoolValue(r, "logs"), 691 Stream: httputils.BoolValue(r, "stream"), 692 DetachKeys: detachKeys, 693 MuxStreams: true, 694 } 695 696 if err = s.backend.ContainerAttach(containerName, attachConfig); err != nil { 697 logrus.WithError(err).Errorf("Handler for %s %s returned error", r.Method, r.URL.Path) 698 // Remember to close stream if error happens 699 conn, _, errHijack := hijacker.Hijack() 700 if errHijack != nil { 701 logrus.WithError(err).Errorf("Handler for %s %s: unable to close stream; error when hijacking connection", r.Method, r.URL.Path) 702 } else { 703 statusCode := httpstatus.FromError(err) 704 statusText := http.StatusText(statusCode) 705 fmt.Fprintf(conn, "HTTP/1.1 %d %s\r\nContent-Type: %s\r\n\r\n%s\r\n", statusCode, statusText, contentType, err.Error()) 706 httputils.CloseStreams(conn) 707 } 708 } 709 return nil 710 } 711 712 func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 713 if err := httputils.ParseForm(r); err != nil { 714 return err 715 } 716 containerName := vars["name"] 717 718 var err error 719 detachKeys := r.FormValue("detachKeys") 720 721 done := make(chan struct{}) 722 started := make(chan struct{}) 723 724 version := httputils.VersionFromContext(ctx) 725 726 setupStreams := func(multiplexed bool) (io.ReadCloser, io.Writer, io.Writer, error) { 727 wsChan := make(chan *websocket.Conn) 728 h := func(conn *websocket.Conn) { 729 wsChan <- conn 730 <-done 731 } 732 733 srv := websocket.Server{Handler: h, Handshake: nil} 734 go func() { 735 close(started) 736 srv.ServeHTTP(w, r) 737 }() 738 739 conn := <-wsChan 740 // In case version 1.28 and above, a binary frame will be sent. 741 // See 28176 for details. 742 if versions.GreaterThanOrEqualTo(version, "1.28") { 743 conn.PayloadType = websocket.BinaryFrame 744 } 745 return conn, conn, conn, nil 746 } 747 748 useStdin, useStdout, useStderr := true, true, true 749 if versions.GreaterThanOrEqualTo(version, "1.42") { 750 useStdin = httputils.BoolValue(r, "stdin") 751 useStdout = httputils.BoolValue(r, "stdout") 752 useStderr = httputils.BoolValue(r, "stderr") 753 } 754 755 attachConfig := &backend.ContainerAttachConfig{ 756 GetStreams: setupStreams, 757 UseStdin: useStdin, 758 UseStdout: useStdout, 759 UseStderr: useStderr, 760 Logs: httputils.BoolValue(r, "logs"), 761 Stream: httputils.BoolValue(r, "stream"), 762 DetachKeys: detachKeys, 763 MuxStreams: false, // never multiplex, as we rely on websocket to manage distinct streams 764 } 765 766 err = s.backend.ContainerAttach(containerName, attachConfig) 767 close(done) 768 select { 769 case <-started: 770 if err != nil { 771 logrus.Errorf("Error attaching websocket: %s", err) 772 } else { 773 logrus.Debug("websocket connection was closed by client") 774 } 775 return nil 776 default: 777 } 778 return err 779 } 780 781 func (s *containerRouter) postContainersPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 782 if err := httputils.ParseForm(r); err != nil { 783 return err 784 } 785 786 pruneFilters, err := filters.FromJSON(r.Form.Get("filters")) 787 if err != nil { 788 return err 789 } 790 791 pruneReport, err := s.backend.ContainersPrune(ctx, pruneFilters) 792 if err != nil { 793 return err 794 } 795 return httputils.WriteJSON(w, http.StatusOK, pruneReport) 796 }