| #!/usr/bin/env python |
| # -*- coding: utf-8 -*- |
| """ |
| tornado-server.py |
| ~~~~~~~~~~~~~~~~~ |
| |
| A fully-functional HTTP/2 server written for Tornado. |
| """ |
| import collections |
| import json |
| import ssl |
| |
| import tornado.gen |
| import tornado.ioloop |
| import tornado.iostream |
| import tornado.tcpserver |
| |
| from h2.config import H2Configuration |
| from h2.connection import H2Connection |
| from h2.events import RequestReceived, DataReceived |
| |
| |
| def create_ssl_context(certfile, keyfile): |
| ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) |
| ssl_context.options |= ( |
| ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_COMPRESSION |
| ) |
| ssl_context.set_ciphers("ECDHE+AESGCM") |
| ssl_context.load_cert_chain(certfile=certfile, keyfile=keyfile) |
| ssl_context.set_alpn_protocols(["h2"]) |
| return ssl_context |
| |
| |
| class H2Server(tornado.tcpserver.TCPServer): |
| |
| @tornado.gen.coroutine |
| def handle_stream(self, stream, address): |
| handler = EchoHeadersHandler(stream) |
| yield handler.handle() |
| |
| |
| class EchoHeadersHandler(object): |
| |
| def __init__(self, stream): |
| self.stream = stream |
| |
| config = H2Configuration(client_side=False) |
| self.conn = H2Connection(config=config) |
| |
| @tornado.gen.coroutine |
| def handle(self): |
| self.conn.initiate_connection() |
| yield self.stream.write(self.conn.data_to_send()) |
| |
| while True: |
| try: |
| data = yield self.stream.read_bytes(65535, partial=True) |
| if not data: |
| break |
| |
| events = self.conn.receive_data(data) |
| for event in events: |
| if isinstance(event, RequestReceived): |
| self.request_received(event.headers, event.stream_id) |
| elif isinstance(event, DataReceived): |
| self.conn.reset_stream(event.stream_id) |
| |
| yield self.stream.write(self.conn.data_to_send()) |
| |
| except tornado.iostream.StreamClosedError: |
| break |
| |
| def request_received(self, headers, stream_id): |
| headers = collections.OrderedDict(headers) |
| data = json.dumps({'headers': headers}, indent=4).encode('utf-8') |
| |
| response_headers = ( |
| (':status', '200'), |
| ('content-type', 'application/json'), |
| ('content-length', str(len(data))), |
| ('server', 'tornado-h2'), |
| ) |
| self.conn.send_headers(stream_id, response_headers) |
| self.conn.send_data(stream_id, data, end_stream=True) |
| |
| |
| if __name__ == '__main__': |
| ssl_context = create_ssl_context('server.crt', 'server.key') |
| server = H2Server(ssl_options=ssl_context) |
| server.listen(8888) |
| io_loop = tornado.ioloop.IOLoop.current() |
| io_loop.start() |