| package main |
| |
| import ( |
| "bytes" |
| "io" |
| "net/http" |
| "net/http/httputil" |
| "strings" |
| "time" |
| |
| "github.com/go-check/check" |
| "golang.org/x/net/websocket" |
| ) |
| |
| func (s *DockerSuite) TestGetContainersAttachWebsocket(c *check.C) { |
| testRequires(c, DaemonIsLinux) |
| out, _ := dockerCmd(c, "run", "-dit", "busybox", "cat") |
| |
| rwc, err := sockConn(time.Duration(10 * time.Second)) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| cleanedContainerID := strings.TrimSpace(out) |
| config, err := websocket.NewConfig( |
| "/containers/"+cleanedContainerID+"/attach/ws?stream=1&stdin=1&stdout=1&stderr=1", |
| "http://localhost", |
| ) |
| if err != nil { |
| c.Fatal(err) |
| } |
| |
| ws, err := websocket.NewClient(config, rwc) |
| if err != nil { |
| c.Fatal(err) |
| } |
| defer ws.Close() |
| |
| expected := []byte("hello") |
| actual := make([]byte, len(expected)) |
| |
| outChan := make(chan error) |
| go func() { |
| _, err := ws.Read(actual) |
| outChan <- err |
| close(outChan) |
| }() |
| |
| inChan := make(chan error) |
| go func() { |
| _, err := ws.Write(expected) |
| inChan <- err |
| close(inChan) |
| }() |
| |
| select { |
| case err := <-inChan: |
| if err != nil { |
| c.Fatal(err) |
| } |
| case <-time.After(5 * time.Second): |
| c.Fatal("Timeout writing to ws") |
| } |
| |
| select { |
| case err := <-outChan: |
| if err != nil { |
| c.Fatal(err) |
| } |
| case <-time.After(5 * time.Second): |
| c.Fatal("Timeout reading from ws") |
| } |
| |
| if !bytes.Equal(expected, actual) { |
| c.Fatal("Expected output on websocket to match input") |
| } |
| } |
| |
| // regression gh14320 |
| func (s *DockerSuite) TestPostContainersAttachContainerNotFound(c *check.C) { |
| status, body, err := sockRequest("POST", "/containers/doesnotexist/attach", nil) |
| c.Assert(status, check.Equals, http.StatusNotFound) |
| c.Assert(err, check.IsNil) |
| expected := "no such id: doesnotexist\n" |
| if !strings.Contains(string(body), expected) { |
| c.Fatalf("Expected response body to contain %q", expected) |
| } |
| } |
| |
| func (s *DockerSuite) TestGetContainersWsAttachContainerNotFound(c *check.C) { |
| status, body, err := sockRequest("GET", "/containers/doesnotexist/attach/ws", nil) |
| c.Assert(status, check.Equals, http.StatusNotFound) |
| c.Assert(err, check.IsNil) |
| expected := "no such id: doesnotexist\n" |
| if !strings.Contains(string(body), expected) { |
| c.Fatalf("Expected response body to contain %q", expected) |
| } |
| } |
| |
| func (s *DockerSuite) TestPostContainersAttach(c *check.C) { |
| testRequires(c, DaemonIsLinux) |
| out, _ := dockerCmd(c, "run", "-dit", "busybox", "cat") |
| |
| r, w := io.Pipe() |
| defer r.Close() |
| defer w.Close() |
| |
| conn, err := sockConn(time.Duration(10 * time.Second)) |
| c.Assert(err, check.IsNil) |
| |
| containerID := strings.TrimSpace(out) |
| |
| req, err := http.NewRequest("POST", "/containers/"+containerID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{})) |
| c.Assert(err, check.IsNil) |
| |
| client := httputil.NewClientConn(conn, nil) |
| defer client.Close() |
| |
| // Do POST attach request |
| resp, err := client.Do(req) |
| c.Assert(resp.StatusCode, check.Equals, http.StatusOK) |
| // If we check the err, we get a ErrPersistEOF = &http.ProtocolError{ErrorString: "persistent connection closed"} |
| // This means that the remote requested this be the last request serviced, is this okay? |
| |
| // Test read and write to the attached container |
| expected := []byte("hello") |
| actual := make([]byte, len(expected)) |
| |
| outChan := make(chan error) |
| go func() { |
| _, err := r.Read(actual) |
| outChan <- err |
| close(outChan) |
| }() |
| |
| inChan := make(chan error) |
| go func() { |
| _, err := w.Write(expected) |
| inChan <- err |
| close(inChan) |
| }() |
| |
| select { |
| case err := <-inChan: |
| c.Assert(err, check.IsNil) |
| case <-time.After(5 * time.Second): |
| c.Fatal("Timeout writing to stdout") |
| } |
| |
| select { |
| case err := <-outChan: |
| c.Assert(err, check.IsNil) |
| case <-time.After(5 * time.Second): |
| c.Fatal("Timeout reading from stdin") |
| } |
| |
| if !bytes.Equal(expected, actual) { |
| c.Fatal("Expected output to match input") |
| } |
| |
| resp.Body.Close() |
| } |
| |
| func (s *DockerSuite) TestPostContainersAttachStderr(c *check.C) { |
| testRequires(c, DaemonIsLinux) |
| out, _ := dockerCmd(c, "run", "-dit", "busybox", "/bin/sh", "-c", "cat >&2") |
| |
| r, w := io.Pipe() |
| defer r.Close() |
| defer w.Close() |
| |
| conn, err := sockConn(time.Duration(10 * time.Second)) |
| c.Assert(err, check.IsNil) |
| |
| containerID := strings.TrimSpace(out) |
| |
| req, err := http.NewRequest("POST", "/containers/"+containerID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{})) |
| c.Assert(err, check.IsNil) |
| |
| client := httputil.NewClientConn(conn, nil) |
| defer client.Close() |
| |
| // Do POST attach request |
| resp, err := client.Do(req) |
| c.Assert(resp.StatusCode, check.Equals, http.StatusOK) |
| // If we check the err, we get a ErrPersistEOF = &http.ProtocolError{ErrorString: "persistent connection closed"} |
| // This means that the remote requested this be the last request serviced, is this okay? |
| |
| // Test read and write to the attached container |
| expected := []byte("hello") |
| actual := make([]byte, len(expected)) |
| |
| outChan := make(chan error) |
| go func() { |
| _, err := r.Read(actual) |
| outChan <- err |
| close(outChan) |
| }() |
| |
| inChan := make(chan error) |
| go func() { |
| _, err := w.Write(expected) |
| inChan <- err |
| close(inChan) |
| }() |
| |
| select { |
| case err := <-inChan: |
| c.Assert(err, check.IsNil) |
| case <-time.After(5 * time.Second): |
| c.Fatal("Timeout writing to stdout") |
| } |
| |
| select { |
| case err := <-outChan: |
| c.Assert(err, check.IsNil) |
| case <-time.After(5 * time.Second): |
| c.Fatal("Timeout reading from stdin") |
| } |
| |
| if !bytes.Equal(expected, actual) { |
| c.Fatal("Expected output to match input") |
| } |
| |
| resp.Body.Close() |
| } |