go中如何在浏览器上实时显示cmd控制台的输出(流式传输)

  1. Code snippet 1
  2. Code snippet 2

Code snippet 1

package main

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "net/http"
    "os/exec"

    "github.com/nbari/violetear"
)

func main() {
    // http.HandleFunc("/", handleRequest)
    // http.ListenAndServe(":8888", nil)
    router := violetear.New()
    router.HandleFunc("/cmd", handleRequest, "GET")
    router.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, World!")
    }, "GET")
    log.Fatal(http.ListenAndServe(":8888", router))
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Cache-Control", "no-cache")
    // 使浏览器页面的滚动条始终置于底部
    scroll := `<body style="word-wrap: break-word; white-space: pre-wrap; background-color: #444; color: #fff; font-family: mono"><script>
    const resizeObserver = new ResizeObserver(() =>
        window.scrollTo(0, document.body.scrollHeight)
    )
    // start observing a DOM node
    resizeObserver.observe(document.body)
</script></body>
`
    fmt.Fprint(w, scroll)
    // cmd := exec.Command("ping", "-4", "www.tumupan.com", "-c", "4") // 执行ping命令
    cmd := exec.Command("ifconfig")
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        log.Println("Error:", err)
        return
    }
    err = cmd.Start()
    if err != nil {
        log.Println("Error:", err)
        return
    }
    reader := bufio.NewReader(stdout)
    for {
        line, err := reader.ReadString('\n')
        if err != nil || io.EOF == err {
            log.Println("Command is finished.")
            break
        }
        fmt.Fprintf(w, line)     // 将输出写入http响应
        w.(http.Flusher).Flush() // 刷新http响应
    }
    err = cmd.Wait()
    if err != nil {
        log.Println("Error:", err)
        return
    }
}

Code snippet 2

package main

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
    "os/exec"

    "github.com/nbari/violetear"
)

func stream(w http.ResponseWriter, r *http.Request) {
    // 使浏览器页面的滚动条始终置于底部
    scroll := `
<body style="word-wrap: break-word; white-space: pre-wrap;"><script>
    const resizeObserver = new ResizeObserver(() =>
        window.scrollTo(0, document.body.scrollHeight)
    )
    // start observing a DOM node
    resizeObserver.observe(document.body)
</script></body>
`
    fmt.Fprint(w, scroll)

    ctx := r.Context()
    ch := make(chan struct{})

    // 每1秒输出一个数,逐渐递增
    cmd := exec.CommandContext(ctx, "ping", "www.baidu.com")
    rPipe, wPipe, err := os.Pipe()
    if err != nil {
        log.Fatal(err)
    }
    cmd.Stdout = wPipe
    cmd.Stderr = wPipe
    if err := cmd.Start(); err != nil {
        log.Fatal(err)
    }

    go writeOutput(w, rPipe)

    go func(ch chan struct{}) {
        cmd.Wait()
        wPipe.Close()
        ch <- struct{}{}
    }(ch)

    select {
    case <-ch:
    case <-ctx.Done():
        err := ctx.Err()
        log.Printf("Client disconnected: %s\n", err)
    }
}

func writeOutput(w http.ResponseWriter, input io.ReadCloser) {
    flusher, ok := w.(http.Flusher)
    if !ok {
        http.Error(w, "Streaming not supported", http.StatusInternalServerError)
        return
    }

    // Important to make it work in browsers
    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    w.Header().Set("Connection", "keep-alive")

    in := bufio.NewScanner(input)
    for in.Scan() {
        data := in.Text()
        log.Printf("data: %s\n", data)
        fmt.Fprintf(w, "data: %s\n", data)
        flusher.Flush()
    }
    input.Close()
}

func main() {
    router := violetear.New()
    router.HandleFunc("/", stream, "GET")
    log.Fatal(http.ListenAndServe(":8080", router))
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 admin@yantu.org