我正在尝试编写一个 API,使用 Rails 6 中的
ActionController::Live::SSE
传递服务器发送的事件。为了理解如何最好地编写测试,我从本质上复制这里看到的简单示例开始:
my_controller.rb
:
class MyController < ApplicationController
include ActionController::Live
def capture
response.headers['Content-Type'] = 'text/event-stream'
sse = SSE.new(response.stream)
3.times do
sse.write({message: "Awaiting confirmation ..."})
sleep 2
end
fake_response = { #The response as hash.
"annotation_id"=>nil,
"domain"=>"some.random.com",
"id"=>2216354,
"path"=>"/flummoxer/",
"protocol"=>"https",
}
sse.write(fake_response, event: 'successful capture')
rescue => e
sse.write(e.message, event: 'something broke: ')
ensure
response.stream.close
end
end
当我向此端点发送 curl 请求(无论是 POST 还是 GET)时,响应全部到达一个块,而不是作为单独的响应:
$ curl -i -X GET -H "Content-Type: application/json" -d '{"url": "https://some.random.com/flummoxer"}' http://localhost:3000/capture
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
ETag: W/"a24048695d2feca40232467f0fbb410a"
X-Request-Id: 648a5229-a43d-40d3-82fd-1c4ea6fe19cc
X-Runtime: 24.082528
Transfer-Encoding: chunked
data: {"message":"Awaiting confirmation ..."}
data: {"message":"Awaiting confirmation ..."}
data: {"message":"Awaiting confirmation ..."}
event: successful capture
data: {"annotation_id":null,"domain":"some.random.com","id":2216354,"path":"/flummoxer/","protocol":"https"}
在我的测试中,尝试解析服务器响应失败的事实更容易看出这一点:
MultiJson::ParseError: 783: unexpected token at 'data: {"message":"Awaiting confirmation ..."}
data: {"message":"Awaiting confirmation ..."}
data: {"message":"Awaiting confirmation ..."}
event: successful capture
data: {"annotation_id":null,"domain":"some.random.com","id":2216354,"path":"/flummoxer/","protocol":"https"}
'
我的服务器是 Puma,所以 不是因为我使用的是 Thin,如这个答案所示.
我做错了什么?如果您需要,我会提供任何可能有用的其他信息。
更新:这个问题的答案建议将
-N
和Accept:text/event-stream
标头添加到请求中。这样做不会改变我在上面描述的行为——直到对 response.stream.close
的调用被触发后,才会发送对请求的响应。
更新 2:我还尝试破解
SSE#write
方法以在 broadcast()
上调用 Mutex::ConditionVariable
以强制发送消息。从它立即发送数据的意义上说,这是有效的,但是 curl 请求的副作用认为流已关闭,因此不会发送更多消息,这不是流。
更新 3:我还修改了
development.rb
以包含 config.allow_concurrency = true
,如 here 所示。上述行为没有变化。
我在基本的“out the book”Rails 5 SSE 应用程序中遇到了类似的问题。问题原来是导致流缓冲的机架更新。更多信息在这里https://github.com/rack/rack/issues/1619并通过包含修复
config.middleware.delete Rack::ETag
在配置/application.rb
此外,如果您在生产中使用 NGINX,它需要特殊配置才能使 SSE 工作。对于具有以下设置的 SSE 控制器的 URL,您应该在 nginx 配置中有特殊的
location
指令:
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;