At present I want to connect container stdin and stdout with websocket. But I can not read stdout if there are no output. e.g. "cd /"
Here is my code:
package main
import (
dcl "github.com/docker/docker/client"
"context"
"html/template"
"github.com/docker/docker/api/types"
"log"
"net/http"
"flag"
"github.com/gorilla/websocket"
"fmt"
"io"
"bufio"
)
var inout chan []byte
var output chan []byte
var addr = flag.String("addr", "localhost:8080", "http service address")
var upgrader = websocket.Upgrader{}
func echo(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print(err)
return
}
defer conn.Close()
cli, err := dcl.NewEnvClient()
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
ctx := context.Background()
execConfig := types.ExecConfig{
AttachStderr: true,
AttachStdin: true,
AttachStdout: true,
Cmd: []string{"/bin/sh"},
Tty: false,
Detach: false,
}
//set target container
exec, err := cli.ContainerExecCreate(ctx, "ubuntu", execConfig)
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
execAttachConfig := types.ExecStartCheck{
Detach: false,
Tty: false,
}
containerConn, err := cli.ContainerExecAttach(ctx, exec.ID, execAttachConfig)
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
defer containerConn.Close()
bufin := bufio.NewReader(containerConn.Reader)
go func(w io.WriteCloser) {
for {
data, ok := <-inout
if !ok {
fmt.Println("!ok")
w.Close()
return
}
fmt.Println(string(data))
w.Write(append(data, '
'))
}
}(containerConn.Conn)
go func() {
for {
buffer := make([]byte, 4096, 4096)
c, err := bufin.Read(buffer)
if err != nil{
fmt.Println(err)
}
//c, err := containerConn.Reader.Read(buffer)
if c > 0 {
output <- buffer[:c]
}
if c == 0{
output <- []byte{' '}
}
if err != nil {
break
}
}
}()
for {
mt, message, err := conn.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
log.Printf("recv: %s", message)
inout <- message
data := <-output
err = conn.WriteMessage(mt, data)
if err != nil {
log.Println("write:", err)
break
}
}
}
func home(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}
func main() {
inout = make(chan []byte)
output = make(chan []byte)
http.HandleFunc("/echo", echo)
http.HandleFunc("/", home)
log.Fatal(http.ListenAndServe(*addr, nil))
}
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
window.addEventListener("load", function(evt) {
var output = document.getElementById("output");
var input = document.getElementById("input");
var ws;
var print = function(message) {
var d = document.createElement("div");
d.innerHTML = message;
output.appendChild(d);
};
document.getElementById("open").onclick = function(evt) {
if (ws) {
return false;
}
ws = new WebSocket("{{.}}");
ws.onopen = function(evt) {
print("OPEN");
}
ws.onclose = function(evt) {
print("CLOSE");
ws = null;
}
ws.onmessage = function(evt) {
print("RESPONSE: " + evt.data);
}
ws.onerror = function(evt) {
print("ERROR: " + evt.data);
}
return false;
};
document.getElementById("send").onclick = function(evt) {
if (!ws) {
return false;
}
print("SEND: " + input.value);
ws.send(input.value);
return false;
};
document.getElementById("close").onclick = function(evt) {
if (!ws) {
return false;
}
ws.close();
return false;
};
});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server,
"Send" to send a message to the server and "Close" to close the connection.
You can change the message and send multiple times.
<p>
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<p><input id="input" type="text" value="ls">
<button id="send">Send</button>
</form>
</td><td valign="top" width="50%">
<div id="output"></div>
</td></tr></table>
</body>
</html>
`))
I try many ways, but no way can work :(
You need to implement the timeout on the call because in this part:
log.Printf("recv: %s", message)
inout <- message
data := <-output
err = conn.WriteMessage(mt, data)
if err != nil {
log.Println("write:", err)
break
}
You are waiting always to get the response for the server.
Here are you code working properly with the timeout implemented and an issue on the socket because needs to send utf8 and needs to be parsed to utf8 before send to the client.
package main
import (
dcl "github.com/docker/docker/client"
"context"
"html/template"
"github.com/docker/docker/api/types"
"log"
"net/http"
"flag"
"github.com/gorilla/websocket"
"fmt"
"io"
"bufio"
"time"
"unicode/utf8"
)
var inout chan []byte
var output chan []byte
var addr = flag.String("addr", "localhost:8080", "http service address")
var upgrader = websocket.Upgrader{}
func echo(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print(err)
return
}
defer conn.Close()
cli, err := dcl.NewEnvClient()
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
ctx := context.Background()
execConfig := types.ExecConfig{
AttachStderr: true,
AttachStdin: true,
AttachStdout: true,
Cmd: []string{"/bin/sh"},
Tty: false,
Detach: false,
}
//set target container
exec, err := cli.ContainerExecCreate(ctx, "vigorous_mclean", execConfig)
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
execAttachConfig := types.ExecStartCheck{
Detach: false,
Tty: false,
}
containerConn, err := cli.ContainerExecAttach(ctx, exec.ID, execAttachConfig)
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
defer containerConn.Close()
bufin := bufio.NewReader(containerConn.Reader)
// Write to docker container
go func(w io.WriteCloser) {
for {
data, ok := <-inout
log.Println("Received to send to docker", data)
if !ok {
fmt.Println("!ok")
w.Close()
return
}
w.Write(append(data, '
'))
}
}(containerConn.Conn)
// Received of Container Docker
go func() {
for {
buffer := make([]byte, 4096, 4096)
c, err := bufin.Read(buffer)
if err != nil {
fmt.Println(err)
}
//c, err := containerConn.Reader.Read(buffer)
if c > 0 {
output <- buffer[:c]
}
if c == 0 {
output <- []byte{' '}
}
if err != nil {
break
}
}
}()
for {
conn.CloseHandler()
mt, message, err := conn.ReadMessage()
log.Println(mt)
if err != nil {
log.Println("read:", err)
break
} else {
log.Printf("recv: %s
", message)
inout <- message
select {
case data := <-output:
stringData := string(data[:])
if !utf8.ValidString(stringData) {
v := make([]rune, 0, len(stringData))
for i, r := range stringData {
if r == utf8.RuneError {
_, size := utf8.DecodeRuneInString(stringData[i:])
if size == 1 {
continue
}
}
v = append(v, r)
}
stringData = string(v)
}
err = conn.WriteMessage(mt, []byte(stringData))
if err != nil {
log.Println("write:", err)
}
case <-time.After(time.Second * 1):
log.Println("Timeout")
}
}
}
}
func home(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}
func main() {
inout = make(chan []byte)
output = make(chan []byte)
http.HandleFunc("/echo", echo)
http.HandleFunc("/", home)
log.Fatal(http.ListenAndServe(*addr, nil))
}
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
window.addEventListener("load", function(evt) {
var output = document.getElementById("output");
var input = document.getElementById("input");
var ws;
var print = function(message) {
var d = document.createElement("div");
d.innerHTML = message;
output.appendChild(d);
};
document.getElementById("open").onclick = function(evt) {
if (ws) {
return false;
}
ws = new WebSocket("{{.}}");
ws.onopen = function(evt) {
print("OPEN");
}
ws.onclose = function(evt) {
print("CLOSE");
ws = null;
}
ws.onmessage = function(evt) {
print("RESPONSE: " + evt.data);
}
ws.onerror = function(evt) {
print("ERROR: " + evt.data);
}
return false;
};
document.getElementById("send").onclick = function(evt) {
if (!ws) {
return false;
}
print("SEND: " + input.value);
ws.send(input.value);
return false;
};
document.getElementById("close").onclick = function(evt) {
if (!ws) {
return false;
}
ws.close();
return false;
};
});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server,
"Send" to send a message to the server and "Close" to close the connection.
You can change the message and send multiple times.
<p>
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<p><input id="input" type="text" value="ls">
<button id="send">Send</button>
</form>
</td><td valign="top" width="50%">
<div id="output"></div>
</td></tr></table>
</body>
</html>
`))
Another method to connect directly the stdout to websocket without any timeout is create a goroutine that when you receive something from the docker send to the client directly and the code can be this
package main
import (
dcl "github.com/docker/docker/client"
"context"
"html/template"
"github.com/docker/docker/api/types"
"log"
"net/http"
"flag"
"github.com/gorilla/websocket"
"fmt"
"io"
"bufio"
"unicode/utf8"
)
var inout chan []byte
var output chan []byte
var addr = flag.String("addr", "localhost:8080", "http service address")
var upgrader = websocket.Upgrader{}
func echo(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print(err)
return
}
defer conn.Close()
cli, err := dcl.NewEnvClient()
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
ctx := context.Background()
execConfig := types.ExecConfig{
AttachStderr: true,
AttachStdin: true,
AttachStdout: true,
Cmd: []string{"/bin/sh"},
Tty: false,
Detach: false,
}
//set target container
exec, err := cli.ContainerExecCreate(ctx, "sharp_goldwasser", execConfig)
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
execAttachConfig := types.ExecStartCheck{
Detach: false,
Tty: false,
}
containerConn, err := cli.ContainerExecAttach(ctx, exec.ID, execAttachConfig)
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
defer containerConn.Close()
bufin := bufio.NewReader(containerConn.Reader)
// Write to docker container
go func(w io.WriteCloser) {
for {
data, ok := <-inout
log.Println("Received to send to docker", data)
if !ok {
fmt.Println("!ok")
w.Close()
return
}
w.Write(append(data, '
'))
}
}(containerConn.Conn)
// Received of Container Docker
go func() {
for {
buffer := make([]byte, 4096, 4096)
c, err := bufin.Read(buffer)
if err != nil {
fmt.Println(err)
}
//c, err := containerConn.Reader.Read(buffer)
if c > 0 {
output <- buffer[:c]
}
if c == 0 {
output <- []byte{' '}
}
if err != nil {
break
}
}
}()
// Connect the STDOUT to the Socket
go func () {
data := <-output
stringData := string(data[:])
if !utf8.ValidString(stringData) {
v := make([]rune, 0, len(stringData))
for i, r := range stringData {
if r == utf8.RuneError {
_, size := utf8.DecodeRuneInString(stringData[i:])
if size == 1 {
continue
}
}
v = append(v, r)
}
stringData = string(v)
}
err = conn.WriteMessage(1, []byte(stringData))
if err != nil {
log.Println("write:", err)
}
}()
for {
conn.CloseHandler()
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("read:", err)
break
} else {
log.Printf("recv: %s
", message)
inout <- message
}
}
}
func home(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}
func main() {
inout = make(chan []byte)
output = make(chan []byte)
http.HandleFunc("/echo", echo)
http.HandleFunc("/", home)
log.Fatal(http.ListenAndServe(*addr, nil))
}
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
window.addEventListener("load", function(evt) {
var output = document.getElementById("output");
var input = document.getElementById("input");
var ws;
var print = function(message) {
var d = document.createElement("div");
d.innerHTML = message;
output.appendChild(d);
};
document.getElementById("open").onclick = function(evt) {
if (ws) {
return false;
}
ws = new WebSocket("{{.}}");
ws.onopen = function(evt) {
print("OPEN");
}
ws.onclose = function(evt) {
print("CLOSE");
ws = null;
}
ws.onmessage = function(evt) {
print("RESPONSE: " + evt.data);
}
ws.onerror = function(evt) {
print("ERROR: " + evt.data);
}
return false;
};
document.getElementById("send").onclick = function(evt) {
if (!ws) {
return false;
}
print("SEND: " + input.value);
ws.send(input.value);
return false;
};
document.getElementById("close").onclick = function(evt) {
if (!ws) {
return false;
}
ws.close();
return false;
};
});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server,
"Send" to send a message to the server and "Close" to close the connection.
You can change the message and send multiple times.
<p>
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<p><input id="input" type="text" value="ls">
<button id="send">Send</button>
</form>
</td><td valign="top" width="50%">
<div id="output"></div>
</td></tr></table>
</body>
</html>
`))