Flask 服务器发送事件套接字异常
我在考虑使用SSE(服务器发送事件)来把新数据推送给客户端,然后用Flot(一个JavaScript图表库)来显示“实时”更新。我的服务器是用Python的Flask框架搭建的,我已经搞定了如何把数据推送到客户端,但问题在于一旦我离开页面,就会出现错误:
Exception happened during processing of request from ('127.0.0.1', 38814)
Traceback (most recent call last):
File "/usr/lib/python2.7/SocketServer.py", line 582, in process_request_thread
self.finish_request(request, client_address)
File "/usr/lib/python2.7/SocketServer.py", line 323, in finish_request
self.RequestHandlerClass(request, client_address, self)
File "/usr/lib/python2.7/SocketServer.py", line 640, in __init__
self.finish()
File "/usr/lib/python2.7/SocketServer.py", line 693, in finish
self.wfile.flush()
File "/usr/lib/python2.7/socket.py", line 303, in flush
self._sock.sendall(view[write_offset:write_offset+buffer_size])
error: [Errno 32] Broken pipe
我明白为什么会出现这个错误——因为在不断循环中提供“实时”数据时,连接没有被关闭。我的问题是,如何检测到页面的变化并干净利落地关闭这个连接?我能在客户端关闭连接吗?那我该如何检测页面变化呢?
这是服务器代码的基本框架,当然我会把文本消息替换成包含要显示对象列表的JSON格式:
def event_stream():
import time
while True:
time.sleep(1)
yield "data: This is a message number X.\n\n"
@app.route('/stream')
def stream():
return Response(event_stream(), mimetype="text/event-stream")
4 个回答
我找到了一种比较“脏”的解决办法(包括了一些临时修补),但它确实能工作。
因为在连接断开时,SocketServer.StreamRequestHandler.finish
会出现一个异常,所以我们可以对它进行修补,捕捉这个异常,然后按照我们的需求来处理:
import socket
import SocketServer
def patched_finish(self):
try:
if not self.wfile.closed:
self.wfile.flush()
self.wfile.close()
except socket.error:
# Remove this code, if you don't need access to the Request object
if _request_ctx_stack.top is not None:
request = _request_ctx_stack.top.request
# More cleanup code...
self.rfile.close()
SocketServer.StreamRequestHandler.finish = patched_finish
如果你需要访问对应的 Request
对象,你还需要用 flask.stream_with_context
来包装事件流,在我的例子中是这样:
@app.route(url)
def method(host):
return Response(stream_with_context(event_stream()),
mimetype='text/event-stream')
再次强调,这个解决办法非常“脏”,如果你不使用内置的 WSGI 服务器,可能就不管用了。
我没有更好的答案,但我觉得上面的ajax请求服务器的方式不好。
在flask中,SSE(服务器发送事件)是通过Response对象来进行流式传输的。如果能在Response中检测到断开连接或管道破裂的事件,那就能更好地处理socket事件,并释放其他分配的资源。
你可以使用 onBeforeUnload
或者 jQuery 的 window.unload()
来发送一个 Ajax 请求,调用一个关闭连接的方法。大概是这样的:
$(window).unload(
function() {
$.ajax(type: 'POST',
async: false,
url: 'foo.com/client_teardown')
}
}
不过,unload()
和 onBeforeUnload()
的处理方式在不同浏览器中可能会有些不一致,所以在像 Chrome 这样的浏览器中,你可能还需要做一些额外的工作。