【问题标题】:How to connect container stdin & stdout with websocket?如何将容器标准输入和标准输出与 websocket 连接?
【发布时间】:2018-06-26 09:50:38
【问题描述】:

目前我想用 websocket 连接容器标准输入和标准输出。但是如果没有输出,我就无法读取标准输出。例如"cd /"

这是我的代码:

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, '\n'))
        }
    }(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>
`))

我尝试了很多方法,但没有办法奏效:(

【问题讨论】:

  • 你基本上是在做一个 websocket 版本的 ssh。也许你可以搜索一下。
  • gorilla command example 可能对您有用。用 docker 客户端调用替换 os/exec 调用。

标签: go containers stdout stdin


【解决方案1】:

您需要在调用中实现超时,因为在这部分:

log.Printf("recv: %s", message)
    inout <- message
    data := <-output
    err = conn.WriteMessage(mt, data)
    if err != nil {
        log.Println("write:", err)
        break
    }

您总是在等待服务器的响应。

这是您的代码在实现超时和套接字问题的情况下正常工作,因为需要发送 utf8 并且需要在发送到客户端之前将其解析为 utf8。

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, '\n'))
        }
    }(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\n", 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>
`))

另一种直接将stdout连接到websocket而没有任何超时的方法是创建一个goroutine,当你从docker接收到一些东西时直接发送到客户端,代码可以是这样的

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, '\n'))
        }
    }(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\n", 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>
`))

【讨论】:

  • 感谢您的回答。但这不是我期望的行为。我想得到命令的标准输出,如果有超时,我没有机会得到输出。也许我需要研究 websocket 版本的 ssh
  • 为此,我建议将 websocket 更改为另一个库,因为您需要发送消息类型并且您无法预定义消息类型。我修改了代码,将标准输出直接连接到 websocket,就像 ssh 一样。
  • 还是不行。 :-( 我尝试寻找另一个库。我不知道docker如何解决它。如果有时间,我会研究docker代码以找到方法。Tks Adrián Polo Alcaide
  • O.第 167 行丢失了一个 for 循环。我添加了这个 for 循环,但仍然无法获得像“cd /”这样的标准输出。如果我在网络上输入“cd /”,仍然会阻止“data :=
  • 如果你复制答案上的第二个代码就可以了。 “cd /”命令没有任何标准输出。您需要创建一个 gorutine 将标准输出连接到 websocket。
猜你喜欢
  • 2017-05-16
  • 1970-01-01
  • 2019-09-03
  • 1970-01-01
  • 2021-10-20
  • 2013-09-18
  • 1970-01-01
  • 2013-06-13
  • 2012-03-16
相关资源
最近更新 更多