SSE 事件
本頁說明 go-pve-qemu 長時間操作所使用的 Server-Sent Events 格式,以及如何在 Go 中消費。
使用 SSE 的端點:install、start、reboot、set/node。
事件格式
data: {"step":"<階段>","status":"<狀態>","message":"<訊息>"}
event: close
data: {}
注意事項
- 回應一律為 HTTP 200,成功與否需讀取事件判斷
message 以字串拼接輸出、未做 JSON 跳脫;指令輸出含 " 或 \ 時該行可能無法以 JSON 解析,用戶端應略過解析失敗的行
- 前置檢查失敗(
ALLOW_IPS、VM 狀態)時只會收到一行 data: {"message": "..."},沒有 step/status
- 用戶端中斷連線後,伺服器端流程繼續執行,只是不再推送事件
Go 用戶端範例
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
)
type event struct {
Step string `json:"step"`
Status string `json:"status"`
Message string `json:"message"`
}
func main() {
body, err := json.Marshal(map[string]any{
"os": "ubuntu",
"version": "24.04",
"cpu": 2,
"ram": 4096,
"disk": "32G",
})
if err != nil {
log.Fatal(err)
}
resp, err := http.Post("http://192.168.0.11:8080/api/vm/install", "application/json", bytes.NewReader(body))
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Fatalf("install rejected: %s", resp.Status)
}
failed := false
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
if line == "event: close" {
// 伺服器端流程結束
break
}
payload, ok := strings.CutPrefix(line, "data: ")
if !ok {
continue
}
var e event
if err := json.Unmarshal([]byte(payload), &e); err != nil {
// 未跳脫的指令輸出,略過
continue
}
fmt.Printf("[%s] %s %s\n", e.Status, e.Step, e.Message)
if e.Status == "error" {
failed = true
}
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
if failed {
log.Fatal("install failed")
}
}