我正在尝试从Pyramid应用程序中传输Server-Sent Events,但我无法弄清楚如何从我的视图中传输响应主体。这是我正在使用的测试视图(它完全没有实现SSE,只是为了计算流部分):
@view_config(route_name='iter_test')
def iter_test(request):
import time
def test_iter():
i = 0
while True:
i += 1
if i == 5:
raise StopIteration
yield str(time.time())
print time.time()
time.sleep(1)
return test_iter()
这会产生ValueError: Could not convert return value of the view callable function pdiff.views.iter_test into a response object. The value returned was <generator object test_iter at 0x3dc19b0>.
我已经尝试了return Response(app_iter=test_iter())
,它至少没有错误输出,但它不会对响应进行流式处理 - 它会等到生成器完成后再将响应返回给我的浏览器。
我认识到可以简单地为每个请求返回一个事件,并允许客户端在每个事件之后重新连接,但我更喜欢通过从单个请求流式传输多个事件来保留服务器发送事件的实时性质,而不是重新连接延迟。我怎么能用金字塔做到这一点?
答案 0 :(得分:8)
我发现了这个问题。原来我的应用程序代码很好,问题在于Waitress和nginx:
Pyramid使用的默认Web服务器Waitress以18000字节的块缓冲所有输出(有关详细信息,请参阅this issue)。
问题的根源是由我放在我的Pyramid应用程序前面的Web服务器nginx隐藏的,也缓冲响应。
(1)可以通过以下任一方法解决:
在.ini文件中使用send_bytes = 1
配置女服务员。这可以解决流式传输问题,但会使整个应用超级变慢。正如@Zitrax所提到的,您可以使用更高的值恢复某些速度,但任何高于1的值都会使消息卡在缓冲区中。
切换到gunicorn。我不知道gunicorn是否只使用较小的缓冲区,或者它是否与app_iter
表现得更好,但它有效,并保持我的应用程序快速。
(2)可以通过配置nginx来禁用流路由的缓冲来解决。
您需要在nginx conf中设置proxy_buffering off
。此设置适用于通过proxy_pass
托管的网站。如果您未使用proxy_pass
,则可能需要不同的设置。
您可以根据请求标头配置nginx为每个响应动态启用/禁用缓冲,如this question on the topic(EventSource / Server-Sent Events的一个很好的解决方案)
您也可以在nginx conf中的location
块中进行配置。如果您使用EventSource之外的东西,并且您不希望收到特定标头,或者您正在使用EventSource,但是想要从普通浏览器选项卡调试响应,那么这是很好的,您无法发送{您的请求中包含{1}}标题。
答案 1 :(得分:3)
前一段时间我做了一些测试,尝试事件源/服务器发送事件。我刚刚测试过,它仍适用于Pyramid 1.5a。
@view_config(route_name = 'events')
def events(request):
headers = [('Content-Type', 'text/event-stream'),
('Cache-Control', 'no-cache')]
response = Response(headerlist=headers)
response.app_iter = message_generator()
return response
def message_generator():
socket2 = context.socket(zmq.SUB)
socket2.connect(SOCK)
socket2.setsockopt(zmq.SUBSCRIBE, '')
while True:
msg = socket2.recv()
yield "data: %s\n\n" % json.dumps({'message': msg})
此处的完整示例:https://github.com/antoineleclair/zmq-sse-chat。看看https://github.com/antoineleclair/zmq-sse-chat/blob/master/sse/views.py。
我不确定为什么我的工作而不是你的。也许这是标题。或者每条消息后的两个'\n'
。顺便说一句,如果您正确查看事件源规范,则必须为每个新事件添加前缀data:
,并使用\n\n
作为事件分隔符。
答案 2 :(得分:2)
如果您没有为视图指定任何渲染器,则必须返回Response对象。 Pyramid Response对象有一个特殊的参数app_iter用于返回迭代器。所以你应该这样做:
import time
from pyramid.response import Response
@view_config(route_name='iter_test')
def iter_test(request):
def test_iter():
for _ in range(5):
yield str(time.time())
print time.time()
time.sleep(1)
return Response(app_iter=test_iter())
我还编辑了一些代码,以便更具可读性。
<强>更新强>
我已尝试返回响应(app_iter = test_iter()),至少没有错误输出,但它没有流响应 - 它等到发生器完成之前将响应返回给我的浏览器。
我猜问题在于缓冲。尝试发送一个非常大的迭代器。