github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/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  	"strconv"
    10  	"syscall"
    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/versions"
    20  	containerpkg "github.com/docker/docker/container"
    21  	"github.com/docker/docker/errdefs"
    22  	"github.com/docker/docker/pkg/ioutils"
    23  	"github.com/docker/docker/pkg/signal"
    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(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(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  	// if has a tty, we're not muxing streams. if it doesn't, we are. simple.
   159  	// this is the point of no return for writing a response. once we call
   160  	// WriteLogStream, the response has been started and errors will be
   161  	// returned in band by WriteLogStream
   162  	httputils.WriteLogStream(ctx, w, msgs, logsConfig, !tty)
   163  	return nil
   164  }
   165  
   166  func (s *containerRouter) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   167  	return s.backend.ContainerExport(vars["name"], w)
   168  }
   169  
   170  type bodyOnStartError struct{}
   171  
   172  func (bodyOnStartError) Error() string {
   173  	return "starting container with non-empty request body was deprecated since API v1.22 and removed in v1.24"
   174  }
   175  
   176  func (bodyOnStartError) InvalidParameter() {}
   177  
   178  func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   179  	// If contentLength is -1, we can assumed chunked encoding
   180  	// or more technically that the length is unknown
   181  	// https://golang.org/src/pkg/net/http/request.go#L139
   182  	// net/http otherwise seems to swallow any headers related to chunked encoding
   183  	// including r.TransferEncoding
   184  	// allow a nil body for backwards compatibility
   185  
   186  	version := httputils.VersionFromContext(ctx)
   187  	var hostConfig *container.HostConfig
   188  	// A non-nil json object is at least 7 characters.
   189  	if r.ContentLength > 7 || r.ContentLength == -1 {
   190  		if versions.GreaterThanOrEqualTo(version, "1.24") {
   191  			return bodyOnStartError{}
   192  		}
   193  
   194  		if err := httputils.CheckForJSON(r); err != nil {
   195  			return err
   196  		}
   197  
   198  		c, err := s.decoder.DecodeHostConfig(r.Body)
   199  		if err != nil {
   200  			return err
   201  		}
   202  		hostConfig = c
   203  	}
   204  
   205  	if err := httputils.ParseForm(r); err != nil {
   206  		return err
   207  	}
   208  
   209  	checkpoint := r.Form.Get("checkpoint")
   210  	checkpointDir := r.Form.Get("checkpoint-dir")
   211  	if err := s.backend.ContainerStart(vars["name"], hostConfig, checkpoint, checkpointDir); err != nil {
   212  		return err
   213  	}
   214  
   215  	w.WriteHeader(http.StatusNoContent)
   216  	return nil
   217  }
   218  
   219  func (s *containerRouter) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   220  	if err := httputils.ParseForm(r); err != nil {
   221  		return err
   222  	}
   223  
   224  	var seconds *int
   225  	if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
   226  		valSeconds, err := strconv.Atoi(tmpSeconds)
   227  		if err != nil {
   228  			return err
   229  		}
   230  		seconds = &valSeconds
   231  	}
   232  
   233  	if err := s.backend.ContainerStop(vars["name"], seconds); err != nil {
   234  		return err
   235  	}
   236  	w.WriteHeader(http.StatusNoContent)
   237  
   238  	return nil
   239  }
   240  
   241  func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   242  	if err := httputils.ParseForm(r); err != nil {
   243  		return err
   244  	}
   245  
   246  	var sig syscall.Signal
   247  	name := vars["name"]
   248  
   249  	// If we have a signal, look at it. Otherwise, do nothing
   250  	if sigStr := r.Form.Get("signal"); sigStr != "" {
   251  		var err error
   252  		if sig, err = signal.ParseSignal(sigStr); err != nil {
   253  			return errdefs.InvalidParameter(err)
   254  		}
   255  	}
   256  
   257  	if err := s.backend.ContainerKill(name, uint64(sig)); err != nil {
   258  		var isStopped bool
   259  		if errdefs.IsConflict(err) {
   260  			isStopped = true
   261  		}
   262  
   263  		// Return error that's not caused because the container is stopped.
   264  		// Return error if the container is not running and the api is >= 1.20
   265  		// to keep backwards compatibility.
   266  		version := httputils.VersionFromContext(ctx)
   267  		if versions.GreaterThanOrEqualTo(version, "1.20") || !isStopped {
   268  			return errors.Wrapf(err, "Cannot kill container: %s", name)
   269  		}
   270  	}
   271  
   272  	w.WriteHeader(http.StatusNoContent)
   273  	return nil
   274  }
   275  
   276  func (s *containerRouter) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   277  	if err := httputils.ParseForm(r); err != nil {
   278  		return err
   279  	}
   280  
   281  	var seconds *int
   282  	if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
   283  		valSeconds, err := strconv.Atoi(tmpSeconds)
   284  		if err != nil {
   285  			return err
   286  		}
   287  		seconds = &valSeconds
   288  	}
   289  
   290  	if err := s.backend.ContainerRestart(vars["name"], seconds); err != nil {
   291  		return err
   292  	}
   293  
   294  	w.WriteHeader(http.StatusNoContent)
   295  
   296  	return nil
   297  }
   298  
   299  func (s *containerRouter) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   300  	if err := httputils.ParseForm(r); err != nil {
   301  		return err
   302  	}
   303  
   304  	if err := s.backend.ContainerPause(vars["name"]); err != nil {
   305  		return err
   306  	}
   307  
   308  	w.WriteHeader(http.StatusNoContent)
   309  
   310  	return nil
   311  }
   312  
   313  func (s *containerRouter) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   314  	if err := httputils.ParseForm(r); err != nil {
   315  		return err
   316  	}
   317  
   318  	if err := s.backend.ContainerUnpause(vars["name"]); err != nil {
   319  		return err
   320  	}
   321  
   322  	w.WriteHeader(http.StatusNoContent)
   323  
   324  	return nil
   325  }
   326  
   327  func (s *containerRouter) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   328  	// Behavior changed in version 1.30 to handle wait condition and to
   329  	// return headers immediately.
   330  	version := httputils.VersionFromContext(ctx)
   331  	legacyBehaviorPre130 := versions.LessThan(version, "1.30")
   332  	legacyRemovalWaitPre134 := false
   333  
   334  	// The wait condition defaults to "not-running".
   335  	waitCondition := containerpkg.WaitConditionNotRunning
   336  	if !legacyBehaviorPre130 {
   337  		if err := httputils.ParseForm(r); err != nil {
   338  			return err
   339  		}
   340  		switch container.WaitCondition(r.Form.Get("condition")) {
   341  		case container.WaitConditionNextExit:
   342  			waitCondition = containerpkg.WaitConditionNextExit
   343  		case container.WaitConditionRemoved:
   344  			waitCondition = containerpkg.WaitConditionRemoved
   345  			legacyRemovalWaitPre134 = versions.LessThan(version, "1.34")
   346  		}
   347  	}
   348  
   349  	waitC, err := s.backend.ContainerWait(ctx, vars["name"], waitCondition)
   350  	if err != nil {
   351  		return err
   352  	}
   353  
   354  	w.Header().Set("Content-Type", "application/json")
   355  
   356  	if !legacyBehaviorPre130 {
   357  		// Write response header immediately.
   358  		w.WriteHeader(http.StatusOK)
   359  		if flusher, ok := w.(http.Flusher); ok {
   360  			flusher.Flush()
   361  		}
   362  	}
   363  
   364  	// Block on the result of the wait operation.
   365  	status := <-waitC
   366  
   367  	// With API < 1.34, wait on WaitConditionRemoved did not return
   368  	// in case container removal failed. The only way to report an
   369  	// error back to the client is to not write anything (i.e. send
   370  	// an empty response which will be treated as an error).
   371  	if legacyRemovalWaitPre134 && status.Err() != nil {
   372  		return nil
   373  	}
   374  
   375  	var waitError *container.ContainerWaitOKBodyError
   376  	if status.Err() != nil {
   377  		waitError = &container.ContainerWaitOKBodyError{Message: status.Err().Error()}
   378  	}
   379  
   380  	return json.NewEncoder(w).Encode(&container.ContainerWaitOKBody{
   381  		StatusCode: int64(status.ExitCode()),
   382  		Error:      waitError,
   383  	})
   384  }
   385  
   386  func (s *containerRouter) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   387  	changes, err := s.backend.ContainerChanges(vars["name"])
   388  	if err != nil {
   389  		return err
   390  	}
   391  
   392  	return httputils.WriteJSON(w, http.StatusOK, changes)
   393  }
   394  
   395  func (s *containerRouter) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   396  	if err := httputils.ParseForm(r); err != nil {
   397  		return err
   398  	}
   399  
   400  	procList, err := s.backend.ContainerTop(vars["name"], r.Form.Get("ps_args"))
   401  	if err != nil {
   402  		return err
   403  	}
   404  
   405  	return httputils.WriteJSON(w, http.StatusOK, procList)
   406  }
   407  
   408  func (s *containerRouter) postContainerRename(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  	name := vars["name"]
   414  	newName := r.Form.Get("name")
   415  	if err := s.backend.ContainerRename(name, newName); err != nil {
   416  		return err
   417  	}
   418  	w.WriteHeader(http.StatusNoContent)
   419  	return nil
   420  }
   421  
   422  func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   423  	if err := httputils.ParseForm(r); err != nil {
   424  		return err
   425  	}
   426  	if err := httputils.CheckForJSON(r); err != nil {
   427  		return err
   428  	}
   429  
   430  	var updateConfig container.UpdateConfig
   431  
   432  	decoder := json.NewDecoder(r.Body)
   433  	if err := decoder.Decode(&updateConfig); err != nil {
   434  		return err
   435  	}
   436  	if versions.LessThan(httputils.VersionFromContext(ctx), "1.40") {
   437  		updateConfig.PidsLimit = nil
   438  	}
   439  	if updateConfig.PidsLimit != nil && *updateConfig.PidsLimit <= 0 {
   440  		// Both `0` and `-1` are accepted to set "unlimited" when updating.
   441  		// Historically, any negative value was accepted, so treat them as
   442  		// "unlimited" as well.
   443  		var unlimited int64
   444  		updateConfig.PidsLimit = &unlimited
   445  	}
   446  
   447  	hostConfig := &container.HostConfig{
   448  		Resources:     updateConfig.Resources,
   449  		RestartPolicy: updateConfig.RestartPolicy,
   450  	}
   451  
   452  	name := vars["name"]
   453  	resp, err := s.backend.ContainerUpdate(name, hostConfig)
   454  	if err != nil {
   455  		return err
   456  	}
   457  
   458  	return httputils.WriteJSON(w, http.StatusOK, resp)
   459  }
   460  
   461  func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   462  	if err := httputils.ParseForm(r); err != nil {
   463  		return err
   464  	}
   465  	if err := httputils.CheckForJSON(r); err != nil {
   466  		return err
   467  	}
   468  
   469  	name := r.Form.Get("name")
   470  
   471  	config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body)
   472  	if err != nil {
   473  		return err
   474  	}
   475  	version := httputils.VersionFromContext(ctx)
   476  	adjustCPUShares := versions.LessThan(version, "1.19")
   477  
   478  	// When using API 1.24 and under, the client is responsible for removing the container
   479  	if hostConfig != nil && versions.LessThan(version, "1.25") {
   480  		hostConfig.AutoRemove = false
   481  	}
   482  
   483  	if hostConfig != nil && versions.LessThan(version, "1.40") {
   484  		// Ignore BindOptions.NonRecursive because it was added in API 1.40.
   485  		for _, m := range hostConfig.Mounts {
   486  			if bo := m.BindOptions; bo != nil {
   487  				bo.NonRecursive = false
   488  			}
   489  		}
   490  		// Ignore KernelMemoryTCP because it was added in API 1.40.
   491  		hostConfig.KernelMemoryTCP = 0
   492  
   493  		// Older clients (API < 1.40) expects the default to be shareable, make them happy
   494  		if hostConfig.IpcMode.IsEmpty() {
   495  			hostConfig.IpcMode = container.IpcMode("shareable")
   496  		}
   497  	}
   498  	if hostConfig != nil && versions.LessThan(version, "1.41") && !s.cgroup2 {
   499  		// Older clients expect the default to be "host" on cgroup v1 hosts
   500  		if hostConfig.CgroupnsMode.IsEmpty() {
   501  			hostConfig.CgroupnsMode = container.CgroupnsMode("host")
   502  		}
   503  	}
   504  
   505  	var platform *specs.Platform
   506  	if versions.GreaterThanOrEqualTo(version, "1.41") {
   507  		if v := r.Form.Get("platform"); v != "" {
   508  			p, err := platforms.Parse(v)
   509  			if err != nil {
   510  				return errdefs.InvalidParameter(err)
   511  			}
   512  			platform = &p
   513  		}
   514  	}
   515  
   516  	if hostConfig != nil && hostConfig.PidsLimit != nil && *hostConfig.PidsLimit <= 0 {
   517  		// Don't set a limit if either no limit was specified, or "unlimited" was
   518  		// explicitly set.
   519  		// Both `0` and `-1` are accepted as "unlimited", and historically any
   520  		// negative value was accepted, so treat those as "unlimited" as well.
   521  		hostConfig.PidsLimit = nil
   522  	}
   523  
   524  	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
   525  		Name:             name,
   526  		Config:           config,
   527  		HostConfig:       hostConfig,
   528  		NetworkingConfig: networkingConfig,
   529  		AdjustCPUShares:  adjustCPUShares,
   530  		Platform:         platform,
   531  	})
   532  	if err != nil {
   533  		return err
   534  	}
   535  
   536  	return httputils.WriteJSON(w, http.StatusCreated, ccr)
   537  }
   538  
   539  func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   540  	if err := httputils.ParseForm(r); err != nil {
   541  		return err
   542  	}
   543  
   544  	name := vars["name"]
   545  	config := &types.ContainerRmConfig{
   546  		ForceRemove:  httputils.BoolValue(r, "force"),
   547  		RemoveVolume: httputils.BoolValue(r, "v"),
   548  		RemoveLink:   httputils.BoolValue(r, "link"),
   549  	}
   550  
   551  	if err := s.backend.ContainerRm(name, config); err != nil {
   552  		return err
   553  	}
   554  
   555  	w.WriteHeader(http.StatusNoContent)
   556  
   557  	return nil
   558  }
   559  
   560  func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   561  	if err := httputils.ParseForm(r); err != nil {
   562  		return err
   563  	}
   564  
   565  	height, err := strconv.Atoi(r.Form.Get("h"))
   566  	if err != nil {
   567  		return errdefs.InvalidParameter(err)
   568  	}
   569  	width, err := strconv.Atoi(r.Form.Get("w"))
   570  	if err != nil {
   571  		return errdefs.InvalidParameter(err)
   572  	}
   573  
   574  	return s.backend.ContainerResize(vars["name"], height, width)
   575  }
   576  
   577  func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   578  	err := httputils.ParseForm(r)
   579  	if err != nil {
   580  		return err
   581  	}
   582  	containerName := vars["name"]
   583  
   584  	_, upgrade := r.Header["Upgrade"]
   585  	detachKeys := r.FormValue("detachKeys")
   586  
   587  	hijacker, ok := w.(http.Hijacker)
   588  	if !ok {
   589  		return errdefs.InvalidParameter(errors.Errorf("error attaching to container %s, hijack connection missing", containerName))
   590  	}
   591  
   592  	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
   593  		conn, _, err := hijacker.Hijack()
   594  		if err != nil {
   595  			return nil, nil, nil, err
   596  		}
   597  
   598  		// set raw mode
   599  		conn.Write([]byte{})
   600  
   601  		if upgrade {
   602  			fmt.Fprintf(conn, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
   603  		} else {
   604  			fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
   605  		}
   606  
   607  		closer := func() error {
   608  			httputils.CloseStreams(conn)
   609  			return nil
   610  		}
   611  		return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil
   612  	}
   613  
   614  	attachConfig := &backend.ContainerAttachConfig{
   615  		GetStreams: setupStreams,
   616  		UseStdin:   httputils.BoolValue(r, "stdin"),
   617  		UseStdout:  httputils.BoolValue(r, "stdout"),
   618  		UseStderr:  httputils.BoolValue(r, "stderr"),
   619  		Logs:       httputils.BoolValue(r, "logs"),
   620  		Stream:     httputils.BoolValue(r, "stream"),
   621  		DetachKeys: detachKeys,
   622  		MuxStreams: true,
   623  	}
   624  
   625  	if err = s.backend.ContainerAttach(containerName, attachConfig); err != nil {
   626  		logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err)
   627  		// Remember to close stream if error happens
   628  		conn, _, errHijack := hijacker.Hijack()
   629  		if errHijack == nil {
   630  			statusCode := httpstatus.FromError(err)
   631  			statusText := http.StatusText(statusCode)
   632  			fmt.Fprintf(conn, "HTTP/1.1 %d %s\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n%s\r\n", statusCode, statusText, err.Error())
   633  			httputils.CloseStreams(conn)
   634  		} else {
   635  			logrus.Errorf("Error Hijacking: %v", err)
   636  		}
   637  	}
   638  	return nil
   639  }
   640  
   641  func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   642  	if err := httputils.ParseForm(r); err != nil {
   643  		return err
   644  	}
   645  	containerName := vars["name"]
   646  
   647  	var err error
   648  	detachKeys := r.FormValue("detachKeys")
   649  
   650  	done := make(chan struct{})
   651  	started := make(chan struct{})
   652  
   653  	version := httputils.VersionFromContext(ctx)
   654  
   655  	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
   656  		wsChan := make(chan *websocket.Conn)
   657  		h := func(conn *websocket.Conn) {
   658  			wsChan <- conn
   659  			<-done
   660  		}
   661  
   662  		srv := websocket.Server{Handler: h, Handshake: nil}
   663  		go func() {
   664  			close(started)
   665  			srv.ServeHTTP(w, r)
   666  		}()
   667  
   668  		conn := <-wsChan
   669  		// In case version 1.28 and above, a binary frame will be sent.
   670  		// See 28176 for details.
   671  		if versions.GreaterThanOrEqualTo(version, "1.28") {
   672  			conn.PayloadType = websocket.BinaryFrame
   673  		}
   674  		return conn, conn, conn, nil
   675  	}
   676  
   677  	attachConfig := &backend.ContainerAttachConfig{
   678  		GetStreams: setupStreams,
   679  		Logs:       httputils.BoolValue(r, "logs"),
   680  		Stream:     httputils.BoolValue(r, "stream"),
   681  		DetachKeys: detachKeys,
   682  		UseStdin:   true,
   683  		UseStdout:  true,
   684  		UseStderr:  true,
   685  		MuxStreams: false, // TODO: this should be true since it's a single stream for both stdout and stderr
   686  	}
   687  
   688  	err = s.backend.ContainerAttach(containerName, attachConfig)
   689  	close(done)
   690  	select {
   691  	case <-started:
   692  		if err != nil {
   693  			logrus.Errorf("Error attaching websocket: %s", err)
   694  		} else {
   695  			logrus.Debug("websocket connection was closed by client")
   696  		}
   697  		return nil
   698  	default:
   699  	}
   700  	return err
   701  }
   702  
   703  func (s *containerRouter) postContainersPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   704  	if err := httputils.ParseForm(r); err != nil {
   705  		return err
   706  	}
   707  
   708  	pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
   709  	if err != nil {
   710  		return errdefs.InvalidParameter(err)
   711  	}
   712  
   713  	pruneReport, err := s.backend.ContainersPrune(ctx, pruneFilters)
   714  	if err != nil {
   715  		return err
   716  	}
   717  	return httputils.WriteJSON(w, http.StatusOK, pruneReport)
   718  }