wsgi-server.py (31177B)
1 # -*- coding: utf-8 -*- 2 """ 3 asyncio-server.py 4 ~~~~~~~~~~~~~~~~~ 5 6 A fully-functional WSGI server, written using h2. Requires asyncio. 7 8 To test it, try installing httpbin from pip (``pip install httpbin``) and then 9 running the server (``python asyncio-server.py httpbin:app``). 10 11 This server does not support HTTP/1.1: it is a HTTP/2-only WSGI server. The 12 purpose of this code is to demonstrate how to integrate h2 into a more 13 complex application, and to demonstrate several principles of concurrent 14 programming. 15 16 The architecture looks like this: 17 18 +---------------------------------+ 19 | 1x HTTP/2 Server Thread | 20 | (running asyncio) | 21 +---------------------------------+ 22 +---------------------------------+ 23 | N WSGI Application Threads | 24 | (no asyncio) | 25 +---------------------------------+ 26 27 Essentially, we spin up an asyncio-based event loop in the main thread. This 28 launches one HTTP/2 Protocol instance for each inbound connection, all of which 29 will read and write data from within the main thread in an asynchronous manner. 30 31 When each HTTP request comes in, the server will build the WSGI environment 32 dictionary and create a ``Stream`` object. This object will hold the relevant 33 state for the request/response pair and will act as the WSGI side of the logic. 34 That object will then be passed to a background thread pool, and when a worker 35 is available the WSGI logic will begin to be executed. This model ensures that 36 the asyncio web server itself is never blocked by the WSGI application. 37 38 The WSGI application and the HTTP/2 server communicate via an asyncio queue, 39 together with locks and threading events. The locks themselves are implicit in 40 asyncio's "call_soon_threadsafe", which allows for a background thread to 41 register an action with the main asyncio thread. When the asyncio thread 42 eventually takes the action in question it sets as threading event, signaling 43 to the background thread that it is free to continue its work. 44 45 To make the WSGI application work with flow control, there is a very important 46 invariant that must be observed. Any WSGI action that would cause data to be 47 emitted to the network MUST be accompanied by a threading Event that is not 48 set until that data has been written to the transport. This ensures that the 49 WSGI application *blocks* until the data is actually sent. The reason we 50 require this invariant is that the HTTP/2 server may choose to re-order some 51 data chunks for flow control reasons: that is, the application for stream X may 52 have actually written its data first, but the server may elect to send the data 53 for stream Y first. This means that it's vital that there not be *two* writes 54 for stream X active at any one point or they may get reordered, which would be 55 particularly terrible. 56 57 Thus, the server must cooperate to ensure that each threading event only fires 58 when the *complete* data for that event has been written to the asyncio 59 transport. Any earlier will cause untold craziness. 60 """ 61 import asyncio 62 import importlib 63 import queue 64 import ssl 65 import sys 66 import threading 67 68 from h2.config import H2Configuration 69 from h2.connection import H2Connection 70 from h2.events import ( 71 DataReceived, RequestReceived, WindowUpdated, StreamEnded, StreamReset 72 ) 73 74 75 # Used to signal that a request has completed. 76 # 77 # This is a convenient way to do "in-band" signaling of stream completion 78 # without doing anything so heavyweight as using a class. Essentially, we can 79 # test identity against this empty object. In fact, this is so convenient that 80 # we use this object for all streams, for data in both directions: in and out. 81 END_DATA_SENTINEL = object() 82 83 # The WSGI callable. Stored here so that the protocol instances can get hold 84 # of the data. 85 APPLICATION = None 86 87 88 class H2Protocol(asyncio.Protocol): 89 def __init__(self): 90 config = H2Configuration(client_side=False, header_encoding='utf-8') 91 92 # Our server-side state machine. 93 self.conn = H2Connection(config=config) 94 95 # The backing transport. 96 self.transport = None 97 98 # A dictionary of ``Stream`` objects, keyed by their stream ID. This 99 # makes it easy to route data to the correct WSGI application instance. 100 self.streams = {} 101 102 # A queue of data emitted by WSGI applications that has not yet been 103 # sent. Each stream may only have one chunk of data in either this 104 # queue or the flow_controlled_data dictionary at any one time. 105 self._stream_data = asyncio.Queue() 106 107 # Data that has been pulled off the queue that is for a stream blocked 108 # behind flow control limitations. This is used to avoid spinning on 109 # _stream_data queue when a stream cannot have its data sent. Data that 110 # cannot be sent on the connection when it is popped off the queue gets 111 # placed here until the stream flow control window opens up again. 112 self._flow_controlled_data = {} 113 114 # A reference to the loop in which this protocol runs. This is needed 115 # to synchronise up with background threads. 116 self._loop = asyncio.get_event_loop() 117 118 # Any streams that have been remotely reset. We keep track of these to 119 # ensure that we don't emit data from a WSGI application whose stream 120 # has been cancelled. 121 self._reset_streams = set() 122 123 # Keep track of the loop sending task so we can kill it when the 124 # connection goes away. 125 self._send_loop_task = None 126 127 def connection_made(self, transport): 128 """ 129 The connection has been made. Here we need to save off our transport, 130 do basic HTTP/2 connection setup, and then start our data writing 131 coroutine. 132 """ 133 self.transport = transport 134 self.conn.initiate_connection() 135 self.transport.write(self.conn.data_to_send()) 136 self._send_loop_task = self._loop.create_task(self.sending_loop()) 137 138 def connection_lost(self, exc): 139 """ 140 With the end of the connection, we just want to cancel our data sending 141 coroutine. 142 """ 143 self._send_loop_task.cancel() 144 145 def data_received(self, data): 146 """ 147 Process inbound data. 148 """ 149 events = self.conn.receive_data(data) 150 151 for event in events: 152 if isinstance(event, RequestReceived): 153 self.request_received(event) 154 elif isinstance(event, DataReceived): 155 self.data_frame_received(event) 156 elif isinstance(event, WindowUpdated): 157 self.window_opened(event) 158 elif isinstance(event, StreamEnded): 159 self.end_stream(event) 160 elif isinstance(event, StreamReset): 161 self.reset_stream(event) 162 163 outbound_data = self.conn.data_to_send() 164 if outbound_data: 165 self.transport.write(outbound_data) 166 167 def window_opened(self, event): 168 """ 169 The flow control window got opened. 170 171 This is important because it's possible that we were unable to send 172 some WSGI data because the flow control window was too small. If that 173 happens, the sending_loop coroutine starts buffering data. 174 175 As the window gets opened, we need to unbuffer the data. We do that by 176 placing the data chunks back on the back of the send queue and letting 177 the sending loop take another shot at sending them. 178 179 This system only works because we require that each stream only have 180 *one* data chunk in the sending queue at any time. The threading events 181 force this invariant to remain true. 182 """ 183 if event.stream_id: 184 # This is specific to a single stream. 185 if event.stream_id in self._flow_controlled_data: 186 self._stream_data.put_nowait( 187 self._flow_controlled_data.pop(event.stream_id) 188 ) 189 else: 190 # This event is specific to the connection. Free up *all* the 191 # streams. This is a bit tricky, but we *must not* yield the flow 192 # of control here or it all goes wrong. 193 for data in self._flow_controlled_data.values(): 194 self._stream_data.put_nowait(data) 195 196 self._flow_controlled_data = {} 197 198 @asyncio.coroutine 199 def sending_loop(self): 200 """ 201 A call that loops forever, attempting to send data. This sending loop 202 contains most of the flow-control smarts of this class: it pulls data 203 off of the asyncio queue and then attempts to send it. 204 205 The difficulties here are all around flow control. Specifically, a 206 chunk of data may be too large to send. In this case, what will happen 207 is that this coroutine will attempt to send what it can and will then 208 store the unsent data locally. When a flow control event comes in that 209 data will be freed up and placed back onto the asyncio queue, causing 210 it to pop back up into the sending logic of this coroutine. 211 212 This method explicitly *does not* handle HTTP/2 priority. That adds an 213 extra layer of complexity to what is already a fairly complex method, 214 and we'll look at how to do it another time. 215 216 This coroutine explicitly *does not end*. 217 """ 218 while True: 219 stream_id, data, event = yield from self._stream_data.get() 220 221 # If this stream got reset, just drop the data on the floor. Note 222 # that we need to reset the event here to make sure that 223 # application doesn't lock up. 224 if stream_id in self._reset_streams: 225 event.set() 226 227 # Check if the body is done. If it is, this is really easy! Again, 228 # we *must* set the event here or the application will lock up. 229 if data is END_DATA_SENTINEL: 230 self.conn.end_stream(stream_id) 231 self.transport.write(self.conn.data_to_send()) 232 event.set() 233 continue 234 235 # We need to send data, but not to exceed the flow control window. 236 # For that reason, grab only the data that fits: we'll buffer the 237 # rest. 238 window_size = self.conn.local_flow_control_window(stream_id) 239 chunk_size = min(window_size, len(data)) 240 data_to_send = data[:chunk_size] 241 data_to_buffer = data[chunk_size:] 242 243 if data_to_send: 244 # There's a maximum frame size we have to respect. Because we 245 # aren't paying any attention to priority here, we can quite 246 # safely just split this string up into chunks of max frame 247 # size and blast them out. 248 # 249 # In a *real* application you'd want to consider priority here. 250 max_size = self.conn.max_outbound_frame_size 251 chunks = ( 252 data_to_send[x:x+max_size] 253 for x in range(0, len(data_to_send), max_size) 254 ) 255 for chunk in chunks: 256 self.conn.send_data(stream_id, chunk) 257 self.transport.write(self.conn.data_to_send()) 258 259 # If there's data left to buffer, we should do that. Put it in a 260 # dictionary and *don't set the event*: the app must not generate 261 # any more data until we got rid of all of this data. 262 if data_to_buffer: 263 self._flow_controlled_data[stream_id] = ( 264 stream_id, data_to_buffer, event 265 ) 266 else: 267 # We sent everything. We can let the WSGI app progress. 268 event.set() 269 270 def request_received(self, event): 271 """ 272 A HTTP/2 request has been received. We need to invoke the WSGI 273 application in a background thread to handle it. 274 """ 275 # First, we are going to want an object to hold all the relevant state 276 # for this request/response. For that, we have a stream object. We 277 # need to store the stream object somewhere reachable for when data 278 # arrives later. 279 s = Stream(event.stream_id, self) 280 self.streams[event.stream_id] = s 281 282 # Next, we need to build the WSGI environ dictionary. 283 environ = _build_environ_dict(event.headers, s) 284 285 # Finally, we want to throw these arguments out to a threadpool and 286 # let it run. 287 self._loop.run_in_executor( 288 None, 289 s.run_in_threadpool, 290 APPLICATION, 291 environ, 292 ) 293 294 def data_frame_received(self, event): 295 """ 296 Data has been received by WSGI server and needs to be dispatched to a 297 running application. 298 299 Note that the flow control window is not modified here. That's 300 deliberate: see Stream.__next__ for a longer discussion of why. 301 """ 302 # Grab the stream in question from our dictionary and pass it on. 303 stream = self.streams[event.stream_id] 304 stream.receive_data(event.data, event.flow_controlled_length) 305 306 def end_stream(self, event): 307 """ 308 The stream data is complete. 309 """ 310 stream = self.streams[event.stream_id] 311 stream.request_complete() 312 313 def reset_stream(self, event): 314 """ 315 A stream got forcefully reset. 316 317 This is a tricky thing to deal with because WSGI doesn't really have a 318 good notion for it. Essentially, you have to let the application run 319 until completion, but not actually let it send any data. 320 321 We do that by discarding any data we currently have for it, and then 322 marking the stream as reset to allow us to spot when that stream is 323 trying to send data and drop that data on the floor. 324 325 We then *also* signal the WSGI application that no more data is 326 incoming, to ensure that it does not attempt to do further reads of the 327 data. 328 """ 329 if event.stream_id in self._flow_controlled_data: 330 del self._flow_controlled_data 331 332 self._reset_streams.add(event.stream_id) 333 self.end_stream(event) 334 335 def data_for_stream(self, stream_id, data): 336 """ 337 Thread-safe method called from outside the main asyncio thread in order 338 to send data on behalf of a WSGI application. 339 340 Places data being written by a stream on an asyncio queue. Returns a 341 threading event that will fire when that data is sent. 342 """ 343 event = threading.Event() 344 self._loop.call_soon_threadsafe( 345 self._stream_data.put_nowait, 346 (stream_id, data, event) 347 ) 348 return event 349 350 def send_response(self, stream_id, headers): 351 """ 352 Thread-safe method called from outside the main asyncio thread in order 353 to send the HTTP response headers on behalf of a WSGI application. 354 355 Returns a threading event that will fire when the headers have been 356 emitted to the network. 357 """ 358 event = threading.Event() 359 360 def _inner_send(stream_id, headers, event): 361 self.conn.send_headers(stream_id, headers, end_stream=False) 362 self.transport.write(self.conn.data_to_send()) 363 event.set() 364 365 self._loop.call_soon_threadsafe( 366 _inner_send, 367 stream_id, 368 headers, 369 event 370 ) 371 return event 372 373 def open_flow_control_window(self, stream_id, increment): 374 """ 375 Opens a flow control window for the given stream by the given amount. 376 Called from a WSGI thread. Does not return an event because there's no 377 need to block on this action, it may take place at any time. 378 """ 379 def _inner_open(stream_id, increment): 380 self.conn.increment_flow_control_window(increment, stream_id) 381 self.conn.increment_flow_control_window(increment, None) 382 self.transport.write(self.conn.data_to_send()) 383 384 self._loop.call_soon_threadsafe( 385 _inner_open, 386 stream_id, 387 increment, 388 ) 389 390 391 class Stream: 392 """ 393 This class holds all of the state for a single stream. It also provides 394 several of the callables used by the WSGI application. Finally, it provides 395 the logic for actually interfacing with the WSGI application. 396 397 For these reasons, the object has *strict* requirements on thread-safety. 398 While the object can be initialized in the main WSGI thread, the 399 ``run_in_threadpool`` method *must* be called from outside that thread. At 400 that point, the main WSGI thread may only call specific methods. 401 """ 402 def __init__(self, stream_id, protocol): 403 self.stream_id = stream_id 404 self._protocol = protocol 405 406 # Queue for data that has been received from the network. This is a 407 # thread-safe queue, to allow both the WSGI application to block on 408 # receiving more data and to allow the asyncio server to keep sending 409 # more data. 410 # 411 # This queue is unbounded in size, but in practice it cannot contain 412 # too much data because the flow control window doesn't get adjusted 413 # unless data is removed from it. 414 self._received_data = queue.Queue() 415 416 # This buffer is used to hold partial chunks of data from 417 # _received_data that were not returned out of ``read`` and friends. 418 self._temp_buffer = b'' 419 420 # Temporary variables that allow us to keep hold of the headers and 421 # response status until such time as the application needs us to send 422 # them. 423 self._response_status = b'' 424 self._response_headers = [] 425 self._headers_emitted = False 426 427 # Whether the application has received all the data from the network 428 # or not. This allows us to short-circuit some reads. 429 self._complete = False 430 431 def receive_data(self, data, flow_controlled_size): 432 """ 433 Called by the H2Protocol when more data has been received from the 434 network. 435 436 Places the data directly on the queue in a thread-safe manner without 437 blocking. Does not introspect or process the data. 438 """ 439 self._received_data.put_nowait((data, flow_controlled_size)) 440 441 def request_complete(self): 442 """ 443 Called by the H2Protocol when all the request data has been received. 444 445 This works by placing the ``END_DATA_SENTINEL`` on the queue. The 446 reading code knows, when it sees the ``END_DATA_SENTINEL``, to expect 447 no more data from the network. This ensures that the state of the 448 application only changes when it has finished processing the data from 449 the network, even though the server may have long-since finished 450 receiving all the data for this request. 451 """ 452 self._received_data.put_nowait((END_DATA_SENTINEL, None)) 453 454 def run_in_threadpool(self, wsgi_application, environ): 455 """ 456 This method should be invoked in a threadpool. At the point this method 457 is invoked, the only safe methods to call from the original thread are 458 ``receive_data`` and ``request_complete``: any other method is unsafe. 459 460 This method handles the WSGI logic. It invokes the application callable 461 in this thread, passing control over to the WSGI application. It then 462 ensures that the data makes it back to the HTTP/2 connection via 463 the thread-safe APIs provided below. 464 """ 465 result = wsgi_application(environ, self.start_response) 466 467 try: 468 for data in result: 469 self.write(data) 470 finally: 471 # This signals that we're done with data. The server will know that 472 # this allows it to clean up its state: we're done here. 473 self.write(END_DATA_SENTINEL) 474 475 # The next few methods are called by the WSGI application. Firstly, the 476 # three methods provided by the input stream. 477 def read(self, size=None): 478 """ 479 Called by the WSGI application to read data. 480 481 This method is the one of two that explicitly pumps the input data 482 queue, which means it deals with the ``_complete`` flag and the 483 ``END_DATA_SENTINEL``. 484 """ 485 # If we've already seen the END_DATA_SENTINEL, return immediately. 486 if self._complete: 487 return b'' 488 489 # If we've been asked to read everything, just iterate over ourselves. 490 if size is None: 491 return b''.join(self) 492 493 # Otherwise, as long as we don't have enough data, spin looking for 494 # another data chunk. 495 data = b'' 496 while len(data) < size: 497 try: 498 chunk = next(self) 499 except StopIteration: 500 break 501 502 # Concatenating strings this way is slow, but that's ok, this is 503 # just a demo. 504 data += chunk 505 506 # We have *at least* enough data to return, but we may have too much. 507 # If we do, throw it on a buffer: we'll use it later. 508 to_return = data[:size] 509 self._temp_buffer = data[size:] 510 return to_return 511 512 def readline(self, hint=None): 513 """ 514 Called by the WSGI application to read a single line of data. 515 516 This method rigorously observes the ``hint`` parameter: it will only 517 ever read that much data. It then splits the data on a newline 518 character and throws everything it doesn't need into a buffer. 519 """ 520 data = self.read(hint) 521 first_newline = data.find(b'\n') 522 if first_newline == -1: 523 # No newline, return all the data 524 return data 525 526 # We want to slice the data so that the head *includes* the first 527 # newline. Then, any data left in this line we don't care about should 528 # be prepended to the internal buffer. 529 head, tail = data[:first_newline + 1], data[first_newline + 1:] 530 self._temp_buffer = tail + self._temp_buffer 531 532 return head 533 534 def readlines(self, hint=None): 535 """ 536 Called by the WSGI application to read several lines of data. 537 538 This method is really pretty stupid. It rigorously observes the 539 ``hint`` parameter, and quite happily returns the input split into 540 lines. 541 """ 542 # This method is *crazy inefficient*, but it's also a pretty stupid 543 # method to call. 544 data = self.read(hint) 545 lines = data.split(b'\n') 546 547 # Split removes the newline character, but we want it, so put it back. 548 lines = [line + b'\n' for line in lines] 549 550 # Except if the last character was a newline character we now have an 551 # extra line that is just a newline: pull that out. 552 if lines[-1] == b'\n': 553 lines = lines[:-1] 554 return lines 555 556 def start_response(self, status, response_headers, exc_info=None): 557 """ 558 This is the PEP-3333 mandated start_response callable. 559 560 All it does is store the headers for later sending, and return our 561 ```write`` callable. 562 """ 563 if self._headers_emitted and exc_info is not None: 564 raise exc_info[1].with_traceback(exc_info[2]) 565 566 assert not self._response_status or exc_info is not None 567 self._response_status = status 568 self._response_headers = response_headers 569 570 return self.write 571 572 def write(self, data): 573 """ 574 Provides some data to write. 575 576 This function *blocks* until such time as the data is allowed by 577 HTTP/2 flow control. This allows a client to slow or pause the response 578 as needed. 579 580 This function is not supposed to be used, according to PEP-3333, but 581 once we have it it becomes quite convenient to use it, so this app 582 actually runs all writes through this function. 583 """ 584 if not self._headers_emitted: 585 self._emit_headers() 586 event = self._protocol.data_for_stream(self.stream_id, data) 587 event.wait() 588 return 589 590 def _emit_headers(self): 591 """ 592 Sends the response headers. 593 594 This is only called from the write callable and should only ever be 595 called once. It does some minor processing (converts the status line 596 into a status code because reason phrases are evil) and then passes 597 the headers on to the server. This call explicitly blocks until the 598 server notifies us that the headers have reached the network. 599 """ 600 assert self._response_status and self._response_headers 601 assert not self._headers_emitted 602 self._headers_emitted = True 603 604 # We only need the status code 605 status = self._response_status.split(" ", 1)[0] 606 headers = [(":status", status)] 607 headers.extend(self._response_headers) 608 event = self._protocol.send_response(self.stream_id, headers) 609 event.wait() 610 return 611 612 # These two methods implement the iterator protocol. This allows a WSGI 613 # application to iterate over this Stream object to get the data. 614 def __iter__(self): 615 return self 616 617 def __next__(self): 618 # If the complete request has been read, abort immediately. 619 if self._complete: 620 raise StopIteration() 621 622 # If we have data stored in a temporary buffer for any reason, return 623 # that and clear the buffer. 624 # 625 # This can actually only happen when the application uses one of the 626 # read* callables, but that's fine. 627 if self._temp_buffer: 628 buffered_data = self._temp_buffer 629 self._temp_buffer = b'' 630 return buffered_data 631 632 # Otherwise, pull data off the queue (blocking as needed). If this is 633 # the end of the request, we're done here: mark ourselves as complete 634 # and call it time. Otherwise, open the flow control window an 635 # appropriate amount and hand the chunk off. 636 chunk, chunk_size = self._received_data.get() 637 if chunk is END_DATA_SENTINEL: 638 self._complete = True 639 raise StopIteration() 640 641 # Let's talk a little bit about why we're opening the flow control 642 # window *here*, and not in the server thread. 643 # 644 # The purpose of HTTP/2 flow control is to allow for servers and 645 # clients to avoid needing to buffer data indefinitely because their 646 # peer is producing data faster than they can consume it. As a result, 647 # it's important that the flow control window be opened as late in the 648 # processing as possible. In this case, we open the flow control window 649 # exactly when the server hands the data to the application. This means 650 # that the flow control window essentially signals to the remote peer 651 # how much data hasn't even been *seen* by the application yet. 652 # 653 # If you wanted to be really clever you could consider not opening the 654 # flow control window until the application asks for the *next* chunk 655 # of data. That means that any buffers at the application level are now 656 # included in the flow control window processing. In my opinion, the 657 # advantage of that process does not outweigh the extra logical 658 # complexity involved in doing it, so we don't bother here. 659 # 660 # Another note: you'll notice that we don't include the _temp_buffer in 661 # our flow control considerations. This means you could in principle 662 # lead us to buffer slightly more than one connection flow control 663 # window's worth of data. That risk is considered acceptable for the 664 # much simpler logic available here. 665 # 666 # Finally, this is a pretty dumb flow control window management scheme: 667 # it causes us to emit a *lot* of window updates. A smarter server 668 # would want to use the content-length header to determine whether 669 # flow control window updates need to be emitted at all, and then to be 670 # more efficient about emitting them to avoid firing them off really 671 # frequently. For an example like this, there's very little gained by 672 # worrying about that. 673 self._protocol.open_flow_control_window(self.stream_id, chunk_size) 674 675 return chunk 676 677 678 def _build_environ_dict(headers, stream): 679 """ 680 Build the WSGI environ dictionary for a given request. To do that, we'll 681 temporarily create a dictionary for the headers. While this isn't actually 682 a valid way to represent headers, we know that the special headers we need 683 can only have one appearance in the block. 684 685 This code is arguably somewhat incautious: the conversion to dictionary 686 should only happen in a way that allows us to correctly join headers that 687 appear multiple times. That's acceptable in a demo app: in a productised 688 version you'd want to fix it. 689 """ 690 header_dict = dict(headers) 691 path = header_dict.pop(u':path') 692 try: 693 path, query = path.split(u'?', 1) 694 except ValueError: 695 query = u"" 696 server_name = header_dict.pop(u':authority') 697 try: 698 server_name, port = server_name.split(u':', 1) 699 except ValueError as e: 700 port = "8443" 701 702 environ = { 703 u'REQUEST_METHOD': header_dict.pop(u':method'), 704 u'SCRIPT_NAME': u'', 705 u'PATH_INFO': path, 706 u'QUERY_STRING': query, 707 u'SERVER_NAME': server_name, 708 u'SERVER_PORT': port, 709 u'SERVER_PROTOCOL': u'HTTP/2', 710 u'HTTPS': u"on", 711 u'SSL_PROTOCOL': u'TLSv1.2', 712 u'wsgi.version': (1, 0), 713 u'wsgi.url_scheme': header_dict.pop(u':scheme'), 714 u'wsgi.input': stream, 715 u'wsgi.errors': sys.stderr, 716 u'wsgi.multithread': True, 717 u'wsgi.multiprocess': False, 718 u'wsgi.run_once': False, 719 } 720 if u'content-type' in header_dict: 721 environ[u'CONTENT_TYPE'] = header_dict[u'content-type'] 722 if u'content-length' in header_dict: 723 environ[u'CONTENT_LENGTH'] = header_dict[u'content-length'] 724 for name, value in header_dict.items(): 725 environ[u'HTTP_' + name.upper()] = value 726 return environ 727 728 729 # Set up the WSGI app. 730 application_string = sys.argv[1] 731 path, func = application_string.split(':', 1) 732 module = importlib.import_module(path) 733 APPLICATION = getattr(module, func) 734 735 # Set up TLS 736 ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) 737 ssl_context.options |= ( 738 ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_COMPRESSION 739 ) 740 ssl_context.set_ciphers("ECDHE+AESGCM") 741 ssl_context.load_cert_chain(certfile="cert.crt", keyfile="cert.key") 742 ssl_context.set_alpn_protocols(["h2"]) 743 744 # Do the asnycio bits 745 loop = asyncio.get_event_loop() 746 # Each client connection will create a new protocol instance 747 coro = loop.create_server(H2Protocol, '127.0.0.1', 8443, ssl=ssl_context) 748 server = loop.run_until_complete(coro) 749 750 # Serve requests until Ctrl+C is pressed 751 print('Serving on {}'.format(server.sockets[0].getsockname())) 752 try: 753 loop.run_forever() 754 except KeyboardInterrupt: 755 pass 756 757 # Close the server 758 server.close() 759 loop.run_until_complete(server.wait_closed()) 760 loop.close()