mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Update docs + fix endian issue
This commit is contained in:
parent
082d142024
commit
cb18a6e1b9
6 changed files with 63 additions and 95 deletions
|
@ -951,7 +951,7 @@ func TestPostContainersAttach(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
setTimeout(t, "read/write assertion timed out", 2*time.Second, func() {
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1040,7 +1040,7 @@ func TestPostContainersAttachStderr(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
setTimeout(t, "read/write assertion timed out", 2*time.Second, func() {
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
|
|
@ -44,10 +44,10 @@ What's new
|
||||||
.. http:post:: /containers/(id)/attach
|
.. http:post:: /containers/(id)/attach
|
||||||
|
|
||||||
**New!** You can now split stderr from stdout. This is done by prefixing
|
**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.
|
The WebSocket attach is unchanged.
|
||||||
Note that attach calls on previous API version didn't change. Stdout and
|
Note that attach calls on the previous API version didn't change. Stdout and
|
||||||
stderr are merge.
|
stderr are merged.
|
||||||
|
|
||||||
|
|
||||||
:doc:`docker_remote_api_v1.5`
|
:doc:`docker_remote_api_v1.5`
|
||||||
|
|
|
@ -468,7 +468,7 @@ Attach to a container
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
Content-Type: application/vnd.docker.raw-stream
|
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 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
|
: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 404: no such container
|
||||||
:statuscode 500: server error
|
: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
|
Wait a container
|
||||||
****************
|
****************
|
||||||
|
|
10
server.go
10
server.go
|
@ -1208,16 +1208,20 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
|
||||||
} else {
|
} else {
|
||||||
dec := json.NewDecoder(cLog)
|
dec := json.NewDecoder(cLog)
|
||||||
for {
|
for {
|
||||||
var l utils.JSONLog
|
l := &utils.JSONLog{}
|
||||||
if err := dec.Decode(&l); err == io.EOF {
|
|
||||||
|
if err := dec.Decode(l); err == io.EOF {
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
utils.Debugf("Error streaming logs: %s", err)
|
utils.Debugf("Error streaming logs: %s", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (l.Stream == "stdout" && stdout) || (l.Stream == "stderr" && stderr) {
|
if l.Stream == "stdout" && stdout {
|
||||||
fmt.Fprintf(outStream, "%s", l.Log)
|
fmt.Fprintf(outStream, "%s", l.Log)
|
||||||
}
|
}
|
||||||
|
if l.Stream == "stderr" && stderr {
|
||||||
|
fmt.Fprintf(errStream, "%s", l.Log)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,18 +4,8 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func CheckBigEndian() bool {
|
|
||||||
var x uint32 = 0x01020304
|
|
||||||
|
|
||||||
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
StdWriterPrefixLen = 8
|
StdWriterPrefixLen = 8
|
||||||
StdWriterFdIndex = 0
|
StdWriterFdIndex = 0
|
||||||
|
@ -32,16 +22,15 @@ var (
|
||||||
|
|
||||||
type StdWriter struct {
|
type StdWriter struct {
|
||||||
io.Writer
|
io.Writer
|
||||||
prefix StdType
|
prefix StdType
|
||||||
sizeBuf []byte
|
sizeBuf []byte
|
||||||
byteOrder binary.ByteOrder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *StdWriter) Write(buf []byte) (n int, err error) {
|
func (w *StdWriter) Write(buf []byte) (n int, err error) {
|
||||||
if w == nil || w.Writer == nil {
|
if w == nil || w.Writer == nil {
|
||||||
return 0, errors.New("Writer not instanciated")
|
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...)
|
buf = append(w.prefix[:], buf...)
|
||||||
|
|
||||||
n, err = w.Writer.Write(buf)
|
n, err = w.Writer.Write(buf)
|
||||||
|
@ -55,18 +44,10 @@ func NewStdWriter(w io.Writer, t StdType) *StdWriter {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var bo binary.ByteOrder
|
|
||||||
|
|
||||||
if CheckBigEndian() {
|
|
||||||
bo = binary.BigEndian
|
|
||||||
} else {
|
|
||||||
bo = binary.LittleEndian
|
|
||||||
}
|
|
||||||
return &StdWriter{
|
return &StdWriter{
|
||||||
Writer: w,
|
Writer: w,
|
||||||
prefix: t,
|
prefix: t,
|
||||||
sizeBuf: make([]byte, 4),
|
sizeBuf: make([]byte, 4),
|
||||||
byteOrder: bo,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,17 +72,9 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
|
||||||
nr, nw int
|
nr, nw int
|
||||||
er, ew error
|
er, ew error
|
||||||
out io.Writer
|
out io.Writer
|
||||||
byteOrder binary.ByteOrder
|
|
||||||
frameSize int
|
frameSize int
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check the machine's endianness
|
|
||||||
if CheckBigEndian() {
|
|
||||||
byteOrder = binary.BigEndian
|
|
||||||
} else {
|
|
||||||
byteOrder = binary.LittleEndian
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// Make sure we have at least a full header
|
// Make sure we have at least a full header
|
||||||
for nr < StdWriterPrefixLen {
|
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
|
// 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.
|
// Check if the buffer is big enough to read the frame.
|
||||||
// Extend it if necessary.
|
// Extend it if necessary.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue