英文:
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论