dsgdsf12312 2018-01-17 13:32
浏览 161

如何将容器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 :(

  • 写回答

1条回答 默认 最新

  • dpsr1670 2018-01-17 16:30
    关注

    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>
    `))
    
    评论

报告相同问题?

悬赏问题

  • ¥15 优质github账号直接兑换rmb,感兴趣伙伴可以私信
  • ¥15 错误(10048): “调用exui内部功能”库命令的参数“参数4”不能接受空数据。怎么解决啊
  • ¥15 安装svn网络有问题怎么办
  • ¥15 Python爬取指定微博话题下的内容,保存为txt
  • ¥15 vue2登录调用后端接口如何实现
  • ¥65 永磁型步进电机PID算法
  • ¥15 sqlite 附加(attach database)加密数据库时,返回26是什么原因呢?
  • ¥88 找成都本地经验丰富懂小程序开发的技术大咖
  • ¥15 如何处理复杂数据表格的除法运算
  • ¥15 如何用stc8h1k08的片子做485数据透传的功能?(关键词-串口)