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