Pyramid flow reaction body

I am trying to pass Server-Sent events from my Pyramid application, but I cannot figure out how to pass the response body from my view. Here's the test representation I'm using (it doesn't fully implement SSE, it's just meant to handle the streaming part):

@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() 

This gives a 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>. 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>.

I tried return Response(app_iter=test_iter()) instead, which at least does not throw an error, but does not send a response - it waits for the generator to complete before returning the response to my browser.

I understand that it can simply return one event for each request and allow clients to reconnect after each event, but I would prefer to keep the real nature of the server-related events by streaming multiple events from one request without delaying reconnection. How can I do this with Pyramid?

+7
python ajax pyramid server-sent-events
source share
3 answers

I found a problem. It turns out my application code is fine, and the problem is with Waitress and nginx:

  • The waitress, used by default for the Pyramid web server, buffers all output in 18000-byte fragments (see this problem for details).

  • The source of the problem was hidden from me nginx, the web server that I put before my Pyramid application, which also buffers responses.

(1) can be solved either:

  • Setting up the waitress with send_bytes = 1 in your .ini file. This fixes the streaming issue, but makes your application too slow. As @Zitrax mentioned, you can restore some speed with higher values, but any value greater than 1 will depend on messages stuck in the buffer.

  • Switch to guns. I don’t know if gunicorn uses a smaller buffer or works better with app_iter , but it worked and changed the application quickly.

(2) can be solved by setting nginx to disable buffering for your flow routes.

You need to install proxy_buffering off in your nginx config. This parameter applies to sites hosted through proxy_pass . If you are not using proxy_pass , you may need another parameter.

  • You can configure nginx to dynamically enable / disable buffering for each response based on request headers, as shown in this question topic (a good solution for EventSource / Server-Sent events)

  • You can alternatively configure this in the location block in your nginx conf. This is good if you use something other than EventSource and do not expect to receive a specific header or use EventSource, but want to debug the response in a simple browser tab where you cannot send Accept in your request.

+6
source share

I did some tests some time ago to try Source Source / Server Sent Events. I just tested and still works great with 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}) 

Full example here: https://github.com/antoineleclair/zmq-sse-chat . Take a look at https://github.com/antoineleclair/zmq-sse-chat/blob/master/sse/views.py .

I don’t know exactly why my work, not yours. Maybe these are the headers. Or two '\n' after each message. By the way, if you look at the event source correctly, you must prefix each new data: event and use \n\n as the event separator.

+3
source share

If you did not specify any render for your view, you need to return a Response object. The Pyramid Response object has a special app_iter argument to return iterators. Therefore, you should do it as follows:

 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()) 

I also modified your code a bit to be more readable.

UPDATE

I tried to return Response (app_iter = test_iter ()), which, at least, does not throw an error, but does not send a response - it waits until the generator completes before returning the answer to my browser.

I think the problem is buffering. Try sending a really big iterator.

+1
source share

All Articles