diff --git a/api_test.go b/api_test.go index f652b07844..034008b2eb 100644 --- a/api_test.go +++ b/api_test.go @@ -951,7 +951,7 @@ func TestPostContainersAttach(t *testing.T) { }) setTimeout(t, "read/write assertion timed out", 2*time.Second, func() { - if err := assertPipe("hello\n", string([]byte{1, 0, 0, 0, 6, 0, 0, 0})+"hello", stdout, stdinPipe, 15); err != nil { + if err := assertPipe("hello\n", string([]byte{1, 0, 0, 0, 0, 0, 0, 6})+"hello", stdout, stdinPipe, 15); err != nil { t.Fatal(err) } }) @@ -1040,7 +1040,7 @@ func TestPostContainersAttachStderr(t *testing.T) { }) setTimeout(t, "read/write assertion timed out", 2*time.Second, func() { - if err := assertPipe("hello\n", string([]byte{2, 0, 0, 0, 6, 0, 0, 0})+"hello", stdout, stdinPipe, 15); err != nil { + if err := assertPipe("hello\n", string([]byte{2, 0, 0, 0, 0, 0, 0, 6})+"hello", stdout, stdinPipe, 15); err != nil { t.Fatal(err) } }) diff --git a/docs/sources/api/attach_api_1.6.rst b/docs/sources/api/attach_api_1.6.rst deleted file mode 100644 index d165b12897..0000000000 --- a/docs/sources/api/attach_api_1.6.rst +++ /dev/null @@ -1,52 +0,0 @@ -:title: Attach stream API -:description: API Documentation for the Attach command in Docker -:keywords: API, Docker, Attach, Stream, REST, documentation - -================= -Docker Attach stream API -================= - -.. contents:: Table of Contents - -1. Brief introduction -===================== - -- This is the Attach stream API for Docker - -2. Format -========= - -The attach format is a Header and a Payload (frame). - -2.1 Header -^^^^^^^^^^ - -The header will contain the information on which stream write -the stream (stdout or stderr). -It also contain the size of the associated frame encoded on the last 4 bytes (uint32). - -It is encoded on the first 8 bytes like this: -header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4} - -STREAM_TYPE can be: -- 0: stdin (will be writen on stdout) -- 1: stdout -- 2: stderr - -SIZE1, SIZE2, SIZE3, SIZE4 are the 4 bytes of the uint32 size. - -2.1 Payload (frame) -^^^^^^^^^^^^^^^^^^^ - -The payload is the raw stream. - -3. Implementation -================= - -The simplest way to implement the Attach protocol is the following: - -1) Read 8 bytes -2) chose stdout or stderr depending on the first byte -3) Extract the frame size from the last 4 byets -4) Read the extracted size and output it on the correct output -5) Goto 1) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 480e24bdaf..9a6b72fafa 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -44,10 +44,10 @@ What's new .. http:post:: /containers/(id)/attach **New!** You can now split stderr from stdout. This is done by prefixing - a header to each transmition. See :doc:`attach_api_1.6`. + a header to each transmition. See :http:post:`/containers/(id)/attach`. The WebSocket attach is unchanged. - Note that attach calls on previous API version didn't change. Stdout and - stderr are merge. + Note that attach calls on the previous API version didn't change. Stdout and + stderr are merged. :doc:`docker_remote_api_v1.5` diff --git a/docs/sources/api/docker_remote_api_v1.6.rst b/docs/sources/api/docker_remote_api_v1.6.rst index b98e770b46..d84d5e85d0 100644 --- a/docs/sources/api/docker_remote_api_v1.6.rst +++ b/docs/sources/api/docker_remote_api_v1.6.rst @@ -468,7 +468,7 @@ Attach to a container HTTP/1.1 200 OK Content-Type: application/vnd.docker.raw-stream - {{ PREFIXED STREAM }} See :doc:`attach_api_1.6` + {{ STREAM }} :query logs: 1/True/true or 0/False/false, return logs. Default false :query stream: 1/True/true or 0/False/false, return stream. Default false @@ -480,6 +480,49 @@ Attach to a container :statuscode 404: no such container :statuscode 500: server error + **Stream details**: + + When using the TTY setting is enabled in + :http:post:`/containers/create`, the stream is the raw data + from the process PTY and client's stdin. When the TTY is + disabled, then the stream is multiplexed to separate stdout + and stderr. + + The format is a **Header** and a **Payload** (frame). + + **HEADER** + + The header will contain the information on which stream write + the stream (stdout or stderr). It also contain the size of + the associated frame encoded on the last 4 bytes (uint32). + + It is encoded on the first 8 bytes like this:: + + header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4} + + ``STREAM_TYPE`` can be: + + - 0: stdin (will be writen on stdout) + - 1: stdout + - 2: stderr + + ``SIZE1, SIZE2, SIZE3, SIZE4`` are the 4 bytes of the uint32 size encoded as big endian. + + **PAYLOAD** + + The payload is the raw stream. + + **IMPLEMENTATION** + + The simplest way to implement the Attach protocol is the following: + + 1) Read 8 bytes + 2) chose stdout or stderr depending on the first byte + 3) Extract the frame size from the last 4 byets + 4) Read the extracted size and output it on the correct output + 5) Goto 1) + + Wait a container **************** diff --git a/server.go b/server.go index b40f60556e..38b7bcf147 100644 --- a/server.go +++ b/server.go @@ -1208,16 +1208,20 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std } else { dec := json.NewDecoder(cLog) for { - var l utils.JSONLog - if err := dec.Decode(&l); err == io.EOF { + l := &utils.JSONLog{} + + if err := dec.Decode(l); err == io.EOF { break } else if err != nil { utils.Debugf("Error streaming logs: %s", err) break } - if (l.Stream == "stdout" && stdout) || (l.Stream == "stderr" && stderr) { + if l.Stream == "stdout" && stdout { fmt.Fprintf(outStream, "%s", l.Log) } + if l.Stream == "stderr" && stderr { + fmt.Fprintf(errStream, "%s", l.Log) + } } } } diff --git a/utils/stdcopy.go b/utils/stdcopy.go index 4965ea41b0..42dad738e4 100644 --- a/utils/stdcopy.go +++ b/utils/stdcopy.go @@ -4,18 +4,8 @@ import ( "encoding/binary" "errors" "io" - "unsafe" ) -func CheckBigEndian() bool { - var x uint32 = 0x01020304 - - if *(*byte)(unsafe.Pointer(&x)) == 0x01 { - return true - } - return false -} - const ( StdWriterPrefixLen = 8 StdWriterFdIndex = 0 @@ -32,16 +22,15 @@ var ( type StdWriter struct { io.Writer - prefix StdType - sizeBuf []byte - byteOrder binary.ByteOrder + prefix StdType + sizeBuf []byte } func (w *StdWriter) Write(buf []byte) (n int, err error) { if w == nil || w.Writer == nil { return 0, errors.New("Writer not instanciated") } - w.byteOrder.PutUint32(w.prefix[4:], uint32(len(buf))) + binary.BigEndian.PutUint32(w.prefix[4:], uint32(len(buf))) buf = append(w.prefix[:], buf...) n, err = w.Writer.Write(buf) @@ -55,18 +44,10 @@ func NewStdWriter(w io.Writer, t StdType) *StdWriter { return nil } - var bo binary.ByteOrder - - if CheckBigEndian() { - bo = binary.BigEndian - } else { - bo = binary.LittleEndian - } return &StdWriter{ - Writer: w, - prefix: t, - sizeBuf: make([]byte, 4), - byteOrder: bo, + Writer: w, + prefix: t, + sizeBuf: make([]byte, 4), } } @@ -91,17 +72,9 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) nr, nw int er, ew error out io.Writer - byteOrder binary.ByteOrder frameSize int ) - // Check the machine's endianness - if CheckBigEndian() { - byteOrder = binary.BigEndian - } else { - byteOrder = binary.LittleEndian - } - for { // Make sure we have at least a full header for nr < StdWriterPrefixLen { @@ -132,7 +105,7 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) } // Retrieve the size of the frame - frameSize = int(byteOrder.Uint32(buf[StdWriterSizeIndex : StdWriterSizeIndex+4])) + frameSize = int(binary.BigEndian.Uint32(buf[StdWriterSizeIndex : StdWriterSizeIndex+4])) // Check if the buffer is big enough to read the frame. // Extend it if necessary.