使用Python,Twisted和Flask的服务器发送事件:这是一种正确的睡眠方法吗?

时间:2013-12-04 16:01:44

标签: python flask twisted server-sent-events

我开始查看服务器发送的事件,并有兴趣尝试使用我的首选工具,Python,Flask和Twisted。 我问是否正在按照我正在做的方式睡觉是好的,与gevent的greenlet.sleep做的方式相比,这是我非常简单的代码并且“移植”到Twisted(来自gevent):

#!/usr/bin/env python

import random
from twisted.web.server import Site
from twisted.web.wsgi import WSGIResource
from twisted.internet import reactor
import time

from flask import Flask, request, Response
app = Flask(__name__)

def event_stream():
    count = 0
    while True:
        count += 1
        yield 'data: %c (%d)\n\n' % (random.choice('abcde'), count)
        time.sleep(1)


@app.route('/my_event_source')
def sse_request():
    return Response(
            event_stream(),
            mimetype='text/event-stream')


@app.route('/')
def page():
    return '''
<!DOCTYPE html>
<html>
    <head>
        <script type="text/javascript" src="//code.jquery.com/jquery-1.8.0.min.js"></script>
        <script type="text/javascript">
            $(document).ready(
                    function() {
                        sse = new EventSource('/my_event_source');
                        sse.onmessage = function(message) {
                            console.log('A message has arrived!');
                            $('#output').append('<li>'+message.data+'</li>');
                        }

                    })
        </script>
    </head>
    <body>
        <h2>Demo</h2>
        <ul id="output"></ul>
    </body>
</html>
'''


if __name__ == '__main__':
    resource = WSGIResource(reactor, reactor.getThreadPool(), app)
    site = Site(resource)
    reactor.listenTCP(8001, site)
    reactor.run()

尽管time.sleep是一个阻塞函数,但它不会阻止Twisted反应器,这应该通过多个不同的浏览器可以访问页面并正确接收事件来证明:如果需要使用不同的浏览器,正如Chromium所做的那样,具有相同URI的多个不同请求将排队,因为这是一个流响应,所以浏览器队列将一直忙,直到套接字或请求关闭。 你有什么勇气?有更好的方法吗?关于Twisted和Flask周围没有太多示例代码。

1 个答案:

答案 0 :(得分:4)

您的示例仅使用扭曲为wsgi container。除了任何其他基于线程的wsgi容器,它允许您使用time.sleep(1)

允许扭曲直接处理/my_event_source可能是有益的。以下是使用twisted:

在Python中实现的Using server sent events示例
def cycle(echo):
    # Every second, sent a "ping" event.
    timestr = datetime.utcnow().isoformat()+"Z"
    echo("event: ping\n")
    echo('data: ' +  json.dumps(dict(time=timestr)))
    echo("\n\n")

    # Send a simple message at random intervals.
    if random.random() < 0.1:
        echo("data: This is a message at time {}\n\n".format(timestr))

class SSEResource(resource.Resource):
    def render_GET(self, request):
        request.setHeader("Content-Type", "text/event-stream")
        lc = task.LoopingCall(cycle, request.write)
        lc.start(1) # repeat every second
        request.notifyFinish().addBoth(lambda _: lc.stop())
        return server.NOT_DONE_YET

客户static/index.html来自the same source

<!doctype html>
<title>Using server-sent events</title>
<ol id="eventlist">nothing sent yet.</ol>
<script>
if (!!window.EventSource) {
  var eventList = document.getElementById("eventlist");
  var source = new EventSource('/my_event_source');
  source.onmessage = function(e) {
    var newElement = document.createElement("li");

    newElement.innerHTML = "message: " + e.data;
    eventList.appendChild(newElement);
  }
  source.addEventListener("ping", function(e) {
    var newElement = document.createElement("li");

    var obj = JSON.parse(e.data);
    newElement.innerHTML = "ping at " + obj.time;
    eventList.appendChild(newElement);
  }, false);
  source.onerror = function(e) {
    alert("EventSource failed.");
    source.close();
  };
}
</script>

您可以将它与您的wsgi应用程序结合使用:

app = Flask(__name__)
@app.route('/')
def index():
    return redirect(url_for('static', filename='index.html'))

if __name__ == "__main__":
    root = resource.Resource()
    root.putChild('', wsgi.WSGIResource(reactor, reactor.getThreadPool(), app))
    root.putChild('static', static.File("./static"))
    root.putChild('my_event_source', SSEResource())

    reactor.listenTCP(8001, server.Site(root))
    reactor.run()

WSGIResource期望处理所有网址,因此需要重写路由代码以支持多个烧瓶处理程序。