Logo Trilium Web Clipper

For Server-Sent Events (SSE) what Nginx proxy configuration is appropriate?

Long-running connection  长时间运行的连接

Server-Sent Events (SSE) are a long-running HTTP connection**, so for starters we need this:
服务器发送事件(SSE)是一个长期运行的 HTTP 连接**,因此首先我们需要这个:

proxy_http_version 1.1;
proxy_set_header Connection "";

NOTE: TCP connections in HTTP/1.1 are persistent by default, so setting the Connection header to empty does the right thing and is the Nginx suggestion.
注意:HTTP/1.1 中的 TCP 连接默认是持久的,因此将 Connection 标头设置为空是正确的,也是 Nginx 的建议。

Chunked Transfer-Encoding
分块传输编码

Now an aside; SSE responses don't set a Content-Length header because they cannot know how much data will be sent, instead they need to use the Transfer-Encoding header[0][1], what allows for a streaming connection. Also note: if you don't add a Content-Length most HTTP servers will set Transfer-Encoding: chunked; for you. Strangely, HTTP chunking warned against and causes confusion.
现在顺便提一下;SSE 响应不会设置 Content-Length 标头,因为它们无法知道将发送多少数据,而是需要使用 Transfer-Encoding 标头[0][1],这允许流式连接。另请注意:如果您不添加 Content-Length,大多数 HTTP 服务器都会为您设置 Transfer-Encoding: chunked; 。奇怪的是,HTTP 分块会发出警告并引起混淆。

The confusion stems from a somewhat vague warning in the Notes section of the W3 EventSource description:
这种混淆源于 W3 EventSource 描述的注释部分中一个有些模糊的警告:

Authors are also cautioned that HTTP chunking can have unexpected negative effects on the reliability of this protocol. Where possible, chunking should be disabled for serving event streams unless the rate of messages is high enough for this not to matter.
还需注意,HTTP 分块可能会对该协议的可靠性产生意想不到的负面影响。在可能的情况下,应禁用分块来处理事件流,除非消息速率足够高,以至于不会产生影响。

Which would lead one to believe Transfer-Encoding: chunked; is a bad thing for SSE. However: this isn't necessarily the case, it's only a problem when your webserver is doing the chunking for you (not knowing information about your data). So, while most posts will suggest adding chunked_transfer_encoding off; this isn't necessary in the typical case[3].
这让人觉得 Transfer-Encoding: chunked; 对 SSE 来说是个坏事。然而:情况并非总是如此,只有当你的 Web 服务器替你进行分块(不知道你的数据信息)时,这才会是个问题。所以,虽然大多数帖子会建议添加 chunked_transfer_encoding off; ,但在典型情况下,这并非必要[3]。

Buffering (the real problem)
缓冲(真正的问题)

Where most problems come from is having any type of buffering between the app server and the client. By default[4], Nginx uses proxy_buffering on (also take a look at uwsgi_buffering and fastcgi_buffering depending on your application) and may choose to buffer the chunks that you want to get out to your client. This is a bad thing because the realtime nature of SSE breaks.
大多数问题都源于应用服务器和客户端之间存在任何类型的缓冲。默认情况下[4],Nginx 使用 proxy_buffering on (也可以根据你的应用查看 uwsgi_bufferingfastcgi_buffering ),并且可能会选择缓冲你想要发送给客户端的数据块。这很糟糕,因为 SSE 的实时性会被破坏。

However, instead of turning proxy_buffering off for everything, it's actually best (if you're able to) to add the X-Accel-Buffering: no as a response header in your application server code to only turn buffering off for the SSE based response and not for all responses coming from your app server. Bonus: this will also work for uwsgi and fastcgi.
但是,与其 proxy_buffering off ,实际上最好的做法(如果可以的话)是将 X-Accel-Buffering: no 福利:这种方法也适用于 uwsgifastcgi

Solution  解决方案

And so the really important settings are actually the app-server response headers:
因此真正重要的设置实际上是应用服务器响应标头:

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

And potentially the implementation of some ping mechanism so that the connection doesn't stay idle for too long. The danger of this is that Nginx will close idle connections as set using the keepalive setting.
并且可能实现某种 ping 机制,以防止连接空闲时间过长。这样做的危险在于,Nginx 会根据 keepalive 设置的设置关闭空闲连接。


[0] https://www.rfc-editor.org/rfc/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://www.rfc-editor.org/rfc/rfc7230#section-6.3
[6] https://gist.github.com/CMCDragonkai/6bfade6431e9ffb7fe88