1. 为什么需要流式API与SSE配置
在实际项目开发中,前端页面经常需要展示实时更新的数据。比如股票行情、聊天消息、进度状态等场景,传统的一次性返回所有数据的API设计会导致两个明显问题:一是用户需要长时间等待所有数据处理完成,二是服务器内存压力大。这时候流式API就派上用场了。
我最近就遇到一个典型场景:前端同事需要展示一个包含2000多条记录的表格,每条记录都需要经过复杂计算。如果等所有数据计算完再返回,用户点击刷新后要等待近30秒。这体验实在太糟糕了。我们尝试了以下几种方案:
- 进度条方案:需要前后端都做大量改造
- 分页加载:破坏了数据的连续性
- WebSocket:有点杀鸡用牛刀的感觉
- 流式API:最终选择的方案
流式API的核心思想是"边计算边返回",就像打开水龙头一样,数据源源不断地流向客户端。而SSE(Server-Sent Events)则是专门为这种单向实时通信设计的标准协议,相比WebSocket更轻量,实现起来也简单得多。
2. Flask流式API基础实现
2.1 最简单的流式响应
让我们从一个最简单的Flask流式API开始。这个例子会每秒返回一个数字:
python复制from flask import Flask, Response
import time
app = Flask(__name__)
@app.route('/stream')
def stream_numbers():
def generate():
for i in range(1, 6):
yield f"数据块 {i}\n"
time.sleep(1)
return Response(generate())
if __name__ == '__main__':
app.run(port=5000)
这段代码的关键点在于:
- 使用生成器函数(带yield的函数)逐步产生数据
- 用Response对象包装生成器
- 每个数据块以\n结尾(这是SSE协议的要求)
测试时可以直接用curl命令:
bash复制curl http://localhost:5000/stream
2.2 处理流式请求的客户端代码
前端使用EventSource API接收流式数据:
javascript复制const eventSource = new EventSource('http://localhost:5000/stream');
eventSource.onmessage = (event) => {
console.log('收到数据:', event.data);
};
eventSource.onerror = () => {
console.error('连接出错');
eventSource.close();
};
3. 解决跨域问题
3.1 CORS配置
现代前端框架(Vue/React)通常运行在独立端口,这就涉及跨域问题。Flask中解决跨域最简单的办法是使用flask-cors扩展:
python复制from flask_cors import CORS
# 允许所有来源访问所有路由
CORS(app)
# 更安全的做法是指定具体来源
CORS(app, resources={
r"/stream": {"origins": ["http://localhost:8080"]}
})
3.2 开发环境特殊处理
在开发环境中,我习惯这样配置:
python复制CORS(app, supports_credentials=True)
同时前端请求需要带上credentials:
javascript复制new EventSource(url, { withCredentials: true })
4. 完善SSE响应头配置
要让EventSource正常工作,必须设置正确的响应头。以下是生产环境推荐的配置:
python复制headers = {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no' # 禁用Nginx缓冲
}
return Response(generate(), headers=headers)
这些头部的含义:
Content-Type: text/event-stream:声明SSE协议Cache-Control: no-cache:禁用缓存Connection: keep-alive:保持长连接X-Accel-Buffering: no:防止代理服务器缓冲
5. 完整的SSE格式实现
5.1 SSE消息格式规范
标准的SSE消息格式要求每条消息包含:
- data字段:实际内容
- id字段:可选的消息ID
- event字段:可选的事件类型
- 空行:表示消息结束
示例格式:
code复制id: 123
event: update
data: {"value":42}
5.2 Flask中的实现
完整的生产级实现:
python复制import json
import time
@app.route('/stream')
def stream_data():
def generate():
message_id = 0
while True:
message_id += 1
data = {
"time": time.time(),
"value": random.random()
}
yield f"id: {message_id}\n"
yield "event: metrics\n"
yield f"data: {json.dumps(data)}\n\n"
time.sleep(1)
headers = {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache'
}
return Response(generate(), headers=headers)
前端可以这样处理不同类型的事件:
javascript复制eventSource.addEventListener('metrics', (event) => {
const data = JSON.parse(event.data);
console.log('收到指标数据:', data);
});
6. 生产环境注意事项
6.1 连接管
解锁全文
加入我们的会员,获取最新、最热、最精彩的开发者技术内容