Skip to content

Commit 67eb9f2

Browse files
committed
feat: add sse example
1 parent cc003ec commit 67eb9f2

File tree

3 files changed

+459
-0
lines changed
  • docs/tutorials/http/server
  • i18n/en/docusaurus-plugin-content-docs/current/tutorials/http/server

3 files changed

+459
-0
lines changed

docs/tutorials/http/server/sse.md

+241
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
---
2+
title: Server-Sent Events (SSE)
3+
slug: /docs/tutorials/http/server/sse
4+
---
5+
6+
在现代 Web 开发中,实时数据推送是一个常见需求。比如,股票价格更新或聊天消息通知。Server-Sent Events (SSE) 是一种基于 HTTP 的轻量级技术,特别适合服务器主动向客户端推送更新的场景。今天,我们将结合 **go-zero**,带你一步步实现一个简单的 SSE 服务,并附上完整代码和运行步骤。
7+
8+
## 什么是 SSE?
9+
10+
SSE(Server-Sent Events)是 HTML5 提供的一种技术,允许服务器通过持久化的 HTTP 连接向客户端单向推送事件。相比 WebSocket,SSE 更轻量,支持简单的实时更新场景,且基于标准 HTTP 协议,开箱即用。
11+
12+
SSE 的核心特点:
13+
- **单向通信**:服务器主动推送,客户端被动接收。
14+
- **简单协议**:基于 `text/event-stream` 格式,易于实现。
15+
- **自动重连**:浏览器内置重连机制,断开后可自动尝试恢复。
16+
17+
接下来,我们用 go-zero 实现一个 SSE 服务,功能是每秒向客户端推送当前服务器时间。
18+
19+
## 实现步骤
20+
21+
### 1. 项目初始化
22+
23+
首先,确保你已安装 Go 并引入 go-zero 依赖:
24+
25+
```bash
26+
go get -u github.com/zeromicro/go-zero
27+
```
28+
29+
创建一个项目目录,结构如下:
30+
31+
```
32+
sse-demo/
33+
├── main.go # 主程序
34+
└── static/
35+
└── index.html # 前端页面
36+
```
37+
38+
### 2. 编写服务端代码
39+
40+
我们将使用 go-zero 的 REST 服务,同时集成 SSE 和静态文件服务。完整代码如下:
41+
42+
```go
43+
package main
44+
45+
import (
46+
"fmt"
47+
"net/http"
48+
"time"
49+
50+
"github.com/zeromicro/go-zero/core/logx"
51+
"github.com/zeromicro/go-zero/rest"
52+
)
53+
54+
type SseHandler struct {
55+
clients map[chan string]bool
56+
}
57+
58+
func NewSseHandler() *SseHandler {
59+
return &SseHandler{
60+
clients: make(map[chan string]bool),
61+
}
62+
}
63+
64+
// Serve 处理 SSE 连接
65+
func (h *SseHandler) Serve(w http.ResponseWriter, r *http.Request) {
66+
// 设置 SSE 必需的 HTTP 头
67+
w.Header().Add("Content-Type", "text/event-stream")
68+
w.Header().Add("Cache-Control", "no-cache")
69+
w.Header().Add("Connection", "keep-alive")
70+
71+
// 为每个客户端创建一个 channel
72+
clientChan := make(chan string)
73+
h.clients[clientChan] = true
74+
75+
// 客户端断开时清理
76+
defer func() {
77+
delete(h.clients, clientChan)
78+
close(clientChan)
79+
}()
80+
81+
// 持续监听并推送事件
82+
for {
83+
select {
84+
case msg := <-clientChan:
85+
// 发送事件数据
86+
fmt.Fprintf(w, "data: %s\n\n", msg)
87+
w.(http.Flusher).Flush()
88+
case <-r.Context().Done():
89+
// 客户端断开连接
90+
return
91+
}
92+
}
93+
}
94+
95+
// SimulateEvents 模拟周期性事件
96+
func (h *SseHandler) SimulateEvents() {
97+
ticker := time.NewTicker(time.Second)
98+
defer ticker.Stop()
99+
100+
for range ticker.C {
101+
message := fmt.Sprintf("Server time: %s", time.Now().Format(time.RFC3339))
102+
// 广播给所有客户端
103+
for clientChan := range h.clients {
104+
select {
105+
case clientChan <- message:
106+
default:
107+
// 跳过阻塞的 channel
108+
}
109+
}
110+
}
111+
}
112+
113+
func main() {
114+
// 创建 go-zero REST 服务,集成静态文件服务
115+
server := rest.MustNewServer(rest.RestConf{
116+
Host: "0.0.0.0",
117+
Port: 8080,
118+
}, rest.WithFileServer("/static", http.Dir("static")))
119+
defer server.Stop()
120+
121+
// 初始化 SSE 处理
122+
sseHandler := NewSseHandler()
123+
124+
// 注册 SSE 路由
125+
server.AddRoute(rest.Route{
126+
Method: http.MethodGet,
127+
Path: "/sse",
128+
Handler: sseHandler.Serve,
129+
}, rest.WithTimeout(0))
130+
131+
// 在单独的 goroutine 中模拟事件
132+
go sseHandler.SimulateEvents()
133+
134+
logx.Info("Server starting on :8080")
135+
server.Start()
136+
}
137+
```
138+
139+
#### 代码解析
140+
141+
- **SseHandler 结构**
142+
- 使用 `map[chan string]bool` 维护所有客户端的 channel,方便广播消息。
143+
- `NewSseHandler` 初始化这个 map。
144+
145+
- **Serve 方法**
146+
- 设置 SSE 必需的 HTTP 头:`Content-Type: text/event-stream``Cache-Control: no-cache``Connection: keep-alive`
147+
- 为每个连接创建一个 channel,存储到 `clients` 中。
148+
- 使用 `select` 监听 channel 消息或客户端断开信号(通过 `r.Context().Done()`)。
149+
- 收到消息时,格式化为 SSE 协议(`data: 消息\n\n`),并通过 `Flush()` 立即推送。
150+
151+
- **SimulateEvents 方法**
152+
- 使用 `time.Ticker` 每秒生成一个事件(当前时间)。
153+
- 遍历 `clients`,将消息广播给所有连接的客户端。
154+
- 使用非阻塞发送(`select` + `default`),避免某个客户端阻塞影响整体。
155+
156+
- **main 函数**
157+
- 使用 `rest.MustNewServer` 创建服务,监听 `8080` 端口。
158+
- 通过 `rest.WithFileServer` 配置静态文件服务,映射 `/static` 到本地 `static` 目录。
159+
- 注册 `/sse` 路由,绑定 `SseHandler.Serve`,并禁用超时,确保长连接不会被超时机制中断,如果是在 `api` 文件中定义 `SSE` 路由,需要加上 `timeout: 0s`
160+
- 在 goroutine 中启动事件模拟。
161+
162+
### 3. 编写前端代码
163+
164+
`static/index.html` 中编写简单的客户端代码:
165+
166+
```html
167+
<!DOCTYPE html>
168+
<html>
169+
<head>
170+
<title>SSE 示例</title>
171+
</head>
172+
<body>
173+
<h1>Server-Sent Events 演示</h1>
174+
<div id="events"></div>
175+
176+
<script>
177+
const eventList = document.getElementById('events');
178+
// 连接到同一服务器的 SSE 端点
179+
const source = new EventSource('/sse');
180+
181+
source.onmessage = function(event) {
182+
const newElement = document.createElement("p");
183+
newElement.textContent = event.data;
184+
eventList.appendChild(newElement);
185+
};
186+
187+
source.onerror = function() {
188+
console.log("发生错误");
189+
};
190+
</script>
191+
</body>
192+
</html>
193+
```
194+
195+
#### 前端解析
196+
197+
- 使用 `EventSource` 连接到 `/sse` 端点。
198+
- `onmessage` 回调接收服务器推送的数据,动态添加到页面。
199+
- `onerror` 处理连接错误(例如服务器关闭)。
200+
201+
### 4. 运行和测试
202+
203+
1. **保存文件**:确保 `main.go``static/index.html` 在正确的位置。
204+
2. **启动服务**
205+
```bash
206+
go run main.go
207+
```
208+
3. **访问页面**:打开浏览器,输入 `http://localhost:8080/static/index.html`
209+
4. **效果**:页面每秒显示一条新的服务器时间。
210+
211+
## 关键技术点
212+
213+
### SSE 协议
214+
SSE 使用简单的文本格式推送事件:
215+
```
216+
data: 消息内容\n\n
217+
```
218+
可以用 `event:` 指定事件类型,`id:` 设置事件 ID,`retry:` 配置重连时间。例如:
219+
```
220+
event: update\ndata: Hello\nid: 1\n\n
221+
```
222+
223+
### go-zero 的优势
224+
- **路由简洁**`AddRoute` 轻松绑定 handler。
225+
- **静态服务**`WithFileServer` 一行代码搞定静态文件托管。
226+
- **高性能**:go-zero 内置的并发优化,确保多客户端连接稳定。
227+
228+
### 注意事项
229+
- **CORS**:当前代码中,HTML 和 SSE 同源,无需 CORS。如果前端部署在其他域名,需添加 `w.Header().Add("Access-Control-Allow-Origin", "*")`
230+
- **客户端管理**:使用 `defer` 清理断开连接的客户端,避免内存泄漏。
231+
- **非阻塞广播**`select` + `default` 确保某个客户端阻塞不会影响其他客户端。
232+
233+
## 扩展思路
234+
235+
- **自定义事件**:在 `SimulateEvents` 中添加不同类型的事件,客户端用 `source.addEventListener` 监听。
236+
- **认证**:在 `Serve` 中检查请求头或参数,实现权限控制。
237+
- **更多数据**:推送 JSON 格式数据,客户端解析后渲染复杂 UI。
238+
239+
## 总结
240+
241+
通过 go-zero,我们轻松实现了一个 SSE 服务,展示了服务器如何实时推送数据给客户端。代码简洁、功能完整,非常适合学习和扩展。无论是实时监控、通知系统还是简单的数据流应用,SSE 配合 go-zero 都是一个优雅的选择。

0 commit comments

Comments
 (0)