在Golang中实时从Stdout获取数据

huangapple go评论83阅读模式
英文:

Getting data from Stdout in real time in Golang

问题

在你的程序中,你想要实时监控一些控制台命令的执行情况。

以下是两个示例代码,可以在控制台实时显示数据,但在网页上,数据只会在命令完成后显示,而不是实时显示。

请问如何在网页上实时获取数据?

在这两个示例中:控制台实时显示数据。在网页上,数据只会在命令完成后显示。我需要在网页上实时显示数据。

示例1:

package main

import (
    "bytes"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/exec"
    "io"
)

func main() {
    http.HandleFunc("/", mainLogic)
    http.ListenAndServe(":8080", nil)
}

func mainLogic(w http.ResponseWriter, r *http.Request) {
    cmd := exec.Command("ping", "8.8.8.8", "-c5")
    var stdBuffer bytes.Buffer
    mw := io.MultiWriter(os.Stdout, &stdBuffer)
    cmd.Stdout = mw
    cmd.Stderr = mw
    if err := cmd.Run(); err != nil {
        log.Panic(err)
    }
    fmt.Fprintf(w, stdBuffer.String())
}

示例2:

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
    "os/exec"
    "io"
    "sync"
)

func main() {
    http.HandleFunc("/", mainLogic)
    http.ListenAndServe(":8080", nil)
}

func mainLogic(w http.ResponseWriter, r *http.Request) {
    cmd := exec.Command("ping", "8.8.8.8", "-c5")
    var stdout []byte
    var errStdout, errStderr error
    stdoutIn, _ := cmd.StdoutPipe()
    stderrIn, _ := cmd.StderrPipe()
    err := cmd.Start()
    if err != nil {
        log.Fatalf("cmd.Start() failed with '%s'\n", err)
    }
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        stdout, errStdout = copyAndCapture(os.Stdout, stdoutIn)
        wg.Done()
    }()
    _, errStderr = copyAndCapture(os.Stderr, stderrIn)
    wg.Wait()
    err = cmd.Wait()
    if err != nil {
        log.Fatalf("cmd.Run() failed with %s\n", err)
    }
    if errStdout != nil || errStderr != nil {
        log.Fatal("failed to capture stdout or stderr\n")
    }
    fmt.Fprintf(w, string(stdout))
}

func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) {
    var out []byte
    buf := make([]byte, 1024, 1024)
    for {
        n, err := r.Read(buf[:])
        if n > 0 {
            d := buf[:n]
            out = append(out, d...)
            _, err := w.Write(d)
            if err != nil {
                return out, err
            }
        }
        if err != nil {
            if err == io.EOF {
                err = nil
            }
            return out, err
        }
    }
}
英文:

In my program, I would like to be able to monitor the execution of some console commands in real time.

Here are two examples of code that displays data in real time in the console, but on the web page the data is not displayed in real time, but only after the completion of the command.

Please advise how can I get real time data in a web page?

In both examples: The console displays data in real time. On the web page, the data is not displayed in real time, but only after the command is completed. I need to display data in real time on a web page.

Example 1

package main
import (
"bytes"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"io"
)
func main() {
http.HandleFunc("/", mainLogic)
http.ListenAndServe(":8080", nil)
}
func mainLogic(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("ping", "8.8.8.8", "-c5")
var stdBuffer bytes.Buffer
mw := io.MultiWriter(os.Stdout, &stdBuffer)
cmd.Stdout = mw
cmd.Stderr = mw
if err := cmd.Run(); err != nil {
log.Panic(err)
}
fmt.Fprintf(w, stdBuffer.String())
}

Example 2

package main
import (
"fmt"
"log"
"net/http"
"os"
"os/exec"
"io"
"sync"
)
func main() {
http.HandleFunc("/", mainLogic)
http.ListenAndServe(":8080", nil)
}
func mainLogic(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("ping", "8.8.8.8", "-c5")
var stdout []byte
var errStdout, errStderr error
stdoutIn, _ := cmd.StdoutPipe()
stderrIn, _ := cmd.StderrPipe()
err := cmd.Start()
if err != nil {
log.Fatalf("cmd.Start() failed with '%s'\n", err)
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
stdout, errStdout = copyAndCapture(os.Stdout, stdoutIn)
wg.Done()
}()
_, errStderr = copyAndCapture(os.Stderr, stderrIn)
wg.Wait()
err = cmd.Wait()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)
}
if errStdout != nil || errStderr != nil {
log.Fatal("failed to capture stdout or stderr\n")
}
fmt.Fprintf(w, string(stdout))
}
func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) {
var out []byte
buf := make([]byte, 1024, 1024)
for {
n, err := r.Read(buf[:])
if n > 0 {
d := buf[:n]
out = append(out, d...)
_, err := w.Write(d)
if err != nil {
return out, err
}
}
if err != nil {
if err == io.EOF {
err = nil
}
return out, err
}
}
}

答案1

得分: 1

HTTP响应默认情况下会进行缓冲以提高性能,因此您需要显式调用Flush以立即发送数据。

如果您想从分配给exec.Cmd输出的写入器中执行此操作,您可以将http.ResponseWriter包装在一个类型中,在每次Write时自动调用Flush

type flushWriter interface {
	io.Writer
	http.Flusher
}

type flusher struct {
	w flushWriter
}

func (f *flusher) Write(d []byte) (int, error) {
	n, err := f.w.Write(d)
	if err != nil {
		return n, err
	}
	f.w.Flush()
	return n, nil
}

...

if w, ok := w.(flushWriter); ok {
	cmd.Stdout = &flusher{w}
	cmd.Stderr = &flusher{w}
}

如果您想要数据的副本,您可以使用io.MultiWriter在数据被写入时捕获输出。这里我们还涵盖了http.ResponseWriter不是http.Flusher的情况,但这只有在您已经通过其他中间件进行了包装的情况下才可能发生。

var mw io.Writer
var buff bytes.Buffer
if w, ok := w.(flushWriter); ok {
	mw = io.MultiWriter(&buff, &flusher{w})
} else {
	mw = io.MultiWriter(&buff, w)
}
cmd.Stdout = mw
cmd.Stderr = mw
英文:

HTTP responses are buffered by default for performance reasons, so you need to explicitly call Flush in order to send data immediately.

If you want to do this from a writer assigned to the outputs of exec.Cmd, you can wrap the http.ResponseWriter in a type which will call Flush for you on every Write.

type flushWriter interface {
io.Writer
http.Flusher
}
type flusher struct {
w flushWriter
}
func (f *flusher) Write(d []byte) (int, error) {
n, err := f.w.Write(d)
if err != nil {
return n, err
}
f.w.Flush()
return n, nil
}
...
if w, ok := w.(flushWriter); ok {
cmd.Stdout = &flusher{w}
cmd.Stderr = &flusher{w}
}

If you want a copy of the data, you could the use the io.MultiWriter to capture the output while it's being written. Here we also cover the case where the http.ResponseWriter is not an http.Flusher, but that is only likely to happen if you already wrapped in via some other middleware.

var mw io.Writer
var buff bytes.Buffer
if w, ok := w.(flushWriter); ok {
mw = io.MultiWriter(&buff, &flusher{w})
} else {
mw = io.MultiWriter(&buff, w)
}
cmd.Stdout = mw
cmd.Stderr = mw

huangapple
  • 本文由 发表于 2023年3月30日 21:30:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/75888917.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定