umi
umi copied to clipboard
[Feature Request] 代理配置里,能否提供一个是否进行压缩的配置项
Background
Hello,首先感谢umijs团队对开源界的付出和努力。 我最近在做一个类似chatgpt的网页聊天应用,需要使用HTTP的SSE特性实现文本流式输出的功能。但是在使用umijs提供的代理功能对服务端的sse接口进行代理之后,接口会被阻塞,直到所有的event的都处理结束,才一次性返回到页面端。
即,正常的情况下,sse请求应该是如下的情况:
事件 | 时间 |
---|---|
发起请求 | 00:00:00 |
事件1 | 00:00:01 |
事件2 | 00:00:03 |
事件3 | 00:00:05 |
结束 | 00:00:05 |
实际在使用了代理后,sse变成了如下情况:
事件 | 时间 |
---|---|
发起请求 | 00:00:00 |
事件1 | 00:00:05 |
事件2 | 00:00:05 |
事件3 | 00:00:05 |
结束 | 00:00:05 |
这样用户实际上还是要等到所有事件处理完后才能得到响应,失去了sse的意义。
我搜索了其他issue,也有其他人遇到了同样的问题: https://github.com/umijs/umi/issues/11958 https://github.com/umijs/umi/issues/11453 但是好像没有人给出直接问题原因所在。
Proposal
根据我的研究,问题出在umi代码中,bundler-webpack/src/server/server.ts的48行:
server.ts
即:
app.use(require('@umijs/bundler-webpack/compiled/compression')());
这里为代理服务器的express实例引入了压缩中间件,并且没有提供配置来避免引入这个中间件。而强制压缩,会导致sse的事件不会如预期的流式输出,而是等到所有所有事件都到达,并且完成压缩后才传递到页面。
目前我有两个解决方案,
一个是自己自定义一个插件来修改express的app实例,如下
api.onBeforeMiddleware(({app}) => {
console.log(app._router.stack);
const compressionMiddleware = app._router.stack.findIndex((layer: any) => {
return layer.name === 'compression';
});
app._router.stack.splice(compressionMiddleware, 1);
});
另一个方案是,手动删除这一行来保证我的sse请求正常执行。 但我觉得这个两个方法的侵入性都太强了,尤其第二个。所以希望umijs官方能否提供一个类似webpack里devServer的compress配置项,来把这个压缩的逻辑交给用户来决定。
表示有同样的需求
是的,欢迎 PR 提供一个 devServer.compress
的选项用来关闭 compress 插件,或者做一个环境变量判断 if (process.env.UMI_DEV_SERVER_COMPRESS !== 'none')
来关闭 compress 插件。
综合 issue 来看,有这个需求的场景仅限于开发时的 sse 传输不达预期,而生产构建后和 dev server 无关,所以只是很少数人的需求。
是的,欢迎 PR 提供一个
devServer.compress
的选项用来关闭 compress 插件,或者做一个环境变量判断if (process.env.UMI_DEV_SERVER_COMPRESS !== 'none')
来关闭 compress 插件。综合 issue 来看,有这个需求的场景仅限于开发时的 sse 传输不达预期,而生产构建后和 dev server 无关,所以只是很少数人的需求。
感谢回复。 已提交pr,因为看到你们官网的QA说umi4不再提供devServer的配置,所以我这里采用了环境变量的方式。 同时在examples/dev-server-no-compress里创建了一个验证子项目
看起来和这个issue 有关: https://github.com/chimurai/http-proxy-middleware/issues/371
也可以在 proxy 中添加 类似这样的配置,对单独的接口进行处理:
onProxyRes: (proxyRes: any, req: any, res: any) => {
if (req.headers.accept === 'text/event-stream') {
res.writeHead(res.statusCode, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-transform',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no',
'Access-Control-Allow-Origin': '*'
});
proxyRes.pipe(res);
}
}
生产环境会有问题吗
上面的对话中,有两种解决方案,一个是环境变量( UMI_DEV_SERVER_COMPRESS=none umi dev
) ,一个是配置 onProxyRes
,都可以解决开发时 SSE 的问题。
这个问题只出现在开发时,本地服务,如果要发到生产,需要配置好你的 nginx 或者网关等,有专门 SSE 的配置,详情搜索下吧。
看起来和这个issue 有关: chimurai/http-proxy-middleware#371
也可以在 proxy 中添加 类似这样的配置,对单独的接口进行处理:
onProxyRes: (proxyRes: any, req: any, res: any) => { if (req.headers.accept === 'text/event-stream') { res.writeHead(res.statusCode, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-transform', 'Connection': 'keep-alive', 'X-Accel-Buffering': 'no', 'Access-Control-Allow-Origin': '*' }); proxyRes.pipe(res); } }
proxyRes.pipe(res)会导致流式消息嵌套,例如
event: message [{"field":value}](event: message {"field":value})
导致数据解析异常