无论用户在哪个页面上,服务器发送的事件都必须始终触发吗?

Must a server sent event always be firing regardless of what page a user is on?

我是 SSE 的新手,所以如果我误解了目的,请随时告诉我,还有更好的方法来实现我想要的!

我有一个正常工作的 SSE,它每分钟都会更新用户的仪表板。代码如下所示:

# SitesController
def dashboard
end

def regular_update
  response.headers['Content-Type'] = 'text/event-stream'
  sse = SSE.new(response.stream, event: 'notice')
  begin
    sse.write(NoticeTask.perform) # custom code returning the JSOn 
    sleep 60
  rescue ClientDisconnected
  ensure
    sse.close
  end
end

# routes
get "/dashboard(/:id)" => "sites#dashboard"
get "/site_update" => 'sites#regular_update'

# view - /dashboard
var source = new EventSource('/site_update');
source.addEventListener('notice', function(event) {
  var data = JSON.parse(event.data)
  appendNoticeAndAlert(data)
});

这很好用。当我为用户 /dashboard 时,SSE 会定期更新正确的信息,太棒了!

但是,我注意到如果我在任何随机页面上,比如主页,SSE 仍然在后台 运行。现在......显然这是有道理的,因为代码中没有任何其他限制......但不应该有吗???就像不应该有某种方式来确定 SSE 的范围吗?如果用户永远不在 /dashboard 上,让 SSE 不断在后台工作,更新 /dashboard 页面,这不是一种巨大的资源浪费吗?

再次,初学SSE,如有根本错误,还请多多指教。谢谢!

在您的控制器中处理 SSE 时,您应该循环更新, 然后 ActionController::Live::ClientDisconnected 在客户离开后由 response.stream.write 引发:

def regular_update
  response.headers['Content-Type'] = 'text/event-stream'
  sse = SSE.new(response.stream, event: 'notice')
  loop do
    sse.write(NoticeTask.perform) # custom code returning the JSOn 
    sleep 60
  end
rescue ClientDisconnected
  logger.info "Client is gone"
ensure
  sse.close
end

您的代码在第一次更新和延迟后断开客户端连接,但一切似乎都正常,因为 EventSource 自动重新连接(因此您获得了长轮询更新)。

在客户端 EventSource 上应该 close()d 一旦不需要。通常它会在导航离开包含它的页面时自动完成,所以:

  1. 确保事件源 javascript 仅在仪表板页面上,而不在 javascript 捆绑包中(或者在捆绑包中,但仅在特定页面上启用)
  2. 如果您使用的是 turbolinks - 作为快速解决方案,您必须手动 close() 连接 - 尝试将 <meta name="turbolinks-visit-control" content="reload"> 添加到页眉或暂时禁用 turbolinks。

还要再想想你是否真的需要 SSE 来完成这个特定的任务,因为对于普通的定期更新,你可以从客户端代码中轮询一个 json 动作,这将呈现相同的数据。这将使控制器更简单,不会为每个客户端保持连接繁忙,具有更广泛的服务器兼容性等。

要对 SSE 进行推理 - 至少检查是否确实发生了某些变化,如果没有,则跳过消息。更好的方法是使用某种 pub-sub(如 Redis 的 SUBSCRIBE/PUBLISH,或 Postgres 的 LISTEN/NOTIFY)——每次都向主题发送事件影响仪表板变化的东西,订阅 SSE connect 等等(也可能是限制更新,取决于你的应用程序)。类似的可以用 ActionCable 实现(有点矫枉过正,但可以很方便,因为已经集成了 pub-sub)