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