别着急,坐和放宽
使用社交账号登录
Accept: text/event-stream;服务器以 Content-Type: text/event-stream 响应并保持连接打开,通过持续的 HTTP 响应流推送数据。event: 事件类型,默认是 messagedata: 消息主体数据(可多行,每行一个 data:)id: 事件唯一标识(用于断线续传)retry: 重连时间间隔(毫秒)
示例:
data: This is a message
或带事件类型与 JSON 数据:
event: userupdate
data: {"username":"john_doe","status":"online"}
id:,浏览器在重连时携带 Last-Event-ID,服务器可从该 ID 之后继续发送。: keepalive)作为保活。建议设置 Cache-Control: no-cache、Connection: keep-alive,必要时对部分代理设置 X-Accel-Buffering: no。<script>
// 订阅统一流 events,根据不同 event 类型分别处理
const es = new EventSource("http://localhost:8080/events?stream=events");
// 默认事件类型 message:文本通知
es.onmessage = (e) => {
console.log("message", e.data);
};
// tick:时间戳字符串
es.addEventListener("tick", (e) => {
console.log("tick", e.data);
});
// stock:行情 JSON
es.addEventListener("stock", (e) => {
try { console.log("stock", JSON.parse(e.data)); } catch { console.log(e.data); }
});
// score:比分 JSON
es.addEventListener("score", (e) => {
try { console.log("score", JSON.parse(e.data)); } catch { console.log(e.data); }
});
// location:位置 JSON
es.addEventListener("location", (e) => {
try { console.log("location", JSON.parse(e.data)); } catch { console.log(e.data); }
});
// progress:进度 JSON/数字
es.addEventListener("progress", (e) => {
try { console.log("progress", JSON.parse(e.data)); } catch { console.log(e.data); }
});
// system:系统事件文本
es.addEventListener("system", (e) => {
console.log("system", e.data);
});
</script>
// 组件中使用:将不同事件类型渲染到页面
const es = new EventSource("http://localhost:8080/events?stream=events");
es.onmessage = (e) => {
const el = document.getElementById("out");
if (el) el.textContent = e.data;
};
es.addEventListener("tick", (e) => {
const el = document.getElementById("tick");
if (el) el.textContent = e.data;
});
es.addEventListener("progress", (e) => {
const el = document.getElementById("progress");
try { const p = JSON.parse(e.data); if (el) el.textContent = p.percent + "%"; }
catch { if (el) el.textContent = e.data; }
});
go run main.go,监听 8080 端口发布消息:POST 文本到 http://localhost:8080/publish?type=message 或 GET http://localhost:8080/publish?type=message&msg=hello,浏览器会收到 events 流的推送(根据 type 作为事件类型)
X-Accel-Buffering: nodata: 前缀;使用 id 支持断线续传(配合 Last-Event-ID)package main
import (
"encoding/json"
"io"
"log"
"net/http"
"strconv"
"strings"
"time"
"github.com/r3labs/sse/v2"
)
func main() {
// 创建 SSE 服务器与单一流(建议统一使用一个流,通过 event 类型区分)
server := sse.New()
server.CreateStream("events")
mux := http.NewServeMux()
mux.HandleFunc("/events", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
server.ServeHTTP(w, r)
})
mux.HandleFunc("/publish", func(w http.ResponseWriter, r *http.Request) {
// 支持通过 type 指定事件类型(默认 message),便于后端统一发布
// 事件类型建议:
// - message:文本通知,适合短消息或提示
// - tick:时间戳字符串,适合心跳与时钟
// - userupdate:JSON 用户状态,如 {"username":"john","status":"online"}
// - stock:JSON 行情,如 {"symbol":"ACME","price":123.45}
// - score:JSON 比分,如 {"home":1,"away":2}
// - location:JSON 位置,如 {"lat":31.23,"lng":121.47}
// - progress:JSON/数字进度,如 {"percent":42}
// - system:文本系统事件,如 "ok" 或日志片段
body, _ := io.ReadAll(r.Body)
msg := strings.TrimSpace(string(body))
if msg == "" {
msg = r.URL.Query().Get("msg")
}
if msg == "" {
http.Error(w, "msg required", http.StatusBadRequest)
return
}
eventType := r.URL.Query().Get("type")
if eventType == "" {
eventType = "message"
}
server.Publish("events", &sse.Event{
ID: []byte(strconv.FormatInt(time.Now().UnixNano(), 10)),
Event: []byte(eventType),
Data: []byte(msg),
})
w.WriteHeader(http.StatusNoContent)
})
go func() {
// tick:时间戳字符串,适合心跳与时钟
ticker := time.NewTicker(5 * time.Second)
for t := range ticker.C {
server.Publish("events", &sse.Event{
ID: []byte(strconv.FormatInt(time.Now().UnixNano(), 10)),
Event: []byte("tick"),
Data: []byte(t.Format(time.RFC3339)),
})
}
}()
go func() {
// stock:行情 JSON,适合实时价格或指标
type Stock struct {
Symbol string `json:"symbol"`
Price float64 `json:"price"`
}
t := time.NewTicker(7 * time.Second)
for tt := range t.C {
_ = tt
payload, _ := json.Marshal(Stock{Symbol: "ACME", Price: 123.45})
server.Publish("events", &sse.Event{
ID: []byte(strconv.FormatInt(time.Now().UnixNano(), 10)),
Event: []byte("stock"),
Data: payload,
})
}
}()
go func() {
// score:比分 JSON,适合体育赛事
type Score struct {
Home int `json:"home"`
Away int `json:"away"`
}
t := time.NewTicker(9 * time.Second)
h, a := 0, 0
for range t.C {
h++
payload, _ := json.Marshal(Score{Home: h, Away: a})
server.Publish("events", &sse.Event{
ID: []byte(strconv.FormatInt(time.Now().UnixNano(), 10)),
Event: []byte("score"),
Data: payload,
})
}
}()
go func() {
// location:位置 JSON,适合地图轨迹
type Location struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
}
t := time.NewTicker(11 * time.Second)
lat, lng := 31.2304, 121.4737
for range t.C {
lat += 0.0003
lng += 0.0002
payload, _ := json.Marshal(Location{Lat: lat, Lng: lng})
server.Publish("events", &sse.Event{
ID: []byte(strconv.FormatInt(time.Now().UnixNano(), 10)),
Event: []byte("location"),
Data: payload,
})
}
}()
go func() {
// progress:进度(数字或 JSON),适合长任务状态
type Progress struct {
Percent int `json:"percent"`
}
for {
for p := 0; p <= 100; p += 20 {
payload, _ := json.Marshal(Progress{Percent: p})
server.Publish("events", &sse.Event{
ID: []byte(strconv.FormatInt(time.Now().UnixNano(), 10)),
Event: []byte("progress"),
Data: payload,
})
time.Sleep(3 * time.Second)
}
}
}()
go func() {
// system:系统事件文本,适合日志片段或健康检查
t := time.NewTicker(30 * time.Second)
for range t.C {
server.Publish("events", &sse.Event{
ID: []byte(strconv.FormatInt(time.Now().UnixNano(), 10)),
Event: []byte("system"),
Data: []byte("ok"),
})
}
}()
log.Fatal(http.ListenAndServe(":8080", mux))
}