Отключение клиента HTTP потокового соединения (SSE) не обнаружено с помощью Sinatra/Thin на Heroku

Я пытаюсь развернуть приложение ответа SSE с потоком Sinatra в стеке Cedar. К сожалению, пока он отлично работает в разработке, после развертывания в Heroku callback или errback никогда не вызывается при вызове соединения, что приводит к тому, что пул соединений заполняется устаревшими соединениями (которые никогда не выходят за пределы, поскольку данные все еще остаются отправленных им на стороне сервера.)

Информация о Relvant из документации Heroku:

Долгосрочные и потоковые ответы

Cedar поддерживает функции HTTP 1.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)

Я в конце концов пытаюсь отладить этот. Кто-нибудь знает, как это исправить?

P.S. Похоже, что была похожая ошибка в Sinatra но она была исправлена ​​год назад. Также эта проблема возникает только при производстве в 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.

Ответ 1

Обновление: после непосредственной работы с командой маршрутизации Heroku (которые являются прекрасными парнями!), теперь это исправлено в новом слое маршрутизации и должно нормально работать на любой платформе.

Ответ 2

Я бы сделал эту проверку вручную, периодически передавая живой сигнал, когда клиент должен был ответить, если сообщение было получено.

Пожалуйста, ознакомьтесь с этой простой реализацией чата https://gist.github.com/tlewin/5708745, которая иллюстрирует эту концепцию.

Приложение взаимодействует с клиентом с использованием простого протокола JSON. Когда клиент получает сообщение alive: true, приложение отправляет ответ и сервер сохраняет последнее время связи.