-
-
Save yxlwfds/aec22a1f85898d14723687f47531d58b to your computer and use it in GitHub Desktop.
| import sys | |
| import ssl | |
| import json | |
| import collections | |
| import trio | |
| import h2.config | |
| import h2.connection | |
| import h2.events | |
| ReceivedData = collections.namedtuple("ReceivedData", ("headers", "data")) | |
| class H2EchoServer: | |
| def __init__(self): | |
| config = h2.config.H2Configuration(client_side=False, header_encoding="utf-8") | |
| self.connection = h2.connection.H2Connection(config=config) | |
| self.received_data = {} | |
| self.flow_control_events = {} | |
| self.write_lock = trio.Lock() | |
| self.stream = None | |
| async def write_all_pending_data(self): | |
| async with self.write_lock: | |
| await self.stream.send_all(self.connection.data_to_send()) | |
| def request_received(self, event): | |
| self.received_data[event.stream_id] = ReceivedData(event.headers, bytearray()) | |
| def data_received(self, event): | |
| try: | |
| self.received_data[event.stream_id].data.extend(event.data) | |
| except KeyError: | |
| self.connection.reset_stream(event.stream_id, | |
| h2.errors.ErrorCodes.PROTOCOL_ERROR) | |
| else: | |
| self.connection.acknowledge_received_data(event.flow_controlled_length, | |
| event.stream_id) | |
| async def reply_echo(self, stream_id): | |
| self.flow_control_events[stream_id] = trio.Event() | |
| response_body = json.dumps({ | |
| "headers": collections.OrderedDict(self.received_data[stream_id].headers), | |
| "body": self.received_data[stream_id].data.decode("utf-8") | |
| }, indent=4).encode("utf-8") | |
| response_headers = ( | |
| (":status", "200"), | |
| ("content-type", "application/json"), | |
| ("content-length", str(len(response_body))), | |
| ("server", "python-trio-h2"), | |
| ) | |
| self.connection.send_headers(stream_id, response_headers) | |
| ptr = 0 | |
| while ptr < len(response_body): | |
| while self.connection.local_flow_control_window(stream_id) == 0: | |
| await self.flow_control_events[stream_id] | |
| chunk_size = min(self.connection.max_outbound_frame_size, | |
| self.connection.local_flow_control_window(stream_id)) | |
| self.connection.send_data(stream_id, response_body[ptr:ptr+chunk_size]) | |
| await self.write_all_pending_data() | |
| ptr += chunk_size | |
| self.connection.end_stream(stream_id) | |
| await self.write_all_pending_data() | |
| def window_updated(self, event): | |
| if event.stream_id == 0: | |
| for event in self.flow_control_events.values(): | |
| event.set() | |
| else: | |
| try: | |
| self.flow_control_events[event.stream_id].set() | |
| except KeyError: | |
| self.connection.reset_stream(event.stream_id, | |
| h2.errors.ErrorCodes.PROTOCOL_ERROR) | |
| async def __call__(self, server_stream): | |
| self.stream = server_stream | |
| try: | |
| self.connection.initiate_connection() | |
| await self.write_all_pending_data() | |
| try: | |
| async with trio.open_nursery() as nursery: | |
| while True: | |
| data = await server_stream.receive_some(65536) | |
| if not data: | |
| return | |
| events = self.connection.receive_data(data) | |
| for event in events: | |
| if isinstance(event, h2.events.RequestReceived): | |
| self.request_received(event) | |
| elif isinstance(event, h2.events.DataReceived): | |
| self.data_received(event) | |
| elif isinstance(event, h2.events.StreamEnded): | |
| nursery.start_soon(self.reply_echo, event.stream_id) | |
| elif isinstance(event, h2.events.WindowUpdated): | |
| self.window_updated(event) | |
| elif isinstance(event, h2.events.ConnectionTerminated): | |
| return | |
| await self.write_all_pending_data() | |
| finally: | |
| await self.write_all_pending_data() | |
| except: | |
| print("Got exception: {!r}".format(sys.exc_info())) | |
| async def main(port): | |
| 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="cert.crt", keyfile="cert.key") | |
| ssl_context.set_alpn_protocols(["h2"]) | |
| await trio.serve_ssl_over_tcp(lambda stream: H2EchoServer()(stream), | |
| port, ssl_context) | |
| if __name__ == "__main__": | |
| port = int(sys.argv[1]) | |
| print("Try: $ curl --tlsv1.2 --http2 -k https://localhost:{}/path -d'data'" | |
| .format(port)) | |
| print("Or open a browser to https://localhost:{}/ and accept all the warnings" | |
| .format(port)) | |
| trio.run(main, port) |
cert.key
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAyq0DWK7wQ3TQVTR4FwEaUtWY0SBXsmRu6Str6TBLyP9TbLnR
A8Ylz8WIhUN+4GqTl0sAJM1zoD8VBxe9aY5zmLGYLdAopqwym6h+orPRX0LqKtl1
hdn2tyxbSAdAiv3z3J1H43VVsiURVb9P0UkvcHZQP0ZUiRQQvObjsJI5Zm8DFBSW
zHwJQ25QJarJkmipMU2PQt4kM5YYKy6DU+SPRDXH6MxUvqXpl/q8yQs04yw6xazF
ST7Qve2X87NxOBQ4KzBTNjn1EiLpG52unShpTw24ev3XKJKo9soWVfXQz0rAQR2c
ilzGE0Pd1pysPJyPe7F5aWorKdza84VSSHCJowIDAQABAoIBACp+nh4BB/VMz8Wd
q7Q/EfLeQB1Q57JKpoqTBRwueSVai3ZXe4CMEi9/HkG6xiZtkiZ9njkZLq4hq9oB
2z//kzMnwV2RsIRJxI6ohGy+wR51HD4BvEdlTPpY/Yabpqe92VyfSYxidKZWaU0O
QMED1EODOw4ZQ+4928iPrJu//PMB4e7TFao0b9Fk/XLWtu5/tQZz9jsrlTi1zthh
7n+oaGNhfTeIJJL4jrhTrKW1CLHXATtr9SJlfZ3wbMxQVeyj2wUlP1V0M6kBuhNj
tbGbMpixD5iCNJ49Cm2PHg+wBOfS3ADGIpi3PcGw5mb8nB3N9eGBRPhLShAlq5Hi
Lv4tyykCgYEA8u3b3xJ04pxWYN25ou/Sc8xzgDCK4XvDNdHVTuZDjLVA+VTVPzql
lw7VvJArsx47MSPvsaX/+4hQXYtfnR7yJpx6QagvQ+z4ludnIZYrQwdUmb9pFL1s
8UNj+3j9QFRPenIiIQ8qxxNIQ9w2HsVQ8scvc9CjYop/YYAPaQyHaL8CgYEA1ZSz
CR4NcpfgRSILdhb1dLcyw5Qus1VOSAx3DYkhDkMiB8XZwgMdJjwehJo9yaqRCLE8
Sw5znMnkfoZpu7+skrjK0FqmMpXMH9gIszHvFG8wSw/6+2HIWS19/wOu8dh95LuC
0zurMk8rFqxgWMWF20afhgYrUz42cvUTo10FVB0CgYEAt7mW6W3PArfUSCxIwmb4
VmXREKkl0ATHDYQl/Cb//YHzot467TgQll883QB4XF5HzBFurX9rSzO7/BN1e6I0
52i+ubtWC9xD4fUetXMaQvZfUGxIL8xXgVxDWKQXfLiG54c8Mp6C7s6xf8kjEUCP
yR1F0SSA/Pzb+8RbY0p7eocCgYA+1rs+SXtHZev0KyoYGnUpW+Uxqd17ofOgOxqj
/t6c5Z+TjeCdtnDTGQkZlo/rT6XQWuUUaDIXxUbW+xEMzj4mBPyXBLS1WWFvVQ5q
OpzO9E/PJeqAH6rkof/aEelc+oc/zvOU1o9uA+D3kMvgEm1psIOq2RHSMhGvDPA0
NmAk+QKBgQCwd1681GagdIYSZUCBecnLtevXmIsJyDW2yR1NNcIe/ukcVQREMDvy
5DDkhnGDgnV1D5gYcXb34g9vYvbfTnBMl/JXmMAAG1kIS+3pvHyN6f1poVe3yJV1
yHVuvymnJxKnyaV0L3ntepVvV0vVNIkA3oauoUTLto6txBI+b/ImDA==
-----END RSA PRIVATE KEY-----
cert.crt
-----BEGIN CERTIFICATE-----
MIIDhTCCAm2gAwIBAgIJAOrxh0dOYJLdMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNTA5MTkxNDE2
NDRaFw0xNTEwMTkxNDE2NDRaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l
LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV
BAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMqt
A1iu8EN00FU0eBcBGlLVmNEgV7Jkbukra+kwS8j/U2y50QPGJc/FiIVDfuBqk5dL
ACTNc6A/FQcXvWmOc5ixmC3QKKasMpuofqKz0V9C6irZdYXZ9rcsW0gHQIr989yd
R+N1VbIlEVW/T9FJL3B2UD9GVIkUELzm47CSOWZvAxQUlsx8CUNuUCWqyZJoqTFN
j0LeJDOWGCsug1Pkj0Q1x+jMVL6l6Zf6vMkLNOMsOsWsxUk+0L3tl/OzcTgUOCsw
UzY59RIi6Rudrp0oaU8NuHr91yiSqPbKFlX10M9KwEEdnIpcxhND3dacrDycj3ux
eWlqKync2vOFUkhwiaMCAwEAAaNQME4wHQYDVR0OBBYEFA0PN+PGoofZ+QIys2Jy
1Zz94vBOMB8GA1UdIwQYMBaAFA0PN+PGoofZ+QIys2Jy1Zz94vBOMAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEplethBoPpcP3EbR5Rz6snDDIcbtAJu
Ngd0YZppGT+P0DYnPJva4vRG3bb84ZMSuppz5j67qD6DdWte8UXhK8BzWiHzwmQE
QmbKyzzTMKQgTNFntpx5cgsSvTtrHpNYoMHzHOmyAOboNeM0DWiRXsYLkWTitLTN
qbOpstwPubExbT9lPjLclntShT/lCupt+zsbnrR9YiqlYFY/fDzfAybZhrD5GMBY
XdMPItwAc/sWvH31yztarjkLmld76AGCcO5r8cSR/cX98SicyfjOBbSco8GkjYNY
582gTPkKGYpStuN7GNT5tZmxvMq935HRa2XZvlAIe8ufp8EHVoYiF3c=
-----END CERTIFICATE-----