文件 v0.1.7

SSE 事件

本頁說明 go-pve-qemu 長時間操作所使用的 Server-Sent Events 格式,以及如何在 Go 中消費。

使用 SSE 的端點:install、start、reboot、set/node。

事件格式

data: {"step":"<階段>","status":"<狀態>","message":"<訊息>"}

event: close
data: {}
欄位 說明
step 步驟名稱;安裝流程為 <階段> > <步驟>,見 佈建流程
status 見下表
message 人類可讀訊息;成功步驟附耗時,例如 (3.21s)
status 意義
processing 步驟進行中,或 qm/初始化腳本的逐行輸出(行首兩個空白)
success 步驟完成
info 結束提示(start/reboot)
error 步驟失敗,流程中止
message 前綴 意義
[*] 資訊
[+] 成功
[-] 失敗

注意事項

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")
    }
}
EN