提问者:小点点

Heroku上的Sinatra/Thin未检测到HTTP流连接(SSE)客户端断开连接


我正在尝试在Cedar堆栈上部署Sinatra流式SSE响应应用程序。不幸的是,虽然它在开发中运行良好,但一旦部署到Heroku,callbackerrback在调用连接时永远不会被调用,导致连接池被陈旧的连接填满(永远不会超时,因为数据仍在服务器端发送给他们。)

Heroku留档的相关信息:

长轮询和流式响应

Cedar支持长轮询和流式响应等HTTP1.1功能。应用程序有一个初始的30秒窗口,可以用单个字节返回客户端。但是,此后传输的每个字节(从客户端接收或由您的应用程序发送)都会重置一个滚动的55秒窗口。如果在55秒窗口内没有发送数据,连接将被终止。

如果您正在发送流式响应,例如服务器发送的事件,您需要检测客户端何时挂断,并确保您的应用服务器及时关闭连接。如果服务器在不发送任何数据的情况下保持连接打开55秒,您将看到请求超时。

这正是我想做的——检测客户端何时挂断,并迅速关闭连接。然而,Heroku路由层的某些东西似乎阻止了Sinatra像往常一样检测流关闭事件。

一些可用于复制的示例代码:

require 'sinatra/base'

class MyApp < Sinatra::Base

  set :path, '/tmp'
  set :environment, 'production'

  def initialize
    @connections = []

    EM::next_tick do
      EM::add_periodic_timer(1) do
        @connections.each do |out|
          out << "connections: " << @connections.count << "\n"
        end
        puts "*** connections: #{@connections.count}"
      end
    end

  end

  get '/' do
    stream(:keep_open) do |out|
      @connections << out
      puts "Stream opened from #{request.ip} (now #{@connections.size} open)"

      out.callback do
        @connections.delete(out)
        puts "Stream closed from #{request.ip} (now #{@connections.size} open)"
      end
    end
  end

end

我用这段代码在http://obscure-depths-3413.herokuapp.com/上安装了一个示例应用程序,说明了这个问题。当你连接时,连接量会增加,但是当你断开连接时,连接量永远不会下降。(Gemfile等演示的完整源代码在https://gist.github.com/mroth/5853993)

我正在尝试调试这个。有人知道如何修复它吗?

附言:辛纳特拉似乎也有类似的bug,但一年前就被修复了。此外,这个问题只出现在Heroku的生产中,但在本地运行时运行良好。

P. S.2.迭代连接对象时也会发生这种情况,例如添加以下代码:

EM::add_periodic_timer(10) do
  num_conns = @connections.count
  @connections.reject!(&:closed?)
  new_conns = @connections.count
  diff = num_conns - new_conns
  puts "Purged #{diff} connections!" if diff > 0
end

在本地工作得很好,但连接在Heroku上从未显示为关闭。


共2个答案

匿名用户

更新:在直接与Heroku路由团队(他们是很棒的人!)合作后,这现在已在他们的新路由层中修复,并且应该在任何平台上正常工作。

匿名用户

我会通过手动发送,在一个周期性的时间,活的信号,如果收到消息,客户端应该响应来做这个检查。

请看这个简单的聊天实现https://gist.github.com/tlewin/5708745来说明这个概念。

应用程序使用简单的JSON协议与客户端通信。当客户端接收到live: true消息时,应用程序会返回一个响应,服务器会存储最后的通信时间。