【问题标题】:unable to handle disconnected clients in SSE无法处理 SSE 中断开连接的客户端
【发布时间】:2017-03-29 15:30:28
【问题描述】:

我正在尝试使用 Redis PubSub 将 SSE 功能添加到我的服务器应用程序中,由许多文章指导,即: how-to-use-actioncontollerlive-along-with-resque-redis.

服务器托管在 Heroku 中,因此心跳也是必要的。

...
sse = SSE.new(response.stream)
begin
    redis = Redis.new(:url => ENV['REDISCLOUD_URL'])
    redis.subscribe(<UUID>, HEARTBEAT_CHANNEL) do |on|
        on.message do |channel, data|
            begin
                if channel == HEARTBEAT_CHANNEL
                    sse.write('', event: "hb")
                else
                  sse.write(data, event: "offer_update")
                end
            rescue StandardError => e #I'll call this section- "internal rescue"
                puts "Internal: #{$!}"
                redis.quit
                sse.close
                puts "SSE was closed
            end
        end
    end
rescue StandardError => e  #I'll call this section- "external rescue"
    puts "External: #{$!}"
ensure
    redis.quit
    sse.close
    puts "sse was closed"
end

问题:

  1. 我没有在网上看到任何地方谈论 SSE 的“内部救援”。但我不知道如果sse.write 引发异常,谁可以捕获异常?常见的情况是在客户端不再连接时发送 HB,这使得这部分非常关键(出现了"Internal: client disconnected")。我说的对吗?
  2. 在什么情况下会触发“外部救援”? 客户端断开连接是否会导致sse.write 在“内部块”(on.message 正文内)引发异常?因为当我尝试模拟它数十次时,它从未被外部救援抓住。
  3. 此代码也受到影响。内部救援部分中的redis.quit 引发了另一个由外部救援语句捕获的异常:External: undefined method 'disconnect' for #&lt;Redis::SubscribedClient:0x007fa8cd54b820&gt;。那么——应该怎么做呢?如何尽快识别客户端断开连接以释放内存和套接字?
  4. sse.write 引发的异常怎么可能没有被外部救援(应该从一开始)捕获,而另一个错误(在我的第三个问题中描述)被捕获?所有这些代码(外部+内部部分)都在同一个线程中运行,对吗?我很乐意为您提供深入的解释。

【问题讨论】:

    标签: ruby-on-rails-4 heroku redis publish-subscribe server-sent-events


    【解决方案1】:

    您在subscribe 中捕获了异常,因此redis 不知道它并且不会正确停止它的内部循环。 redis.quit 将导致它崩溃并停止,因为它无法继续等待消息。这显然不是一个好方法。

    如果您的代码在 subscribe 内抛出异常,它将导致 redis 正常取消订阅,并且您的异常可以在外部救援,就像在“外部救援”中一样。

    另外一点是你不应该在没有完全处理它们的情况下捕获异常,并且你不应该在没有重新引发它们的情况下捕获泛型异常。在这种情况下,您可以放心地将 ClientDisconnected 异常冒泡到 Rails 的代码中。

    你的控制器代码应该是这样的:

    def subscribe
      sse = SSE.new(response.stream)
      redis = Redis.new(:url => ENV['REDISCLOUD_URL'])
      redis.subscribe(<UUID>, HEARTBEAT_CHANNEL) do |on|
        on.message do |channel, data|
          if channel == HEARTBEAT_CHANNEL
            sse.write('', event: "hb")
          else
            sse.write(data, event: "offer_update")
          end
        end
      end
    ensure
      redis.quit if reddis # might not have had a chance to initialize yet
      sse.close if sse # might not have had a chance to initialize yet
      puts "sse was closed"
    end
    

    【讨论】:

      猜你喜欢
      • 2019-12-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-10
      • 1970-01-01
      • 2021-03-25
      • 2013-02-19
      • 1970-01-01
      相关资源
      最近更新 更多