github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/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/docker/docker/api/server/httputils"
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/docker/docker/api/types/backend"
    15  	"github.com/docker/docker/api/types/container"
    16  	"github.com/docker/docker/api/types/filters"
    17  	"github.com/docker/docker/api/types/versions"
    18  	containerpkg "github.com/docker/docker/container"
    19  	"github.com/docker/docker/errdefs"
    20  	"github.com/docker/docker/pkg/ioutils"
    21  	"github.com/docker/docker/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  
   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  	waitC, err := s.backend.ContainerWait(ctx, vars["name"], waitCondition)
   342  	if err != nil {
   343  		return err
   344  	}
   345  
   346  	w.Header().Set("Content-Type", "application/json")
   347  
   348  	if !legacyBehaviorPre130 {
   349  		// Write response header immediately.
   350  		w.WriteHeader(http.StatusOK)
   351  		if flusher, ok := w.(http.Flusher); ok {
   352  			flusher.Flush()
   353  		}
   354  	}
   355  
   356  	// Block on the result of the wait operation.
   357  	status := <-waitC
   358  
   359  	// With API < 1.34, wait on WaitConditionRemoved did not return
   360  	// in case container removal failed. The only way to report an
   361  	// error back to the client is to not write anything (i.e. send
   362  	// an empty response which will be treated as an error).
   363  	if legacyRemovalWaitPre134 && status.Err() != nil {
   364  		return nil
   365  	}
   366  
   367  	var waitError *container.ContainerWaitOKBodyError
   368  	if status.Err() != nil {
   369  		waitError = &container.ContainerWaitOKBodyError{Message: status.Err().Error()}
   370  	}
   371  
   372  	return json.NewEncoder(w).Encode(&container.ContainerWaitOKBody{
   373  		StatusCode: int64(status.ExitCode()),
   374  		Error:      waitError,
   375  	})
   376  }
   377  
   378  func (s *containerRouter) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   379  	changes, err := s.backend.ContainerChanges(vars["name"])
   380  	if err != nil {
   381  		return err
   382  	}
   383  
   384  	return httputils.WriteJSON(w, http.StatusOK, changes)
   385  }
   386  
   387  func (s *containerRouter) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   388  	if err := httputils.ParseForm(r); err != nil {
   389  		return err
   390  	}
   391  
   392  	procList, err := s.backend.ContainerTop(vars["name"], r.Form.Get("ps_args"))
   393  	if err != nil {
   394  		return err
   395  	}
   396  
   397  	return httputils.WriteJSON(w, http.StatusOK, procList)
   398  }
   399  
   400  func (s *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   401  	if err := httputils.ParseForm(r); err != nil {
   402  		return err
   403  	}
   404  
   405  	name := vars["name"]
   406  	newName := r.Form.Get("name")
   407  	if err := s.backend.ContainerRename(name, newName); err != nil {
   408  		return err
   409  	}
   410  	w.WriteHeader(http.StatusNoContent)
   411  	return nil
   412  }
   413  
   414  func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   415  	if err := httputils.ParseForm(r); err != nil {
   416  		return err
   417  	}
   418  	if err := httputils.CheckForJSON(r); err != nil {
   419  		return err
   420  	}
   421  
   422  	var updateConfig container.UpdateConfig
   423  
   424  	decoder := json.NewDecoder(r.Body)
   425  	if err := decoder.Decode(&updateConfig); err != nil {
   426  		return err
   427  	}
   428  	if versions.LessThan(httputils.VersionFromContext(ctx), "1.40") {
   429  		updateConfig.PidsLimit = nil
   430  	}
   431  	if updateConfig.PidsLimit != nil && *updateConfig.PidsLimit <= 0 {
   432  		// Both `0` and `-1` are accepted to set "unlimited" when updating.
   433  		// Historically, any negative value was accepted, so treat them as
   434  		// "unlimited" as well.
   435  		var unlimited int64
   436  		updateConfig.PidsLimit = &unlimited
   437  	}
   438  
   439  	hostConfig := &container.HostConfig{
   440  		Resources:     updateConfig.Resources,
   441  		RestartPolicy: updateConfig.RestartPolicy,
   442  	}
   443  
   444  	name := vars["name"]
   445  	resp, err := s.backend.ContainerUpdate(name, hostConfig)
   446  	if err != nil {
   447  		return err
   448  	}
   449  
   450  	return httputils.WriteJSON(w, http.StatusOK, resp)
   451  }
   452  
   453  func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   454  	if err := httputils.ParseForm(r); err != nil {
   455  		return err
   456  	}
   457  	if err := httputils.CheckForJSON(r); err != nil {
   458  		return err
   459  	}
   460  
   461  	name := r.Form.Get("name")
   462  
   463  	config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body)
   464  	if err != nil {
   465  		return err
   466  	}
   467  	version := httputils.VersionFromContext(ctx)
   468  	adjustCPUShares := versions.LessThan(version, "1.19")
   469  
   470  	// When using API 1.24 and under, the client is responsible for removing the container
   471  	if hostConfig != nil && versions.LessThan(version, "1.25") {
   472  		hostConfig.AutoRemove = false
   473  	}
   474  
   475  	if hostConfig != nil && versions.LessThan(version, "1.40") {
   476  		// Ignore BindOptions.NonRecursive because it was added in API 1.40.
   477  		for _, m := range hostConfig.Mounts {
   478  			if bo := m.BindOptions; bo != nil {
   479  				bo.NonRecursive = false
   480  			}
   481  		}
   482  		// Ignore KernelMemoryTCP because it was added in API 1.40.
   483  		hostConfig.KernelMemoryTCP = 0
   484  
   485  		// Ignore Capabilities because it was added in API 1.40.
   486  		hostConfig.Capabilities = nil
   487  
   488  		// Older clients (API < 1.40) expects the default to be shareable, make them happy
   489  		if hostConfig.IpcMode.IsEmpty() {
   490  			hostConfig.IpcMode = container.IpcMode("shareable")
   491  		}
   492  	}
   493  
   494  	if hostConfig != nil && hostConfig.PidsLimit != nil && *hostConfig.PidsLimit <= 0 {
   495  		// Don't set a limit if either no limit was specified, or "unlimited" was
   496  		// explicitly set.
   497  		// Both `0` and `-1` are accepted as "unlimited", and historically any
   498  		// negative value was accepted, so treat those as "unlimited" as well.
   499  		hostConfig.PidsLimit = nil
   500  	}
   501  
   502  	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
   503  		Name:             name,
   504  		Config:           config,
   505  		HostConfig:       hostConfig,
   506  		NetworkingConfig: networkingConfig,
   507  		AdjustCPUShares:  adjustCPUShares,
   508  	})
   509  	if err != nil {
   510  		return err
   511  	}
   512  
   513  	return httputils.WriteJSON(w, http.StatusCreated, ccr)
   514  }
   515  
   516  func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   517  	if err := httputils.ParseForm(r); err != nil {
   518  		return err
   519  	}
   520  
   521  	name := vars["name"]
   522  	config := &types.ContainerRmConfig{
   523  		ForceRemove:  httputils.BoolValue(r, "force"),
   524  		RemoveVolume: httputils.BoolValue(r, "v"),
   525  		RemoveLink:   httputils.BoolValue(r, "link"),
   526  	}
   527  
   528  	if err := s.backend.ContainerRm(name, config); err != nil {
   529  		return err
   530  	}
   531  
   532  	w.WriteHeader(http.StatusNoContent)
   533  
   534  	return nil
   535  }
   536  
   537  func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   538  	if err := httputils.ParseForm(r); err != nil {
   539  		return err
   540  	}
   541  
   542  	height, err := strconv.Atoi(r.Form.Get("h"))
   543  	if err != nil {
   544  		return errdefs.InvalidParameter(err)
   545  	}
   546  	width, err := strconv.Atoi(r.Form.Get("w"))
   547  	if err != nil {
   548  		return errdefs.InvalidParameter(err)
   549  	}
   550  
   551  	return s.backend.ContainerResize(vars["name"], height, width)
   552  }
   553  
   554  func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   555  	err := httputils.ParseForm(r)
   556  	if err != nil {
   557  		return err
   558  	}
   559  	containerName := vars["name"]
   560  
   561  	_, upgrade := r.Header["Upgrade"]
   562  	detachKeys := r.FormValue("detachKeys")
   563  
   564  	hijacker, ok := w.(http.Hijacker)
   565  	if !ok {
   566  		return errdefs.InvalidParameter(errors.Errorf("error attaching to container %s, hijack connection missing", containerName))
   567  	}
   568  
   569  	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
   570  		conn, _, err := hijacker.Hijack()
   571  		if err != nil {
   572  			return nil, nil, nil, err
   573  		}
   574  
   575  		// set raw mode
   576  		conn.Write([]byte{})
   577  
   578  		if upgrade {
   579  			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")
   580  		} else {
   581  			fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
   582  		}
   583  
   584  		closer := func() error {
   585  			httputils.CloseStreams(conn)
   586  			return nil
   587  		}
   588  		return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil
   589  	}
   590  
   591  	attachConfig := &backend.ContainerAttachConfig{
   592  		GetStreams: setupStreams,
   593  		UseStdin:   httputils.BoolValue(r, "stdin"),
   594  		UseStdout:  httputils.BoolValue(r, "stdout"),
   595  		UseStderr:  httputils.BoolValue(r, "stderr"),
   596  		Logs:       httputils.BoolValue(r, "logs"),
   597  		Stream:     httputils.BoolValue(r, "stream"),
   598  		DetachKeys: detachKeys,
   599  		MuxStreams: true,
   600  	}
   601  
   602  	if err = s.backend.ContainerAttach(containerName, attachConfig); err != nil {
   603  		logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err)
   604  		// Remember to close stream if error happens
   605  		conn, _, errHijack := hijacker.Hijack()
   606  		if errHijack == nil {
   607  			statusCode := errdefs.GetHTTPErrorStatusCode(err)
   608  			statusText := http.StatusText(statusCode)
   609  			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())
   610  			httputils.CloseStreams(conn)
   611  		} else {
   612  			logrus.Errorf("Error Hijacking: %v", err)
   613  		}
   614  	}
   615  	return nil
   616  }
   617  
   618  func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   619  	if err := httputils.ParseForm(r); err != nil {
   620  		return err
   621  	}
   622  	containerName := vars["name"]
   623  
   624  	var err error
   625  	detachKeys := r.FormValue("detachKeys")
   626  
   627  	done := make(chan struct{})
   628  	started := make(chan struct{})
   629  
   630  	version := httputils.VersionFromContext(ctx)
   631  
   632  	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
   633  		wsChan := make(chan *websocket.Conn)
   634  		h := func(conn *websocket.Conn) {
   635  			wsChan <- conn
   636  			<-done
   637  		}
   638  
   639  		srv := websocket.Server{Handler: h, Handshake: nil}
   640  		go func() {
   641  			close(started)
   642  			srv.ServeHTTP(w, r)
   643  		}()
   644  
   645  		conn := <-wsChan
   646  		// In case version 1.28 and above, a binary frame will be sent.
   647  		// See 28176 for details.
   648  		if versions.GreaterThanOrEqualTo(version, "1.28") {
   649  			conn.PayloadType = websocket.BinaryFrame
   650  		}
   651  		return conn, conn, conn, nil
   652  	}
   653  
   654  	attachConfig := &backend.ContainerAttachConfig{
   655  		GetStreams: setupStreams,
   656  		Logs:       httputils.BoolValue(r, "logs"),
   657  		Stream:     httputils.BoolValue(r, "stream"),
   658  		DetachKeys: detachKeys,
   659  		UseStdin:   true,
   660  		UseStdout:  true,
   661  		UseStderr:  true,
   662  		MuxStreams: false, // TODO: this should be true since it's a single stream for both stdout and stderr
   663  	}
   664  
   665  	err = s.backend.ContainerAttach(containerName, attachConfig)
   666  	close(done)
   667  	select {
   668  	case <-started:
   669  		if err != nil {
   670  			logrus.Errorf("Error attaching websocket: %s", err)
   671  		} else {
   672  			logrus.Debug("websocket connection was closed by client")
   673  		}
   674  		return nil
   675  	default:
   676  	}
   677  	return err
   678  }
   679  
   680  func (s *containerRouter) postContainersPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   681  	if err := httputils.ParseForm(r); err != nil {
   682  		return err
   683  	}
   684  
   685  	pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
   686  	if err != nil {
   687  		return errdefs.InvalidParameter(err)
   688  	}
   689  
   690  	pruneReport, err := s.backend.ContainersPrune(ctx, pruneFilters)
   691  	if err != nil {
   692  		return err
   693  	}
   694  	return httputils.WriteJSON(w, http.StatusOK, pruneReport)
   695  }