Eventlet Example ServerΒΆ

This example is a basic HTTP/2 server written using the eventlet concurrent networking framework. This example is notable for demonstrating how to configure PyOpenSSL, which eventlet uses for its TLS layer.

In terms of HTTP/2 functionality, this example is very simple: it returns the request headers as a JSON document to the caller. It does not obey HTTP/2 flow control, which is a flaw, but it is otherwise functional.

  1# -*- coding: utf-8 -*-
  2"""
  3eventlet-server.py
  4~~~~~~~~~~~~~~~~~~
  5
  6A fully-functional HTTP/2 server written for Eventlet.
  7"""
  8import collections
  9import json
 10
 11import eventlet
 12
 13from eventlet.green.OpenSSL import SSL, crypto
 14from h2.config import H2Configuration
 15from h2.connection import H2Connection
 16from h2.events import RequestReceived, DataReceived
 17
 18
 19class ConnectionManager(object):
 20    """
 21    An object that manages a single HTTP/2 connection.
 22    """
 23    def __init__(self, sock):
 24        config = H2Configuration(client_side=False)
 25        self.sock = sock
 26        self.conn = H2Connection(config=config)
 27
 28    def run_forever(self):
 29        self.conn.initiate_connection()
 30        self.sock.sendall(self.conn.data_to_send())
 31
 32        while True:
 33            data = self.sock.recv(65535)
 34            if not data:
 35                break
 36
 37            events = self.conn.receive_data(data)
 38
 39            for event in events:
 40                if isinstance(event, RequestReceived):
 41                    self.request_received(event.headers, event.stream_id)
 42                elif isinstance(event, DataReceived):
 43                    self.conn.reset_stream(event.stream_id)
 44
 45            self.sock.sendall(self.conn.data_to_send())
 46
 47    def request_received(self, headers, stream_id):
 48        headers = collections.OrderedDict(headers)
 49        data = json.dumps({'headers': headers}, indent=4).encode('utf-8')
 50
 51        response_headers = (
 52            (':status', '200'),
 53            ('content-type', 'application/json'),
 54            ('content-length', str(len(data))),
 55            ('server', 'eventlet-h2'),
 56        )
 57        self.conn.send_headers(stream_id, response_headers)
 58        self.conn.send_data(stream_id, data, end_stream=True)
 59
 60
 61def alpn_callback(conn, protos):
 62    if b'h2' in protos:
 63        return b'h2'
 64
 65    raise RuntimeError("No acceptable protocol offered!")
 66
 67
 68def npn_advertise_cb(conn):
 69    return [b'h2']
 70
 71
 72# Let's set up SSL. This is a lot of work in PyOpenSSL.
 73options = (
 74    SSL.OP_NO_COMPRESSION |
 75    SSL.OP_NO_SSLv2 |
 76    SSL.OP_NO_SSLv3 |
 77    SSL.OP_NO_TLSv1 |
 78    SSL.OP_NO_TLSv1_1
 79)
 80context = SSL.Context(SSL.SSLv23_METHOD)
 81context.set_options(options)
 82context.set_verify(SSL.VERIFY_NONE, lambda *args: True)
 83context.use_privatekey_file('server.key')
 84context.use_certificate_file('server.crt')
 85context.set_npn_advertise_callback(npn_advertise_cb)
 86context.set_alpn_select_callback(alpn_callback)
 87context.set_cipher_list(
 88    "ECDHE+AESGCM"
 89)
 90context.set_tmp_ecdh(crypto.get_elliptic_curve(u'prime256v1'))
 91
 92server = eventlet.listen(('0.0.0.0', 443))
 93server = SSL.Connection(context, server)
 94pool = eventlet.GreenPool()
 95
 96while True:
 97    try:
 98        new_sock, _ = server.accept()
 99        manager = ConnectionManager(new_sock)
100        pool.spawn_n(manager.run_forever)
101    except (SystemExit, KeyboardInterrupt):
102        break