1Getting Started: Writing Your Own HTTP/2 Server
2===============================================
3
4This document explains how to get started writing fully-fledged HTTP/2
5implementations using Hyper-h2 as the underlying protocol stack. It covers the
6basic concepts you need to understand, and talks you through writing a very
7simple HTTP/2 server.
8
9This document assumes you're moderately familiar with writing Python, and have
10*some* understanding of how computer networks work. If you don't, you'll find
11it a lot easier if you get some understanding of those concepts first and then
12return to this documentation.
13
14
15.. _h2-connection-basic:
16
17Connections
18-----------
19
20Hyper-h2's core object is the
21:class:`H2Connection <h2.connection.H2Connection>` object. This object is an
22abstract representation of the state of a single HTTP/2 connection, and holds
23all the important protocol state. When using Hyper-h2, this object will be the
24first thing you create and the object that does most of the heavy lifting.
25
26The interface to this object is relatively simple. For sending data, you
27call the object with methods indicating what actions you want to perform: for
28example, you may want to send headers (you'd use the
29:meth:`send_headers <h2.connection.H2Connection.send_headers>` method), or
30send data (you'd use the
31:meth:`send_data <h2.connection.H2Connection.send_data>` method). After you've
32decided what actions you want to perform, you get some bytes out of the object
33that represent the HTTP/2-encoded representation of your actions, and send them
34out over the network however you see fit.
35
36When you receive data from the network, you pass that data in to the
37``H2Connection`` object, which returns a list of *events*.
38These events, covered in more detail later in :ref:`h2-events-basic`, define
39the set of actions the remote peer has performed on the connection, as
40represented by the HTTP/2-encoded data you just passed to the object.
41
42Thus, you end up with a simple loop (which you may recognise as a more-specific
43form of an `event loop`_):
44
45    1. First, you perform some actions.
46    2. You send the data created by performing those actions to the network.
47    3. You read data from the network.
48    4. You decode those into events.
49    5. The events cause you to trigger some actions: go back to step 1.
50
51Of course, HTTP/2 is more complex than that, but in the very simplest case you
52can write a fairly effective HTTP/2 tool using just that kind of loop. Later in
53this document, we'll do just that.
54
55Some important subtleties of ``H2Connection`` objects are covered in
56:doc:`advanced-usage`: see :ref:`h2-connection-advanced` for more information.
57However, one subtlety should be covered, and that is this: Hyper-h2's
58``H2Connection`` object doesn't do I/O. Let's talk briefly about why.
59
60I/O
61~~~
62
63Any useful HTTP/2 tool eventually needs to do I/O. This is because it's not
64very useful to be able to speak to other computers using a protocol like HTTP/2
65unless you actually *speak* to them sometimes.
66
67However, doing I/O is not a trivial thing: there are lots of different ways to
68do it, and once you choose a way to do it your code usually won't work well
69with the approaches you *didn't* choose.
70
71While there are lots of different ways to do I/O, when it comes down to it
72all HTTP/2 implementations transform bytes received into events, and events
73into bytes to send. So there's no reason to have lots of different versions of
74this core protocol code: one for Twisted, one for gevent, one for threading,
75and one for synchronous code.
76
77This is why we said at the top that Hyper-h2 is a *HTTP/2 Protocol Stack*, not
78a *fully-fledged implementation*. Hyper-h2 knows how to transform bytes into
79events and back, but that's it. The I/O and smarts might be different, but
80the core HTTP/2 logic is the same: that's what Hyper-h2 provides.
81
82Not doing I/O makes Hyper-h2 general, and also relatively simple. It has an
83easy-to-understand performance envelope, it's easy to test (and as a result
84easy to get correct behaviour out of), and it behaves in a reproducible way.
85These are all great traits to have in a library that is doing something quite
86complex.
87
88This document will talk you through how to build a relatively simple HTTP/2
89implementation using Hyper-h2, to give you an understanding of where it fits in
90your software.
91
92
93.. _h2-events-basic:
94
95Events
96------
97
98When writing a HTTP/2 implementation it's important to know what the remote
99peer is doing: if you didn't care, writing networked programs would be a lot
100easier!
101
102Hyper-h2 encodes the actions of the remote peer in the form of *events*. When
103you receive data from the remote peer and pass it into your ``H2Connection``
104object (see :ref:`h2-connection-basic`), the ``H2Connection`` returns a list
105of objects, each one representing a single event that has occurred. Each
106event refers to a single action the remote peer has taken.
107
108Some events are fairly high-level, referring to things that are more general
109than HTTP/2: for example, the
110:class:`RequestReceived <h2.events.RequestReceived>` event is a general HTTP
111concept, not just a HTTP/2 one. Other events are extremely HTTP/2-specific:
112for example, :class:`PushedStreamReceived <h2.events.PushedStreamReceived>`
113refers to Server Push, a very HTTP/2-specific concept.
114
115The reason these events exist is that Hyper-h2 is intended to be very general.
116This means that, in many cases, Hyper-h2 does not know exactly what to do in
117response to an event. Your code will need to handle these events, and make
118decisions about what to do. That's the major role of any HTTP/2 implementation
119built on top of Hyper-h2.
120
121A full list of events is available in :ref:`h2-events-api`. For the purposes
122of this example, we will handle only a small set of events.
123
124
125Writing Your Server
126-------------------
127
128Armed with the knowledge you just obtained, we're going to write a very simple
129HTTP/2 web server. The goal of this server is to write a server that can handle
130a HTTP GET, and that returns the headers sent by the client, encoded in JSON.
131Basically, something a lot like `httpbin.org/get`_. Nothing fancy, but this is
132a good way to get a handle on how you should interact with Hyper-h2.
133
134For the sake of simplicity, we're going to write this using the Python standard
135library, in Python 3. In reality, you'll probably want to use an asynchronous
136framework of some kind: see the `examples directory`_ in the repository for
137some examples of how you'd do that.
138
139Before we start, create a new file called ``h2server.py``: we'll use that as
140our workspace. Additionally, you should install Hyper-h2: follow the
141instructions in :doc:`installation`.
142
143Step 1: Sockets
144~~~~~~~~~~~~~~~
145
146To begin with, we need to make sure we can listen for incoming data and send it
147back. To do that, we need to use the `standard library's socket module`_. For
148now we're going to skip doing TLS: if you want to reach your server from your
149web browser, though, you'll need to add TLS and some other function. Consider
150looking at our examples in our `examples directory`_ instead.
151
152Let's begin. First, open up ``h2server.py``. We need to import the socket
153module and start listening for connections.
154
155This is not a socket tutorial, so we're not going to dive too deeply into how
156this works. If you want more detail about sockets, there are lots of good
157tutorials on the web that you should investigate.
158
159When you want to listen for incoming connections, the you need to *bind* an
160address first. So let's do that. Try setting up your file to look like this:
161
162.. code-block:: python
163
164    import socket
165
166    sock = socket.socket()
167    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
168    sock.bind(('0.0.0.0', 8080))
169    sock.listen(5)
170
171    while True:
172        print(sock.accept())
173
174In a shell window, execute this program (``python h2server.py``). Then, open
175another shell and run ``curl http://localhost:8080/``. In the first shell, you
176should see something like this:
177
178.. code-block:: console
179
180    $ python h2server.py
181    (<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 58800)>, ('127.0.0.1', 58800))
182
183Run that ``curl`` command a few more times. You should see a few more similar
184lines appear. Note that the ``curl`` command itself will exit with an error.
185That's fine: it happens because we didn't send any data.
186
187Now go ahead and stop the server running by hitting Ctrl+C in the first shell.
188You should see a ``KeyboardInterrupt`` error take the process down.
189
190What's the program above doing? Well, first it creates a
191:func:`socket <python:socket.socket>` object. This socket is then *bound* to
192a specific address: ``('0.0.0.0', 8080)``. This is a special address: it means
193that this socket should be listening for any traffic to TCP port 8080. Don't
194worry about the call to ``setsockopt``: it just makes sure you can run this
195program repeatedly.
196
197We then loop forever calling the :meth:`accept <python:socket.socket.accept>`
198method on the socket. The accept method blocks until someone attempts to
199connect to our TCP port: when they do, it returns a tuple: the first element is
200a new socket object, the second element is a tuple of the address the new
201connection is from. You can see this in the output from our ``h2server.py``
202script.
203
204At this point, we have a script that can accept inbound connections. This is a
205good start! Let's start getting HTTP/2 involved.
206
207
208Step 2: Add a H2Connection
209~~~~~~~~~~~~~~~~~~~~~~~~~~
210
211Now that we can listen for socket information, we want to prepare our HTTP/2
212connection object and start handing it data. For now, let's just see what
213happens as we feed it data.
214
215To make HTTP/2 connections, we need a tool that knows how to speak HTTP/2.
216Most versions of curl in the wild don't, so let's install a Python tool. In
217your Python environment, run ``pip install hyper``. This will install a Python
218command-line HTTP/2 tool called ``hyper``. To confirm that it works, try
219running this command and verifying that the output looks similar to the one
220shown below:
221
222.. code-block:: console
223
224    $ hyper GET https://nghttp2.org/httpbin/get
225    {'args': {},
226     'headers': {'Host': 'nghttp2.org'},
227     'origin': '10.0.0.2',
228     'url': 'https://nghttp2.org/httpbin/get'}
229
230Assuming it works, you're now ready to start sending HTTP/2 data.
231
232Back in our ``h2server.py`` script, we're going to want to start handling data.
233Let's add a function that takes a socket returned from ``accept``, and reads
234data from it. Let's call that function ``handle``. That function should create
235a :class:`H2Connection <h2.connection.H2Connection>` object and then loop on
236the socket, reading data and passing it to the connection.
237
238To read data from a socket we need to call ``recv``. The ``recv`` function
239takes a number as its argument, which is the *maximum* amount of data to be
240returned from a single call (note that ``recv`` will return as soon as any data
241is available, even if that amount is vastly less than the number you passed to
242it). For the purposes of writing this kind of software the specific value is
243not enormously useful, but should not be overly large. For that reason, when
244you're unsure, a number like 4096 or 65535 is a good bet. We'll use 65535 for
245this example.
246
247The function should look something like this:
248
249.. code-block:: python
250
251    import h2.connection
252    import h2.config
253
254    def handle(sock):
255        config = h2.config.H2Configuration(client_side=False)
256        conn = h2.connection.H2Connection(config=config)
257
258        while True:
259            data = sock.recv(65535)
260            print(conn.receive_data(data))
261
262Let's update our main loop so that it passes data on to our new data handling
263function. Your ``h2server.py`` should end up looking a like this:
264
265.. code-block:: python
266
267    import socket
268
269    import h2.connection
270    import h2.config
271
272    def handle(sock):
273        config = h2.config.H2Configuration(client_side=False)
274        conn = h2.connection.H2Connection(config=config)
275
276        while True:
277            data = sock.recv(65535)
278            if not data:
279                break
280
281            print(conn.receive_data(data))
282
283
284    sock = socket.socket()
285    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
286    sock.bind(('0.0.0.0', 8080))
287    sock.listen(5)
288
289    while True:
290        handle(sock.accept()[0])
291
292Running that in one shell, in your other shell you can run
293``hyper --h2 GET http://localhost:8080/``. That shell should hang, and you
294should then see the following output from your ``h2server.py`` shell:
295
296.. code-block:: console
297
298    $ python h2server.py
299    [<h2.events.RemoteSettingsChanged object at 0x10c4ee390>]
300
301You'll then need to kill ``hyper`` and ``h2server.py`` with Ctrl+C. Feel free
302to do this a few times, to see how things behave.
303
304So, what did we see here? When the connection was opened, we used the
305:meth:`recv <python:socket.socket.recv>` method to read some data from the
306socket, in a loop. We then passed that data to the connection object, which
307returned us a single event object:
308:class:`RemoteSettingsChanged <h2.events.RemoteSettingsChanged>`.
309
310But what we didn't see was anything else. So it seems like all ``hyper`` did
311was change its settings, but nothing else. If you look at the other ``hyper``
312window, you'll notice that it hangs for a while and then eventually fails with
313a socket timeout. It was waiting for something: what?
314
315Well, it turns out that at the start of a connection, both sides need to send
316a bit of data, called "the HTTP/2 preamble". We don't need to get into too much
317detail here, but basically both sides need to send a single block of HTTP/2
318data that tells the other side what their settings are. ``hyper`` did that,
319but we didn't.
320
321Let's do that next.
322
323
324Step 3: Sending the Preamble
325~~~~~~~~~~~~~~~~~~~~~~~~~~~~
326
327Hyper-h2 makes doing connection setup really easy. All you need to do is call
328the
329:meth:`initiate_connection <h2.connection.H2Connection.initiate_connection>`
330method, and then send the corresponding data. Let's update our ``handle``
331function to do just that:
332
333.. code-block:: python
334
335    def handle(sock):
336        config = h2.config.H2Configuration(client_side=False)
337        conn = h2.connection.H2Connection(config=config)
338        conn.initiate_connection()
339        sock.sendall(conn.data_to_send())
340
341        while True:
342            data = sock.recv(65535)
343            print(conn.receive_data(data))
344
345
346The big change here is the call to ``initiate_connection``, but there's another
347new method in there:
348:meth:`data_to_send <h2.connection.H2Connection.data_to_send>`.
349
350When you make function calls on your ``H2Connection`` object, these will often
351want to cause HTTP/2 data to be written out to the network. But Hyper-h2
352doesn't do any I/O, so it can't do that itself. Instead, it writes it to an
353internal buffer. You can retrieve data from this buffer using the
354``data_to_send`` method. There are some subtleties about that method, but we
355don't need to worry about them right now: all we need to do is make sure we're
356sending whatever data is outstanding.
357
358Your ``h2server.py`` script should now look like this:
359
360.. code-block:: python
361
362    import socket
363
364    import h2.connection
365    import h2.config
366
367    def handle(sock):
368        config = h2.config.H2Configuration(client_side=False)
369        conn = h2.connection.H2Connection(config=config)
370        conn.initiate_connection()
371        sock.sendall(conn.data_to_send())
372
373        while True:
374            data = sock.recv(65535)
375            if not data:
376                break
377
378            print(conn.receive_data(data))
379
380
381    sock = socket.socket()
382    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
383    sock.bind(('0.0.0.0', 8080))
384    sock.listen(5)
385
386    while True:
387        handle(sock.accept()[0])
388
389
390With this change made, rerun your ``h2server.py`` script and hit it with the
391same ``hyper`` command: ``hyper --h2 GET http://localhost:8080/``. The
392``hyper`` command still hangs, but this time we get a bit more output from our
393``h2server.py`` script:
394
395.. code-block:: console
396
397    $ python h2server.py
398    [<h2.events.RemoteSettingsChanged object at 0x10292d390>]
399    [<h2.events.SettingsAcknowledged object at 0x102b3a160>]
400    [<h2.events.RequestReceived object at 0x102b3a3c8>, <h2.events.StreamEnded object at 0x102b3a400>]
401
402So, what's happening?
403
404The first thing to note is that we're going around our loop more than once now.
405First, we receive some data that triggers a
406:class:`RemoteSettingsChanged <h2.events.RemoteSettingsChanged>` event.
407Then, we get some more data that triggers a
408:class:`SettingsAcknowledged <h2.events.SettingsAcknowledged>` event.
409Finally, even more data that triggers *two* events:
410:class:`RequestReceived <h2.events.RequestReceived>` and
411:class:`StreamEnded <h2.events.StreamEnded>`.
412
413So, what's happening is that ``hyper`` is telling us about its settings,
414acknowledging ours, and then sending us a request. Then it ends a *stream*,
415which is a HTTP/2 communications channel that holds a request and response
416pair.
417
418A stream isn't done until it's either *reset* or both sides *close* it:
419in this sense it's bi-directional. So what the ``StreamEnded`` event tells us
420is that ``hyper`` is closing its half of the stream: it won't send us any more
421data on that stream. That means the request is done.
422
423So why is ``hyper`` hanging? Well, we haven't sent a response yet: let's do
424that.
425
426
427Step 4: Handling Events
428~~~~~~~~~~~~~~~~~~~~~~~
429
430What we want to do is send a response when we receive a request. Happily, we
431get an event when we receive a request, so we can use that to be our signal.
432
433Let's define a new function that sends a response. For now, this response can
434just be a little bit of data that prints "it works!".
435
436The function should take the ``H2Connection`` object, and the event that
437signaled the request. Let's define it.
438
439.. code-block:: python
440
441    def send_response(conn, event):
442        stream_id = event.stream_id
443        conn.send_headers(
444            stream_id=stream_id,
445            headers=[
446                (':status', '200'),
447                ('server', 'basic-h2-server/1.0')
448            ],
449        )
450        conn.send_data(
451            stream_id=stream_id,
452            data=b'it works!',
453            end_stream=True
454        )
455
456So while this is only a short function, there's quite a lot going on here we
457need to unpack. Firstly, what's a stream ID? Earlier we discussed streams
458briefly, to say that they're a bi-directional communications channel that holds
459a request and response pair. Part of what makes HTTP/2 great is that there can
460be lots of streams going on at once, sending and receiving different requests
461and responses. To identify each stream, we use a *stream ID*. These are unique
462across the lifetime of a connection, and they go in ascending order.
463
464Most ``H2Connection`` functions take a stream ID: they require you to actively
465tell the connection which one to use. In this case, as a simple server, we will
466never need to choose a stream ID ourselves: the client will always choose one
467for us. That means we'll always be able to get the one we need off the events
468that fire.
469
470Next, we send some *headers*. In HTTP/2, a response is made up of some set of
471headers, and optionally some data. The headers have to come first: if you're a
472client then you'll be sending *request* headers, but in our case these headers
473are our *response* headers.
474
475Mostly these aren't very exciting, but you'll notice once special header in
476there: ``:status``. This is a HTTP/2-specific header, and it's used to hold the
477HTTP status code that used to go at the top of a HTTP response. Here, we're
478saying the response is ``200 OK``, which is successful.
479
480To send headers in Hyper-h2, you use the
481:meth:`send_headers <h2.connection.H2Connection.send_headers>` function.
482
483Next, we want to send the body data. To do that, we use the
484:meth:`send_data <h2.connection.H2Connection.send_data>` function. This also
485takes a stream ID. Note that the data is binary: Hyper-h2 does not work with
486unicode strings, so you *must* pass bytestrings to the ``H2Connection``. The
487one exception is headers: Hyper-h2 will automatically encode those into UTF-8.
488
489The last thing to note is that on our call to ``send_data``, we set
490``end_stream`` to ``True``. This tells Hyper-h2 (and the remote peer) that
491we're done with sending data: the response is over. Because we know that
492``hyper`` will have ended its side of the stream, when we end ours the stream
493will be totally done with.
494
495We're nearly ready to go with this: we just need to plumb this function in.
496Let's amend our ``handle`` function again:
497
498.. code-block:: python
499
500    import h2.events
501    import h2.config
502
503    def handle(sock):
504        config = h2.config.H2Configuration(client_side=False)
505        conn = h2.connection.H2Connection(config=config)
506        conn.initiate_connection()
507        sock.sendall(conn.data_to_send())
508
509        while True:
510            data = sock.recv(65535)
511            if not data:
512                break
513
514            events = conn.receive_data(data)
515            for event in events:
516                if isinstance(event, h2.events.RequestReceived):
517                    send_response(conn, event)
518
519            data_to_send = conn.data_to_send()
520            if data_to_send:
521                sock.sendall(data_to_send)
522
523The changes here are all at the end. Now, when we receive some events, we
524look through them for the ``RequestReceived`` event. If we find it, we make
525sure we send a response.
526
527Then, at the bottom of the loop we check whether we have any data to send, and
528if we do, we send it. Then, we repeat again.
529
530With these changes, your ``h2server.py`` file should look like this:
531
532.. code-block:: python
533
534    import socket
535
536    import h2.connection
537    import h2.events
538    import h2.config
539
540    def send_response(conn, event):
541        stream_id = event.stream_id
542        conn.send_headers(
543            stream_id=stream_id,
544            headers=[
545                (':status', '200'),
546                ('server', 'basic-h2-server/1.0')
547            ],
548        )
549        conn.send_data(
550            stream_id=stream_id,
551            data=b'it works!',
552            end_stream=True
553        )
554
555    def handle(sock):
556        config = h2.config.H2Configuration(client_side=False)
557        conn = h2.connection.H2Connection(config=config)
558        conn.initiate_connection()
559        sock.sendall(conn.data_to_send())
560
561        while True:
562            data = sock.recv(65535)
563            if not data:
564                break
565
566            events = conn.receive_data(data)
567            for event in events:
568                if isinstance(event, h2.events.RequestReceived):
569                    send_response(conn, event)
570
571            data_to_send = conn.data_to_send()
572            if data_to_send:
573                sock.sendall(data_to_send)
574
575
576    sock = socket.socket()
577    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
578    sock.bind(('0.0.0.0', 8080))
579    sock.listen(5)
580
581    while True:
582        handle(sock.accept()[0])
583
584Alright. Let's run this, and then run our ``hyper`` command again.
585
586This time, nothing is printed from our server, and the ``hyper`` side prints
587``it works!``. Success! Try running it a few more times, and we can see that
588not only does it work the first time, it works the other times too!
589
590We can speak HTTP/2! Let's add the final step: returning the JSON-encoded
591request headers.
592
593Step 5: Returning Headers
594~~~~~~~~~~~~~~~~~~~~~~~~~
595
596If we want to return the request headers in JSON, the first thing we have to do
597is find them. Handily, if you check the documentation for
598:class:`RequestReceived <h2.events.RequestReceived>` you'll find that this
599event carries, in addition to the stream ID, the request headers.
600
601This means we can make a really simple change to our ``send_response``
602function to take those headers and encode them as a JSON object. Let's do that:
603
604.. code-block:: python
605
606    import json
607
608    def send_response(conn, event):
609        stream_id = event.stream_id
610        response_data = json.dumps(dict(event.headers)).encode('utf-8')
611
612        conn.send_headers(
613            stream_id=stream_id,
614            headers=[
615                (':status', '200'),
616                ('server', 'basic-h2-server/1.0'),
617                ('content-length', str(len(response_data))),
618                ('content-type', 'application/json'),
619            ],
620        )
621        conn.send_data(
622            stream_id=stream_id,
623            data=response_data,
624            end_stream=True
625        )
626
627This is a really simple change, but it's all we need to do: a few extra headers
628and the JSON dump, but that's it.
629
630Section 6: Bringing It All Together
631~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
632
633This should be all we need!
634
635Let's take all the work we just did and throw that into our ``h2server.py``
636file, which should now look like this:
637
638.. code-block:: python
639
640    import json
641    import socket
642
643    import h2.connection
644    import h2.events
645    import h2.config
646
647    def send_response(conn, event):
648        stream_id = event.stream_id
649        response_data = json.dumps(dict(event.headers)).encode('utf-8')
650
651        conn.send_headers(
652            stream_id=stream_id,
653            headers=[
654                (':status', '200'),
655                ('server', 'basic-h2-server/1.0'),
656                ('content-length', str(len(response_data))),
657                ('content-type', 'application/json'),
658            ],
659        )
660        conn.send_data(
661            stream_id=stream_id,
662            data=response_data,
663            end_stream=True
664        )
665
666    def handle(sock):
667        config = h2.config.H2Configuration(client_side=False)
668        conn = h2.connection.H2Connection(config=config)
669        conn.initiate_connection()
670        sock.sendall(conn.data_to_send())
671
672        while True:
673            data = sock.recv(65535)
674            if not data:
675                break
676
677            events = conn.receive_data(data)
678            for event in events:
679                if isinstance(event, h2.events.RequestReceived):
680                    send_response(conn, event)
681
682            data_to_send = conn.data_to_send()
683            if data_to_send:
684                sock.sendall(data_to_send)
685
686
687    sock = socket.socket()
688    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
689    sock.bind(('0.0.0.0', 8080))
690    sock.listen(5)
691
692    while True:
693        handle(sock.accept()[0])
694
695Now, execute ``h2server.py`` and then point ``hyper`` at it again. You should
696see something like the following output from ``hyper``:
697
698.. code-block:: console
699
700    $ hyper --h2 GET http://localhost:8080/
701    {":scheme": "http", ":authority": "localhost", ":method": "GET", ":path": "/"}
702
703Here you can see the HTTP/2 request 'special headers' that ``hyper`` sends.
704These are similar to the ``:status`` header we have to send on our response:
705they encode important parts of the HTTP request in a clearly-defined way. If
706you were writing a client stack using Hyper-h2, you'd need to make sure you
707were sending those headers.
708
709Congratulations!
710~~~~~~~~~~~~~~~~
711
712Congratulations! You've written your first HTTP/2 server! If you want to extend
713it, there are a few directions you could investigate:
714
715- We didn't handle a few events that we saw were being raised: you could add
716  some methods to handle those appropriately.
717- Right now our server is single threaded, so it can only handle one client at
718  a time. Consider rewriting this server to use threads, or writing this
719  server again using your favourite asynchronous programming framework.
720
721  If you plan to use threads, you should know that a ``H2Connection`` object is
722  deliberately not thread-safe. As a possible design pattern, consider creating
723  threads and passing the sockets returned by ``accept`` to those threads, and
724  then letting those threads create their own ``H2Connection`` objects.
725- Take a look at some of our long-form code examples in :doc:`examples`.
726- Alternatively, try playing around with our examples in our repository's
727  `examples directory`_. These examples are a bit more fully-featured, and can
728  be reached from your web browser. Try adjusting what they do, or adding new
729  features to them!
730- You may want to make this server reachable from your web browser. To do that,
731  you'll need to add proper TLS support to your server. This can be tricky, and
732  in many cases requires `PyOpenSSL`_ in addition to the other libraries you
733  have installed. Check the `Eventlet example`_ to see what PyOpenSSL code is
734  required to TLS-ify your server.
735
736
737
738.. _event loop: https://en.wikipedia.org/wiki/Event_loop
739.. _httpbin.org/get: https://httpbin.org/get
740.. _examples directory: https://github.com/python-hyper/hyper-h2/tree/master/examples
741.. _standard library's socket module: https://docs.python.org/3.5/library/socket.html
742.. _Application Layer Protocol Negotiation: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation
743.. _get your certificate here: https://raw.githubusercontent.com/python-hyper/hyper-h2/master/examples/twisted/server.crt
744.. _get your private key here: https://raw.githubusercontent.com/python-hyper/hyper-h2/master/examples/twisted/server.key
745.. _PyOpenSSL: http://pyopenssl.readthedocs.org/
746.. _Eventlet example: https://github.com/python-hyper/hyper-h2/blob/master/examples/eventlet/eventlet-server.py
747