SSE 开发实践 (Umi, Nginx, Flask, Express )

部门项目集成了文心一言和ChatGPT4.0等大模型,之前的数据返回方式采用了轮询的方法,现在要替换成 Server-Sent Events协议。

前提:项目前端采用了 Umijs 后端采用了 Flask

首先开始了对 SSE 的学习,分别搭建了简单的 express 服务 和 使用 vite 的前端项目

服务端代码:

js 复制代码
var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function (req, res, next) {
  res.render('index', { title: 'Express' });
});

router.post('/stream', function (req, res, next) {
  res.set({
    'Access-Control-Allow-Origin': '*',
    'Cache-Control': 'no-cache',
    Connection: 'keep-alive', // allowing TCP connection to remain open for multiple HTTP requests/responses
    'Content-Type': 'text/event-stream', // media type for Server Sent Events (SSE)
  });
  res.flushHeaders();

  const interval = setInterval(() => {
    const stock1Rate = Math.floor(Math.random() * 100000);
    const obj = {
      content: stock1Rate,
      contentType: 'text',
    };
    res.write("data: " + JSON.stringify(obj) + "\n\n");
  }, 500);

  setTimeout(() => {
    clearInterval(interval);
    res.end();
  }, 3000);
});

module.exports = router;

前端代码:

js 复制代码
const [data, setData] = useState('');
const str = useRef('');
  
useEffect(() => {
    async function fetchData() {
        const result = await fetch(`/stream`, { method: 'post' });
        const reader = result.body?.getReader();

        // Here we start prepping for the streaming response
        const decoder = new TextDecoder();
        const loopRunner = true;

        while (loopRunner) {
            // Here we start reading the stream, until its done.
            const { value, done } = await reader!.read();
            if (done) {
                break;
            }
            const decodedChunk = decoder.decode(value, { stream: true });
            console.log('decodedChunk', decodedChunk);

            str.current+=decodedChunk
            setData(str.current); // update state with new chunk
        }
    }

    fetchData();
}, []);

运行结果:

项目中实践一下

将上述的前端代码拷贝到公司项目中,配置了 Umi 的 proxy 代理请求后端,发现接口返回并不是流式的逐渐输出,而是请求全部完成后一并输出了。。。

在排查过程中更改了下代码

js 复制代码
const result = await fetch(`http://localhost:3000/stream`, { method: 'post' });

不使用代理直接去请求服务器,发现请求正常,变成了流式输出,把问题的范围缩小到了 proxy 这一层,既然项目采用了 Umi, 那接下来就去 Github 搜索 SSE 相关的 issue,得到如下结果:

进入第一个 issue,发现问题确实出在 Umi 上,主要原因如该 issue 中所说:

官方已经在 4.1.5 版本修复了该问题。 接下来把 Umi 进行了升级,并修改 package.json 中的 script 脚本,添加了 UMI_DEV_SERVER_COMPRESS=none 这个环境变量:

json 复制代码
"start": "cross-env UMI_ENV=dev UMI_DEV_SERVER_COMPRESS=none max dev",

重新启动项目后,请求已经变得正常,变为流式输出了。

与后端联调遇到的问题

在与后端联调中,发现业务请求又出现无法流式输出的情况了,后端中的测试请求接口却好使。经排查发现是因为返回的流式数据格式有问题,按规范修改后请求变得正常。数据格式参考如下:

部署到测试环境后遇到的问题

部署到测试环境后又不行了🤣。测试环境采用的 Nginx 和 uWIGS,经过一系列百度之后发现这次的问题跟 Nginx 的配置有关系,SSE 相关的 Nginx 配置如下:

nginx.conf 复制代码
proxy_pass http://your_backend;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
send_timeout 86400s;

引用工具

fetch-event-source

相关推荐
子兮曰5 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰5 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万5 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝5 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋5 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁5 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95275 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大5 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师5 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学5 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端