diff --git a/api/server/router/container/backend.go b/api/server/router/container/backend.go index 33bf3c5b7a..0ea785a39f 100644 --- a/api/server/router/container/backend.go +++ b/api/server/router/container/backend.go @@ -44,7 +44,6 @@ type stateBackend interface { ContainerUnpause(name string) error ContainerWait(name string, timeout time.Duration) (int, error) Exists(id string) bool - IsPaused(id string) bool } // monitorBackend includes functions to implement to provide containers monitoring functionality. diff --git a/api/server/router/container/container_routes.go b/api/server/router/container/container_routes.go index 94c8a9661f..e848160496 100644 --- a/api/server/router/container/container_routes.go +++ b/api/server/router/container/container_routes.go @@ -400,29 +400,11 @@ func (s *containerRouter) postContainersAttach(ctx context.Context, w http.Respo } containerName := vars["name"] - if !s.backend.Exists(containerName) { - return derr.ErrorCodeNoSuchContainer.WithArgs(containerName) - } - - if s.backend.IsPaused(containerName) { - return derr.ErrorCodePausedContainer.WithArgs(containerName) - } - - inStream, outStream, err := httputils.HijackConnection(w) - if err != nil { - return err - } - defer httputils.CloseStreams(inStream, outStream) - - if _, ok := r.Header["Upgrade"]; ok { - fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") - } else { - fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") - } + _, upgrade := r.Header["Upgrade"] attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{ - InStream: inStream, - OutStream: outStream, + Hijacker: w.(http.Hijacker), + Upgrade: upgrade, UseStdin: httputils.BoolValue(r, "stdin"), UseStdout: httputils.BoolValue(r, "stdout"), UseStderr: httputils.BoolValue(r, "stderr"), @@ -430,11 +412,7 @@ func (s *containerRouter) postContainersAttach(ctx context.Context, w http.Respo Stream: httputils.BoolValue(r, "stream"), } - if err := s.backend.ContainerAttachWithLogs(containerName, attachWithLogsConfig); err != nil { - fmt.Fprintf(outStream, "Error attaching: %s\n", err) - } - - return nil + return s.backend.ContainerAttachWithLogs(containerName, attachWithLogsConfig) } func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/daemon/attach.go b/daemon/attach.go index 12b206e13b..451d41c01c 100644 --- a/daemon/attach.go +++ b/daemon/attach.go @@ -1,53 +1,84 @@ package daemon import ( + "fmt" "io" + "net/http" "time" "github.com/Sirupsen/logrus" "github.com/docker/docker/container" "github.com/docker/docker/daemon/logger" + derr "github.com/docker/docker/errors" "github.com/docker/docker/pkg/stdcopy" ) // ContainerAttachWithLogsConfig holds the streams to use when connecting to a container to view logs. type ContainerAttachWithLogsConfig struct { - InStream io.ReadCloser - OutStream io.Writer - UseStdin, UseStdout, UseStderr bool - Logs, Stream bool + Hijacker http.Hijacker + Upgrade bool + UseStdin bool + UseStdout bool + UseStderr bool + Logs bool + Stream bool } // ContainerAttachWithLogs attaches to logs according to the config passed in. See ContainerAttachWithLogsConfig. func (daemon *Daemon) ContainerAttachWithLogs(prefixOrName string, c *ContainerAttachWithLogsConfig) error { + if c.Hijacker == nil { + return derr.ErrorCodeNoHijackConnection.WithArgs(prefixOrName) + } container, err := daemon.GetContainer(prefixOrName) + if err != nil { + return derr.ErrorCodeNoSuchContainer.WithArgs(prefixOrName) + } + if container.IsPaused() { + return derr.ErrorCodePausedContainer.WithArgs(prefixOrName) + } + + conn, _, err := c.Hijacker.Hijack() if err != nil { return err } + defer conn.Close() + // Flush the options to make sure the client sets the raw mode + conn.Write([]byte{}) + inStream := conn.(io.ReadCloser) + outStream := conn.(io.Writer) + + if c.Upgrade { + fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n") + } else { + fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") + } var errStream io.Writer if !container.Config.Tty { - errStream = stdcopy.NewStdWriter(c.OutStream, stdcopy.Stderr) - c.OutStream = stdcopy.NewStdWriter(c.OutStream, stdcopy.Stdout) + errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) + outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) } else { - errStream = c.OutStream + errStream = outStream } var stdin io.ReadCloser var stdout, stderr io.Writer if c.UseStdin { - stdin = c.InStream + stdin = inStream } if c.UseStdout { - stdout = c.OutStream + stdout = outStream } if c.UseStderr { stderr = errStream } - return daemon.attachWithLogs(container, stdin, stdout, stderr, c.Logs, c.Stream) + if err := daemon.attachWithLogs(container, stdin, stdout, stderr, c.Logs, c.Stream); err != nil { + fmt.Fprintf(outStream, "Error attaching: %s\n", err) + } + return nil } // ContainerWsAttachWithLogsConfig attach with websockets, since all diff --git a/errors/server.go b/errors/server.go index 1a7af00a13..b54d2d5436 100644 --- a/errors/server.go +++ b/errors/server.go @@ -33,4 +33,13 @@ var ( Description: "Docker's networking stack is disabled for this platform", HTTPStatusCode: http.StatusNotFound, }) + + // ErrorCodeNoHijackConnection is generated when a request tries to attach to a container + // but the connection to hijack is not provided. + ErrorCodeNoHijackConnection = errcode.Register(errGroup, errcode.ErrorDescriptor{ + Value: "HIJACK_CONNECTION_MISSING", + Message: "error attaching to container %s, hijack connection missing", + Description: "The caller didn't provide a connection to hijack", + HTTPStatusCode: http.StatusBadRequest, + }) )