You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GRPC服务端流式搭配Express.js仅渲染数组首个元素问题排查

问题:GRPC流式传输仅显示第一个对象,无法流式输出到浏览器

我尝试使用GRPC和Express.js向浏览器实现基础流式传输,但页面仅显示数组的第一个对象,其余内容无法流式输出。作为技术新手,可能忽略了关键细节,希望得到方向指引。以下是相关代码:

Proto文件

service NewsAlerts {
    rpc getNewsAlerts(NewsRequest) returns (stream NewsResponse) {}
}

message NewsRequest{
}

message NewsResponse{
    string category = 1;
    string url = 2;
}

服务端app.js

var news = [{category: "Weather alert", url: "URL 1"},{category: "System update",url: "URL 2"},{category: "Current news",url: "URL 3"},{category: "Privacy & Legal",url: "URL 4"},{category: "Statistics",url: "URL 5"}]

function getNewsAlerts(call, callback) {
  for(var i = 0; i < news.length; i++){
    call.write({
      category: news[i].category,
      url: news[i].url,
    });
  }
  call.end()
}

客户端index.js

router.get('/news', function(req, res, next) {
  
  var call = client.getNewsAlerts({});

  call.on('data', function(response){
      res.render('news', {category:response.category, url:response.url});
  });

  call.on('end', function(){

  });

  call.on('error', function(e){
    console.log(e);
  })
});

客户端news.ejs

<div id="text-part">
     <%= category %>
     <%= url%>
</div>

排查思路与解决方案

核心问题

res.render只能调用一次,它会直接生成完整的HTTP响应并发送给浏览器,第一次调用后HTTP连接就会关闭。后续GRPC流触发的data事件再调用res.render时,响应已经结束,所以只有第一个对象能显示。

具体解决步骤

  1. 改用HTTP流式响应
    Express实现浏览器端的流式输出,必须用res.write()分步发送内容,最后用res.end()结束响应,不能依赖res.render的一次性渲染。

  2. 修改客户端index.js
    替换原路由代码,直接发送HTML流:

    router.get('/news', function(req, res, next) {
      // 设置响应类型为HTML,支持流式输出
      res.setHeader('Content-Type', 'text/html');
      // 发送页面初始结构
      res.write('<html><head><title>News Alerts</title></head><body><div id="text-part">');
    
      var call = client.getNewsAlerts({});
    
      call.on('data', function(response){
        // 每次收到GRPC流数据,发送对应的HTML片段
        res.write(`<p><strong>${response.category}</strong>: <a href="${response.url}" target="_blank">${response.url}</a></p>`);
      });
    
      call.on('end', function(){
        // GRPC流结束后,关闭页面标签并结束HTTP响应
        res.write('</div></body></html>');
        res.end();
      });
    
      call.on('error', function(e){
        console.error(e);
        // 出错时也要正常结束响应
        res.write(`<p style="color:red;">加载失败:${e.message}</p></div></body></html>`);
        res.end();
      })
    });
    
  3. 移除news.ejs模板
    现在直接通过res.write发送HTML内容,无需再使用EJS渲染完整页面。如果需要复用模板片段,可以用EJS的render函数单独渲染每个新闻项的HTML字符串,但简单场景下直接拼接更直观。

  4. 服务端优化(可选)
    当前服务端是一次性写完所有数据,浏览器可能会一次性接收全部内容,看不到流式加载的效果。可以添加延迟模拟真实场景:

    function getNewsAlerts(call, callback) {
      let index = 0;
      const sendNews = () => {
        if (index < news.length) {
          call.write(news[index]);
          index++;
          setTimeout(sendNews, 1000); // 每隔1秒发送一条
        } else {
          call.end();
        }
      };
      sendNews();
    }
    
  5. 关键误区总结

    • GRPC的流式是服务端到Node.js客户端的流式,但Node.js到浏览器的HTTP响应默认是一次性发送的,必须手动通过res.write实现HTTP流式传输。
    • res.render用于生成完整页面并一次性发送,完全不适合流式输出场景。

内容的提问来源于stack exchange,提问作者Claudia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 19:07:06