#node.js #heroku #server-sent-events
#node.js #heroku #отправленные сервером события
Вопрос:
У меня есть node.js сервер, который использует SSE для отправки обновлений подключенным клиентам. Иногда я получаю сообщение об ошибке сервера H27. В то же время другие клиентские запросы теряются, вероятно, во время повторной регистрации клиента в службе событий SSE.
Время, прошедшее между запросом клиента на получение / событие и ошибкой сервера H27, составляет от 13 секунд до 19:35 минут (в 30 различных случаях). встречается). Но существует полная корреляция между временем запроса GET / event и соответствующей ошибкой H27. Я отправляю сообщение keep alive с сервера каждые 50 секунд, чтобы обойти ограничение времени ожидания Heroku в 55 секунд.
Вот пример полного предупреждения, которое я получаю в журналах Heroku: 2020-10-17T08:49:04.525770 00:00 heroku [маршрутизатор]: sock= client at= код предупреждения = H27 desc= «Запрос клиента прерван» метод = ПОЛУЧИТЬ путь =»/ event» host=appname.herokuapp.com request_id=c4c4e2fd-16ca-4292-a27b-2a70b12b16fa fwd = «77.138.167.76» dyno = web.1 подключение = 1 мс сервис = 9499 мс статус = 499 байт = протокол = https
это произошло в результате следующего запроса GET: 2020-10-17T08:48:55.027638 00:00 приложение [web.1]: зарегистрирован клиент 8
Есть идеи, как я могу это преодолеть? Моя проблема в том, что мое приложение в значительной степени зависит от SSE, и если я теперь должен переключиться на другой механизм (например, сокет), это потребует значительных усилий.
РЕДАКТИРОВАТЬ При дальнейшем расследовании это, по-видимому, происходит из-за того, что клиент не может оставаться подключенным к маршруту отправленных сервером событий на серверах Heroku. Хотя он может установить первое соединение, он не может оставаться на связи. Я подозреваю, что это как-то связано с тайм-аутами запросов Heroku и тем, как Heroku обрабатывает маршрутизацию в целом.
Я до сих пор не нашел решения этой проблемы, поэтому все, пожалуйста, не стесняйтесь комментировать.
Комментарии:
1. Вы смогли это решить? У меня точно такая же проблема. Как ни странно, это работает, когда я впервые запускаю свой сервер, но затем затем перезагружаю новую страницу (она будет работать, возможно, за 1 секунду до ошибки H27), а затем снова перестает работать. Я получаю
code=H27 desc="Client Request Interrupted"
. Я весь день ломал над этим голову, потому что думал, что это проблема в моем коде, но я так думаю. это специфическая проблема Heroku. Вы случайно не используете какие-либо дополнения Heroku?2. К сожалению, нет. Проблема все еще возникает. Хорошей новостью является то, что с прошлого раза H27, похоже, не приводит к какой-либо замеченной проблеме. Приложение, похоже, работает хорошо, и серверная часть, похоже, не пропускает сообщения. Сообщения keep alive настроены на 50 секунд, что соответствует требованию Heruko к 55 секундам, поэтому я до сих пор не понял, в чем причина…
3. Что вы имеете в виду, это не вызывает проблемы? Можете ли вы последовательно получать уведомления в реальном времени с вашего сервера в ваш клиентский браузер до того, как произойдет отключение? В моем случае, поскольку я зависю от обработчиков событий (которые добавляются и удаляются в зависимости от подключения клиента) для отправки сообщений, после прерывания запроса H27 мой сервер удаляет обработчики событий для отправки этих сообщений клиенту, в результате чего клиент больше не получает эти сообщения.
4. Можете ли вы рассказать мне, как вы настроили свой сервер так, чтобы он не мог обнаруживать отключения клиентов? Спасибо @Arik Mirman
Ответ №1:
Я нашел очень хитрое решение этой проблемы, которое работает для меня, поскольку я являюсь единственным пользователем приложения.
Во-первых, некоторый контекст. Проблема в том, что по какой-то причине приложения, развернутые на Heroku, хотя и позволяют клиентам успешно подключаться к маршруту SSE, не могут определить состояние клиентского соединения. После того, как клиент подключается, в течение короткого периода времени, когда сервер не может обнаружить клиента, code=H27 desc="Client Request Interrupted"
на сервере появляется сообщение об ошибке, которое он принимает за признак того, что клиентское соединение закрыто. Поскольку он считает, что клиентское соединение закрыто, он, естественно, запускает close
событие, приводящее к выполнению кода в req.close
блоке в маршруте SSE.
В моей среде разработки мой маршрут SSE настроен следующим образом:
router.get('/updates', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
})
res.write('n')
// eventEmitted listeners are added for each connected client.
const eventEmitted = (data) => {
res.write(`data: ${JSON.stringify(data)}nn`)
}
eventEmitter.addListener('event', eventEmitted)
// eventEmitted listeners are removed when each client disconnects.
req.on('close', () => {
eventEmitter.removeAllListeners()
})
})
Однако, как упоминалось ранее, Heroku запускает close
событие вскоре после подключения клиента из-за неспособности определить состояние клиентских подключений, что приводит к eventEmitter.removeAllListeners()
выполнению. Естественно, после этого мой клиент не получает никаких уведомлений. Я изменил свой маршрут следующим образом, чтобы он работал на Heroku:
// Server Sent Events route for receiving realtime notifications when events are emitted.
router.get('/updates', (req, res) => {
// eventEmitted listeners are added for the most recently connected client.
const eventEmitted = (data) => {
res.write(`data: ${JSON.stringify(data)}nn`)
}
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
})
res.write('n')
eventEmitter.removeAllListeners()
eventEmitter.addListener('event', eventEmitted)
})
Результатом является то, что, поскольку req.on('close')
он удален, H27
ошибка Heroku не возникает, поскольку он не пытается определить состояние клиента. Недостатком является то, что только самый последний подключенный клиент может получать обновления в реальном времени с сервера. У всех ранее подключенных клиентов не будет подключенного прослушивателя событий для доставки им обновлений. Это работает для меня, поскольку я являюсь единственным пользователем приложения, но не будет работать, если у вас запущено более 1 клиента или более 1 экземпляра приложения.
Другой вариант — req.on('close')
полностью удалить и никогда не удалять добавленных слушателей. Это позволяет всем вашим клиентам получать обновления за счет того, что на вашем сервере будет много прослушивателей событий для каждого отключенного клиента, что в конечном итоге приведет к утечке памяти.
Это не совсем приемлемое решение для большинства людей, но единственное, которое я мог придумать. Если у кого-нибудь есть лучшее решение, пожалуйста, не стесняйтесь размещать его здесь.
Комментарии:
1. Спасибо @philosopher за ваш ответ. К сожалению, в моем случае одновременно подключено несколько пользователей.