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  }