В своей программе я хотел бы иметь возможность следить за выполнением некоторых консольных команд в режиме реального времени.
Вот два примера кода, который отображает данные в реальном времени в консоли, но на веб-странице данные отображаются не в реальном времени, а только после выполнения команды.
Пожалуйста, сообщите, как я могу получить данные в реальном времени на веб-странице?
В обоих примерах: Консоль отображает данные в режиме реального времени. На веб-странице данные отображаются не в реальном времени, а только после выполнения команды. Мне нужно отображать данные в режиме реального времени на веб-странице.
Пример 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
}
}
}
Возможно, вы неправильно поняли мою проблему. Может я не ясно описал. Обе версии кода работают одинаково. Консоль отображает данные в режиме реального времени. На веб-странице данные отображаются не в реальном времени, а только после выполнения команды. Мне нужно отображать данные в режиме реального времени на веб-странице.
Да, я понимаю проблему, но они работают совершенно по-разному. Первый, который ведет себя так, как хотелось бы, прост: настройте команду, установите ее стандартный вывод, запустите ее. Второй, который работает не так, как хотелось бы, совершенно другой и использует каналы, и горутины, и группы ожидания, и асинхронное выполнение команд. Почему?
Потому что я открыт для любого решения моей проблемы. Потому что я показываю, что пробовал разные варианты, и не требую от меня написания всего кода за меня с нуля.

Ответы HTTP по умолчанию буферизуются из соображений производительности, поэтому вам нужно явно вызвать Flush, чтобы отправить данные немедленно.
Если вы хотите сделать это из модуля записи, назначенного для выходов exec.Cmd, вы можете обернуть http.ResponseWriter в тип, который будет вызывать Flush для вас на каждом 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}
}
Если вам нужна копия данных, вы можете использовать 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
Большое спасибо. Мне это кажется магией, но это работает.
Извините, можно еще вопрос? Возможно ли в Go подписаться на событие получения новых данных в cmd.Stdout? Я хотел бы проанализировать данные, полученные от cmd.Stdout, и выполнить различные действия на основе анализа.
@WayMax, «подписки» нет, команда вызывает Write все, что назначено Stdout. Если вы хотите скопировать данные, вы можете просто использовать io.MultiWriter, как вы это делали изначально.
Если ваша первая реализация работает, почему вторая совершенно другая? Почему бы не использовать тот же подход?