目录
1 效果演示
在和机器人对话时,经常都是流式方式,这里使用hertz的SSE来实现一个流式接口。最终效果如下

2 代码实现
1、依赖
1 2 |
go get github.com/cloudwego/hertz@latest go get github.com/hertz-contrib/sse@latest |
2、 后端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
package main import ( "context" "github.com/cloudwego/hertz/pkg/app" "github.com/cloudwego/hertz/pkg/app/server" "github.com/cloudwego/hertz/pkg/common/hlog" "github.com/hertz-contrib/sse" "time" ) func main() { h := server.Default(server.WithHostPorts(":8888")) h.GET("/stream-chat", func(c context.Context, ctx *app.RequestContext) { // 1. 创建 SSE 流 stream := sse.NewStream(ctx) // 2. 设置响应头(NewStream 会自动设置部分头,但建议显式声明) ctx.SetContentType("text/event-stream; charset=utf-8") ctx.Response.Header.Set("Cache-Control", "no-cache") ctx.Response.Header.Set("Connection", "keep-alive") ctx.Response.Header.Set("Access-Control-Allow-Origin", "*") // 3. 获取请求参数 question := ctx.Query("q") hlog.Infof("收到新请求 - 问题: %s", question) responses := []string{ "正在分析你的问题...", "处理中,请稍候...", "初步结论:需要进一步检查。", "总结:解决方案已找到!", "流式对话完成。", } for index, resp := range responses { // 构建SSE格式数据(注意末尾的\n\n) event := &sse.Event{ Event: "message", Data: []byte(resp), } hlog.Infof("message %v:%v", index, resp) err := stream.Publish(event) if err != nil { hlog.Infof("err : %s", err) } time.Sleep(1 * time.Second) // 模拟处理延迟 } }) h.Spin() } |
3、 前端 chat.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <!-- 新增字符集声明 --> <title>NewStream 流式对话演示</title> <style> /* 添加中文字体支持 */ body { font-family: "Microsoft YaHei", SimHei, sans-serif; } </style> </head> <body> <h1>实时对话流1 </h1> <input type="text" id="question" placeholder="输入问题"> <button onclick="startStream()">开始对话</button> <div id="output" style="white-space: pre-wrap; margin-top: 20px;"></div> <script> let eventSource = null; function startStream() { if (eventSource) { eventSource.close(); } const question = document.getElementById('question').value || "默认问题"; const outputDiv = document.getElementById('output'); outputDiv.innerHTML = ''; // 强制UTF-8编码 const url = `http://localhost:8888/stream-chat?q=${encodeURIComponent(question)}`; eventSource = new EventSource(url); // 处理消息事件 eventSource.addEventListener('message', (e) => { const data = e.data; // 直接使用 e.data outputDiv.innerHTML += `${data}\n`; outputDiv.scrollTop = outputDiv.scrollHeight; }); // 处理错误 eventSource.onerror = (e) => { console.error('SSE错误:', e); outputDiv.innerHTML += '--- 连接已关闭 ---\n'; eventSource.close(); }; } </script> </body> </html> |
3 http sse可以用再构建app场景吗,还是只有在浏览器使用
3.1 浏览器场景:原生支持,开箱即用
3.2 App 场景:需手动实现客户端解析(无原生 API,但可实现)
SSE 协议核心规范(客户端需适配):
主流 App 平台的实现方式:
4 SSE 和 http streamable 区别
http streamable 介绍
Streamable HTTP 是基于标准 HTTP 的通用流式传输模式,而 SSE(Server-Sent Events,服务器发送事件)是基于 HTTP 设计的、面向 “服务器到客户端单向实时消息” 的特定协议,二者是 “通用方案” 与 “专用方案” 的关系,核心差异体现在设计目标、数据格式和适用场景上。
对比维度 | Streamable HTTP | SSE (Server-Sent Events) |
---|---|---|
本质定位 | 通用流式传输模式(基于 HTTP 协议的 “能力扩展”) | 专用实时消息协议(基于 HTTP 的 “标准化方案”) |
设计目标 | 解决 “大文件 / 动态数据” 的分段传输(边传边用) | 解决 “服务器到客户端” 的单向实时消息推送(低延迟) |
数据格式 | 无固定格式(可传二进制 / 文本,如视频块、文件片段) | 有严格的文本格式(需遵循 event /data /id 字段) |
传输方向 | 双向 / 单向均可(如客户端传分片文件、服务器传流) | 仅单向(服务器 → 客户端,客户端无法通过同连接回传) |
响应头标识 | 核心头:Transfer-Encoding: chunked |
核心头:Content-Type: text/event-stream |
客户端处理 | 需自定义实现(如断点续传需处理 Range 头) |
浏览器原生支持(通过 EventSource API 自动解析) |
http streamable实现方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
func SendMesctx (requestContext *app.RequestContext, msg *dto.DevcopilotQaOpenKefuChatStreamChatResponse) { /<strong>/ 设置Content-Type为JSON c.Response.HijackWriter(resp.NewChunkedBodyWriter(&c.Response, c.GetWriter())) </strong> msg := &Mesage{ .... } jsonData,_ := json.Marshal(msg) // 写入当前项并刷新缓冲区 requestContext.Write(jsonData) requestContext.Write([]byte("\n\n")) requestContext.Flush() } type Mesage struct { .... } |