Для событий, отправляемых сервером (SSE), какая конфигурация прокси Nginx подходит?

20

Я прочитал кучу разных вопросов о том, какая конфигурация Nginx подходит для SSE, и дал несколько непонятных результатов относительно того, какие настройки использовать:

Так какой правильный ответ?

c4urself
источник

Ответы:

45

Длительное соединение

Отправленные сервером события (SSE) - это длительное HTTP-соединение **, поэтому для начала нам понадобится следующее:

proxy_http_version 1.1;
proxy_set_header Connection "";

ПРИМЕЧАНИЕ. TCP-соединения в HTTP / 1.1 являются постоянными по умолчанию, поэтому установка пустого заголовка соединения делает правильную вещь и является рекомендацией Nginx.

Chunked Transfer-Encoding

Теперь в стороне; Ответы SSE не устанавливают заголовок Content-Length, потому что они не могут знать, сколько данных будет отправлено, вместо этого им нужно использовать заголовок Transfer-Encoding [0] [1], что позволяет использовать потоковое соединение. Также обратите внимание: если вы не добавите Content-Length, большинство HTTP-серверов будут установлены Transfer-Encoding: chunked;для вас. Как ни странно, HTTP-чанки предостерегают и вызывают путаницу.

Путаница связана с несколько смутным предупреждением в разделе «Примечания» описания источника событий W3:

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

Что заставило бы поверить, Transfer-Encoding: chunked;что это плохо для SSE. Однако: это не обязательно так, это только проблема, когда ваш веб-сервер выполняет чанкинг за вас (не зная информации о ваших данных). Таким образом, хотя в большинстве постов будет предложено добавить, что chunked_transfer_encoding off;это не обязательно в типичном случае [3].

Буферизация (настоящая проблема)

В большинстве случаев возникает проблема с буферизацией между сервером приложений и клиентом. По умолчанию [4], Nginx использует proxy_buffering on(также посмотрите uwsgi_bufferingи fastcgi_bufferingзависит от вашего приложения) и может выбрать для буферизации фрагменты, которые вы хотите получить для вашего клиента. Это плохо, потому что природа SSE в реальном времени нарушается.

Однако вместо того, чтобы обращаться proxy_buffering offза всем, на самом деле лучше (если вы можете) добавить X-Accel-Buffering: noзаголовок ответа в коде сервера приложений, чтобы отключить буферизацию только для ответа на основе SSE, а не для всех ответов, поступающих из вашего приложения. сервер. Бонус: это также будет работать для uwsgiи fastcgi.

Решение

Итак, действительно важными настройками являются заголовки ответа сервера приложений:

Content-Type: text/event-stream;
Cache-Control: no-cache;
X-Accel-Buffering: no;

И, возможно, реализация некоторого механизма ping, чтобы соединение не оставалось бездействующим слишком долго. Опасность этого заключается в том, что Nginx закроет незанятые соединения в соответствии с keepaliveнастройкой.


[0] https://tools.ietf.org/html/rfc2616#section-3.6
[1] https://en.wikipedia.org/wiki/Chunked_transfer_encoding
[2] https://www.w3.org/TR / 2009 / WD-eventsource-20091029 / # text-event-stream
[3] https://github.com/whatwg/html/issues/515
[4] http://nginx.org/en/docs/http/ ngx_http_proxy_module.html # proxy_buffering
[5] https://tools.ietf.org/html/rfc7230#section-6.3
[6] https://gist.github.com/CMCDragonkai/6bfade6431e9ffb7fe88

c4urself
источник
Можете ли вы уточнить, что такое механизм пинга? Это просто отправка пустого сообщения на канал? Я настроил заголовки уровня nginx и приложения, но я все еще получаю 504 таймаута от nginx для любой из конечных точек источника события.
wgwz
ping - это просто некоторые (поддельные) данные, отправленные с интервалом через соединение, на клиенте вы можете обработать этот ping и проигнорировать его. ПРИМЕЧАНИЕ: если ваше соединение вообще не работает, пинг не поможет, что-то еще не так.
c4urself
2
Я добавил заголовки ответа, как предложено, и это работает. Я не вносил изменений в конфиг nginx v1.12 и пока проблем нет.
Миккель
1
Добавление X-Accel-Buffering: noзаголовка было для меня ключевым, но важно, что я должен был сделать, как писал @ c4urself: «добавить X-Accel-Buffering: no в качестве заголовка ответа в коде вашего сервера приложений ». Добавление этого заголовка в раздел местоположения в моей конфигурации nginx не сработало - весь поток событий ожидал отправки до тех пор, пока приложение не будет завершено / завершено.
MDMower
Является ли proxy_http_version 1.1; необходимым? Я пытаюсь запустить более 6 потоков SSE из браузера и, следовательно, мне нужен HTTP2.
Билал Фазлани