Отправленные сервером события с Python, Twisted и Flask: правильный ли это подход для сна?

Я начал изучать события, отправляемые сервером, и мне стало интересно попробовать их с моими любимыми инструментами: 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.


person Manuel    schedule 04.12.2013    source источник
comment
Я использовал SSE в прошлом для проекта ... несколько месяцев назад я снова использовал SSE и обнаружил, что в Firefox они работают с ошибками, и никто не пытается решить проблемы. Кажется, технология приостановлена. Веб-сокеты кажутся предпочтительными во всем мире. Просто говорю. (к сожалению, я предпочитаю SSE.. :( )   -  person Paolo Casciello    schedule 04.12.2013
comment
Спасибо за отзыв, Паоло, очень приятно!   -  person Manuel    schedule 04.12.2013


Ответы (1)


В вашем примере Twisted используется только как контейнер wsgi. Как и любой другой wsgi-контейнер на основе потоков, он позволяет использовать time.sleep(1).

Это тот случай, когда разрешение Twisted напрямую обрабатывать /my_event_source может быть полезным. Вот пример из использования событий, отправленных сервером, реализованных на Python. используя скрученный:

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 взят из того же источника:

<!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 предполагает обрабатывать все URL-адреса, поэтому код маршрутизации необходимо переписать для поддержки нескольких обработчиков фляг.

person jfs    schedule 12.12.2013
comment
Спасибо за очень полный ответ, очень признателен! - person Manuel; 13.12.2013