Skip to content
This repository was archived by the owner on Jan 13, 2021. It is now read-only.

Commit c860485

Browse files
committed
Add HTTP upgrade functionality
1 parent 8416072 commit c860485

File tree

4 files changed

+43
-15
lines changed

4 files changed

+43
-15
lines changed

hyper/common/connection.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@
55
66
Hyper's HTTP/1.1 and HTTP/2 abstraction layer.
77
"""
8-
from .exceptions import TLSUpgrade
8+
from .exceptions import TLSUpgrade, HTTPUpgrade
99
from ..http11.connection import HTTP11Connection
10-
from ..http20.connection import HTTP20Connection
10+
from ..http20.connection import HTTP20Connection, H2C_PROTOCOL
1111
from ..tls import H2_NPN_PROTOCOLS
1212

13-
1413
class HTTPConnection(object):
1514
"""
1615
An object representing a single HTTP connection to a server.
@@ -88,12 +87,15 @@ def request(self, method, url, body=None, headers={}):
8887
return self._conn.request(
8988
method=method, url=url, body=body, headers=headers
9089
)
91-
except TLSUpgrade as e:
92-
# We upgraded in the NPN/ALPN handshake. We can just go straight to
93-
# the world of HTTP/2. Replace the backing object and insert the
94-
# socket into it.
95-
assert e.negotiated in H2_NPN_PROTOCOLS
96-
90+
except (TLSUpgrade, HTTPUpgrade) as e:
91+
# We upgraded in the NPN/ALPN handshake or via the HTTP Upgrade
92+
# mechanism. We can just go straight to the world of HTTP/2.
93+
#Replace the backing object and insert the socket into it.
94+
if(type(e) is TLSUpgrade):
95+
assert e.negotiated in H2_NPN_PROTOCOLS
96+
else:
97+
assert e.negotiated == H2C_PROTOCOL
98+
9799
self._conn = HTTP20Connection(
98100
self._host, self._port, **self._h2_kwargs
99101
)

hyper/common/exceptions.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,12 @@ def __init__(self, negotiated, sock):
5151
super(TLSUpgrade, self).__init__()
5252
self.negotiated = negotiated
5353
self.sock = sock
54+
55+
class HTTPUpgrade(Exception):
56+
"""
57+
We upgraded to a new protocol via the HTTP Upgrade response.
58+
"""
59+
def __init__(self, negotiated, sock):
60+
super(HTTPUpgrade, self).__init__()
61+
self.negotiated = negotiated
62+
self.sock = sock

hyper/http11/connection.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@
88
import logging
99
import os
1010
import socket
11+
import base64
1112

1213
from .response import HTTP11Response
1314
from ..tls import wrap_socket
1415
from ..common.bufsocket import BufferedSocket
15-
from ..common.exceptions import TLSUpgrade
16+
from ..common.exceptions import TLSUpgrade, HTTPUpgrade
1617
from ..common.headers import HTTPHeaderMap
1718
from ..common.util import to_bytestring
1819
from ..compat import bytes
1920

21+
from ..http20.connection import H2C_PROTOCOL
22+
from ..packages.hyperframe.frame import SettingsFrame
2023

2124
# We prefer pycohttpparser to the pure-Python interpretation
2225
try: # pragma: no cover
@@ -94,7 +97,7 @@ def connect(self):
9497
if self.secure:
9598
sock, proto = wrap_socket(sock, self.host, self.ssl_context)
9699

97-
log.debug("Selected NPN protocol: %s", proto)
100+
log.debug("Selected protocol: %s", proto)
98101
sock = BufferedSocket(sock, self.network_buffer_size)
99102

100103
if proto not in ('http/1.1', None):
@@ -130,6 +133,15 @@ def request(self, method, url, body=None, headers={}):
130133
if self._sock is None:
131134
self.connect()
132135

136+
#add HTTP Upgrade headers
137+
headers[b'connection'] = b'Upgrade, HTTP2-Settings'
138+
headers[b'upgrade'] = H2C_PROTOCOL
139+
140+
#need to encode SETTINGS frame payload in Base64 and put into the HTTP-2 Settings header
141+
http2Settings = SettingsFrame(0)
142+
http2Settings.settings[SettingsFrame.INITIAL_WINDOW_SIZE] = 65535
143+
headers[b'HTTP2-Settings'] = base64.b64encode(http2Settings.serialize_body())
144+
133145
# We may need extra headers.
134146
if body:
135147
body_type = self._add_body_headers(headers, body)
@@ -166,6 +178,11 @@ def get_response(self):
166178

167179
self._sock.advance_buffer(response.consumed)
168180

181+
if(response.status == 101 and
182+
response.getheader('Connection') == 'Upgrade' and
183+
response.getheader('Upgrade') == H2C_PROTOCOL):
184+
raise HTTPUpgrade(proto, sock)
185+
169186
return HTTP11Response(
170187
response.status,
171188
response.msg.tobytes(),

hyper/http20/connection.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
import logging
2626
import socket
2727

28-
log = logging.getLogger(__name__)
28+
H2C_PROTOCOL = 'h2c'
2929

30-
H2_CLEARTEXT_PROTOCOL = 'h2c'
30+
log = logging.getLogger(__name__)
3131

3232
class HTTP20Connection(object):
3333
"""
@@ -224,10 +224,10 @@ def connect(self):
224224
if self.secure:
225225
sock, proto = wrap_socket(sock, self.host, self.ssl_context)
226226
else:
227-
proto = H2_CLEARTEXT_PROTOCOL
227+
proto = H2C_PROTOCOL
228228

229229
log.debug("Selected NPN protocol: %s", proto)
230-
assert proto in (H2_NPN_PROTOCOLS, H2_CLEARTEXT_PROTOCOL)
230+
assert proto in (H2_NPN_PROTOCOLS, H2C_PROTOCOL)
231231

232232
self._sock = BufferedSocket(sock, self.network_buffer_size)
233233

0 commit comments

Comments
 (0)