github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/api/server/router/container/container_routes.go (about)

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