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 安卓adb backup备份应用数据失败
  • ¥15 eclipse运行项目时遇到的问题
  • ¥15 关于#c##的问题:最近需要用CAT工具Trados进行一些开发
  • ¥15 南大pa1 小游戏没有界面,并且报了如下错误,尝试过换显卡驱动,但是好像不行
  • ¥15 没有证书,nginx怎么反向代理到只能接受https的公网网站
  • ¥50 成都蓉城足球俱乐部小程序抢票
  • ¥15 yolov7训练自己的数据集
  • ¥15 esp8266与51单片机连接问题(标签-单片机|关键词-串口)(相关搜索:51单片机|单片机|测试代码)
  • ¥15 电力市场出清matlab yalmip kkt 双层优化问题
  • ¥30 ros小车路径规划实现不了,如何解决?(操作系统-ubuntu)