Adventures in Semi-Synchronous WSGI

PEP 3333

import time

def data_stream():
  for i in range(0, 2):
    yield 'data: "event"\n\n'.encode('UTF-8')
  yield 'data: "closed"\n\n'.encode('UTF-8')

def app(environ, start_response):
  if environ.get('PATH_INFO') == '/events':
    start_response('200 OK', [
      ('Content-Type', 'text/event-stream; charset=UTF-8'),
      ('Cache-Control', 'no-cache'),
      ('Content-Encoding', 'identity'),
      ('Transfer-Encoding', 'identity'),
    return iter(data_stream())

  response = "Hello, world!\n\n"

  for key, value in environ.items():
    response += f"  {key}: {value}\n"

  status = '200 Hello World'
  headers = [
    ('Content-Type', 'text/plain; charset=UTF-8'),
    ('Content-Length', str(len(response))),
  start_response(status, headers)

  return iter([ response.encode('UTF-8') ])

gunicorn -b -w 2 app:app

(Note: this is actually horrible because it blocks the workers. I leave it as an exercise to the reader to figure out an Eventlet/Gevent-based solution to that.)

(And, BTW, don't @ me, etc: I prefer Eventlet, and in my limited testing it even proved to perform better than Gevent. You're more likely to exhaust IP connection ports than to have your application be the bottleneck, or at least it was so in my extremely synthetic and limited testing!)