如何将容器stdin和stdout与websocket连接?

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 :(

dpxf81245
dpxf81245 大猩猩命令示例可能对您有用。用docker客户端调用替换os/exec调用。
接近 3 年之前 回复
dpfz27768
dpfz27768 您基本上是在做ssh的websocket版本。也许您可以搜索。
接近 3 年之前 回复

1个回答

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>
`))
douzai8285
douzai8285 如果您在答案上复制第二个代码,将可以正常工作。 “ cd /”命令没有任何标准输出。 您需要创建一个gorutine来将标准输出连接到websocket。
接近 3 年之前 回复
dsfds2343
dsfds2343 O. 167行丢失了for循环。 我添加了此for循环,但仍然无法获得“ cd /”之类的标准输出。 如果我在网络上键入“ cd /”,则仍在“数据:= <-输出”中进行阻止。 :-(
接近 3 年之前 回复
dtwye28880
dtwye28880 有哪些错误?
接近 3 年之前 回复
doumao1047
doumao1047 仍然无法正常工作。 :-(我试图找到另一个库。我不知道docker如何解决它。如果有时间,我将研究docker代码以找到一种方法。TksAdriánPolo Alcaide
接近 3 年之前 回复
dongzhi4690
dongzhi4690 为此,我建议更改另一个库的websocket,因为您需要发送消息类型,并且不能预定义消息类型。 我修改了代码,将sdout直接连接到websocket,就像ssh一样。
接近 3 年之前 回复
dotws86260
dotws86260 感谢您的回答。 但这不是我的预期行为。 我想获取命令的标准输出,如果超时,则没有机会获得输出。 也许我需要研究ssh的websocket版本
接近 3 年之前 回复
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问
相关内容推荐