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