Skip to content

Commit 27760be

Browse files
committed
Examples for TLS-negotiated HTTP/2.
1 parent 3d33c2d commit 27760be

3 files changed

Lines changed: 238 additions & 0 deletions

File tree

docs/source/negotiating-http2.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,28 @@ This method is the simplest to use once the TLS connection is established. To us
1414

1515
At this point, you're free to use all the HTTP/2 functionality provided by Hyper-h2.
1616

17+
Server Setup Example
18+
~~~~~~~~~~~~~~~~~~~~
19+
20+
This example uses the APIs as defined in Python 3.5. If you are using an older version of Python you may not have access to the APIs used here. As noted above, please consult the documentation for the :mod:`ssl module <python:ssl>` to confirm.
21+
22+
.. literalinclude:: ../../examples/fragments/server_https_setup_fragment.py
23+
:language: python
24+
:linenos:
25+
:encoding: utf-8
26+
27+
28+
Client Setup Example
29+
~~~~~~~~~~~~~~~~~~~~
30+
31+
The client example is very similar to the server example above. The :class:`SSLContext <python:ssl.SSLContext>` object requires some minor changes, as does the :class:`H2Connection <h2.connection.H2Connection>`, but the bulk of the code is the same.
32+
33+
.. literalinclude:: ../../examples/fragments/client_https_setup_fragment.py
34+
:language: python
35+
:linenos:
36+
:encoding: utf-8
37+
38+
1739
.. _starting-upgrade:
1840

1941
HTTP URLs (Upgrade)
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Client HTTPS Setup
4+
~~~~~~~~~~~~~~~~~~
5+
6+
This example code fragment demonstrates how to set up a HTTP/2 client that
7+
negotiates HTTP/2 using NPN and ALPN. For the sake of maximum explanatory value
8+
this code uses the synchronous, low-level sockets API: however, if you're not
9+
using sockets directly (e.g. because you're using asyncio), you should focus on
10+
the set up required for the SSLContext object. For other concurrency libraries
11+
you may need to use other setup (e.g. for Twisted you'll need to use
12+
IProtocolNegotiationFactory).
13+
"""
14+
import h2.connection
15+
import socket
16+
import ssl
17+
18+
19+
def establish_tcp_connection():
20+
"""
21+
This function establishes a client-side TCP connection. How it works isn't
22+
very important to this example. For the purpose of this example we connect
23+
to localhost.
24+
"""
25+
return socket.create_connection(('localhost', 443))
26+
27+
28+
def get_http2_ssl_context():
29+
"""
30+
This function creates an SSLContext object that is suitably configured for
31+
HTTP/2. If you're working with Python TLS directly, you'll want to do the
32+
exact same setup as this function does.
33+
"""
34+
# Get the basic context from the standard library.
35+
ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
36+
37+
# RFC 7540 Section 9.2: Implementations of HTTP/2 MUST use TLS version 1.2
38+
# or higher. Disable TLS 1.1 and lower.
39+
ctx.options |= (
40+
ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
41+
)
42+
43+
# RFC 7540 Section 9.2.1: A deployment of HTTP/2 over TLS 1.2 MUST disable
44+
# compression.
45+
ctx.options |= ssl.OP_NO_COMPRESSION
46+
47+
# RFC 7540 Section 9.2.2: "deployments of HTTP/2 that use TLS 1.2 MUST
48+
# support TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256". In practice, the
49+
# blacklist defined in this section allows only the AES GCM and ChaCha20
50+
# cipher suites with ephemeral key negotiation.
51+
ctx.set_ciphers("ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20")
52+
53+
# We want to negotiate using NPN and ALPN. ALPN is mandatory, but NPN may
54+
# be absent, so allow that. This setup allows for negotiation of HTTP/1.1.
55+
ctx.set_alpn_protocols(["h2", "http/1.1"])
56+
57+
try:
58+
ctx.set_npn_protocols(["h2", "http/1.1"])
59+
except NotImplementedError:
60+
pass
61+
62+
return ctx
63+
64+
65+
def negotiate_tls(tcp_conn, context):
66+
"""
67+
Given an established TCP connection and a HTTP/2-appropriate TLS context,
68+
this function:
69+
70+
1. wraps TLS around the TCP connection.
71+
2. confirms that HTTP/2 was negotiated and, if it was not, throws an error.
72+
"""
73+
# Note that SNI is mandatory for HTTP/2, so you *must* pass the
74+
# server_hostname argument.
75+
tls_conn = context.wrap_socket(tcp_conn, server_hostname='localhost')
76+
77+
# Always prefer the result from ALPN to that from NPN.
78+
# You can only check what protocol was negotiated once the handshake is
79+
# complete.
80+
negotiated_protocol = tls_conn.selected_alpn_protocol()
81+
if negotiated_protocol is None:
82+
negotiated_protocol = tls_conn.selected_npn_protocol()
83+
84+
if negotiated_protocol != "h2":
85+
raise RuntimeError("Didn't negotiate HTTP/2!")
86+
87+
return tls_conn
88+
89+
90+
def main():
91+
# Step 1: Set up your TLS context.
92+
context = get_http2_ssl_context()
93+
94+
# Step 2: Create a TCP connection.
95+
connection = establish_tcp_connection()
96+
97+
# Step 3: Wrap the connection in TLS and validate that we negotiated HTTP/2
98+
tls_connection = negotiate_tls(connection, context)
99+
100+
# Step 4: Create a server-side H2 connection.
101+
http2_connection = h2.connection.H2Connection(client_side=True)
102+
103+
# Step 5: Initiate the connection
104+
http2_connection.initiate_connection()
105+
tls_connection.sendall(http2_connection.data_to_send())
106+
107+
# The TCP, TLS, and HTTP/2 handshakes are now complete. You can enter your
108+
# main loop now.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Server HTTPS Setup
4+
~~~~~~~~~~~~~~~~~~
5+
6+
This example code fragment demonstrates how to set up a HTTP/2 server that
7+
negotiates HTTP/2 using NPN and ALPN. For the sake of maximum explanatory value
8+
this code uses the synchronous, low-level sockets API: however, if you're not
9+
using sockets directly (e.g. because you're using asyncio), you should focus on
10+
the set up required for the SSLContext object. For other concurrency libraries
11+
you may need to use other setup (e.g. for Twisted you'll need to use
12+
IProtocolNegotiationFactory).
13+
"""
14+
import h2.connection
15+
import socket
16+
import ssl
17+
18+
19+
def establish_tcp_connection():
20+
"""
21+
This function establishes a server-side TCP connection. How it works isn't
22+
very important to this example.
23+
"""
24+
bind_socket = socket.socket()
25+
bind_socket.bind(('', 443))
26+
bind_socket.listen(5)
27+
return bind_socket.accept()[0]
28+
29+
30+
def get_http2_ssl_context():
31+
"""
32+
This function creates an SSLContext object that is suitably configured for
33+
HTTP/2. If you're working with Python TLS directly, you'll want to do the
34+
exact same setup as this function does.
35+
"""
36+
# Get the basic context from the standard library.
37+
ctx = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
38+
39+
# RFC 7540 Section 9.2: Implementations of HTTP/2 MUST use TLS version 1.2
40+
# or higher. Disable TLS 1.1 and lower.
41+
ctx.options |= (
42+
ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
43+
)
44+
45+
# RFC 7540 Section 9.2.1: A deployment of HTTP/2 over TLS 1.2 MUST disable
46+
# compression.
47+
ctx.options |= ssl.OP_NO_COMPRESSION
48+
49+
# RFC 7540 Section 9.2.2: "deployments of HTTP/2 that use TLS 1.2 MUST
50+
# support TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256". In practice, the
51+
# blacklist defined in this section allows only the AES GCM and ChaCha20
52+
# cipher suites with ephemeral key negotiation.
53+
ctx.set_ciphers("ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20")
54+
55+
# We want to negotiate using NPN and ALPN. ALPN is mandatory, but NPN may
56+
# be absent, so allow that. This setup allows for negotiation of HTTP/1.1.
57+
ctx.set_alpn_protocols(["h2", "http/1.1"])
58+
59+
try:
60+
ctx.set_npn_protocols(["h2", "http/1.1"])
61+
except NotImplementedError:
62+
pass
63+
64+
return ctx
65+
66+
67+
def negotiate_tls(tcp_conn, context):
68+
"""
69+
Given an established TCP connection and a HTTP/2-appropriate TLS context,
70+
this function:
71+
72+
1. wraps TLS around the TCP connection.
73+
2. confirms that HTTP/2 was negotiated and, if it was not, throws an error.
74+
"""
75+
tls_conn = context.wrap_socket(tcp_conn, server_side=True)
76+
77+
# Always prefer the result from ALPN to that from NPN.
78+
# You can only check what protocol was negotiated once the handshake is
79+
# complete.
80+
negotiated_protocol = tls_conn.selected_alpn_protocol()
81+
if negotiated_protocol is None:
82+
negotiated_protocol = tls_conn.selected_npn_protocol()
83+
84+
if negotiated_protocol != "h2":
85+
raise RuntimeError("Didn't negotiate HTTP/2!")
86+
87+
return tls_conn
88+
89+
90+
def main():
91+
# Step 1: Set up your TLS context.
92+
context = get_http2_ssl_context()
93+
94+
# Step 2: Receive a TCP connection.
95+
connection = establish_tcp_connection()
96+
97+
# Step 3: Wrap the connection in TLS and validate that we negotiated HTTP/2
98+
tls_connection = negotiate_tls(connection, context)
99+
100+
# Step 4: Create a server-side H2 connection.
101+
http2_connection = h2.connection.H2Connection(client_side=False)
102+
103+
# Step 5: Initiate the connection
104+
http2_connection.initiate_connection()
105+
tls_connection.sendall(http2_connection.data_to_send())
106+
107+
# The TCP, TLS, and HTTP/2 handshakes are now complete. You can enter your
108+
# main loop now.

0 commit comments

Comments
 (0)