Tornado Example ServerΒΆ

This example is a basic HTTP/2 server written using the Tornado asynchronous networking library.

The server returns the request headers as a JSON document to the caller, just like the example from the Getting Started: Writing Your Own HTTP/2 Server document.

 1#!/usr/bin/env python
 2# -*- coding: utf-8 -*-
 3"""
 4tornado-server.py
 5~~~~~~~~~~~~~~~~~
 6
 7A fully-functional HTTP/2 server written for Tornado.
 8"""
 9import collections
10import json
11import ssl
12
13import tornado.gen
14import tornado.ioloop
15import tornado.iostream
16import tornado.tcpserver
17
18from h2.config import H2Configuration
19from h2.connection import H2Connection
20from h2.events import RequestReceived, DataReceived
21
22
23def create_ssl_context(certfile, keyfile):
24    ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
25    ssl_context.options |= (
26        ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_COMPRESSION
27    )
28    ssl_context.set_ciphers("ECDHE+AESGCM")
29    ssl_context.load_cert_chain(certfile=certfile, keyfile=keyfile)
30    ssl_context.set_alpn_protocols(["h2"])
31    return ssl_context
32
33
34class H2Server(tornado.tcpserver.TCPServer):
35
36    @tornado.gen.coroutine
37    def handle_stream(self, stream, address):
38        handler = EchoHeadersHandler(stream)
39        yield handler.handle()
40
41
42class EchoHeadersHandler(object):
43
44    def __init__(self, stream):
45        self.stream = stream
46
47        config = H2Configuration(client_side=False)
48        self.conn = H2Connection(config=config)
49
50    @tornado.gen.coroutine
51    def handle(self):
52        self.conn.initiate_connection()
53        yield self.stream.write(self.conn.data_to_send())
54
55        while True:
56            try:
57                data = yield self.stream.read_bytes(65535, partial=True)
58                if not data:
59                    break
60
61                events = self.conn.receive_data(data)
62                for event in events:
63                    if isinstance(event, RequestReceived):
64                        self.request_received(event.headers, event.stream_id)
65                    elif isinstance(event, DataReceived):
66                        self.conn.reset_stream(event.stream_id)
67
68                yield self.stream.write(self.conn.data_to_send())
69
70            except tornado.iostream.StreamClosedError:
71                break
72
73    def request_received(self, headers, stream_id):
74        headers = collections.OrderedDict(headers)
75        data = json.dumps({'headers': headers}, indent=4).encode('utf-8')
76
77        response_headers = (
78            (':status', '200'),
79            ('content-type', 'application/json'),
80            ('content-length', str(len(data))),
81            ('server', 'tornado-h2'),
82        )
83        self.conn.send_headers(stream_id, response_headers)
84        self.conn.send_data(stream_id, data, end_stream=True)
85
86
87if __name__ == '__main__':
88    ssl_context = create_ssl_context('server.crt', 'server.key')
89    server = H2Server(ssl_options=ssl_context)
90    server.listen(8888)
91    io_loop = tornado.ioloop.IOLoop.current()
92    io_loop.start()