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

Commit 80ecee4

Browse files
authored
Merge pull request #296 from nateprewitt/285
add HTTP version to connection and responses
2 parents b7d7a84 + bdcf38e commit 80ecee4

File tree

9 files changed

+147
-4
lines changed

9 files changed

+147
-4
lines changed

Diff for: hyper/common/util.py

+10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
66
General utility functions for use with hyper.
77
"""
8+
from enum import Enum
9+
810
from hyper.compat import unicode, bytes, imap
911
from ..packages.rfc3986.uri import URIReference
1012
from ..compat import is_py3
@@ -57,3 +59,11 @@ def to_native_string(string, encoding='utf-8'):
5759
return string
5860

5961
return string.decode(encoding) if is_py3 else string.encode(encoding)
62+
63+
64+
class HTTPVersion(Enum):
65+
"""
66+
Collection of all HTTP versions used in hyper.
67+
"""
68+
http11 = "HTTP/1.1"
69+
http20 = "HTTP/2"

Diff for: hyper/http11/connection.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from ..common.bufsocket import BufferedSocket
2121
from ..common.exceptions import TLSUpgrade, HTTPUpgrade
2222
from ..common.headers import HTTPHeaderMap
23-
from ..common.util import to_bytestring, to_host_port_tuple
23+
from ..common.util import to_bytestring, to_host_port_tuple, HTTPVersion
2424
from ..compat import bytes
2525

2626
# We prefer pycohttpparser to the pure-Python interpretation
@@ -56,6 +56,9 @@ class HTTP11Connection(object):
5656
and one also isn't provided in the ``proxy`` parameter,
5757
defaults to 8080.
5858
"""
59+
60+
version = HTTPVersion.http11
61+
5962
def __init__(self, host, port=None, secure=None, ssl_context=None,
6063
proxy_host=None, proxy_port=None, **kwargs):
6164
if port is None:

Diff for: hyper/http11/response.py

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from ..common.decoder import DeflateDecoder
1414
from ..common.exceptions import ChunkedDecodeError, InvalidResponseError
1515
from ..common.exceptions import ConnectionResetError
16+
from ..common.util import HTTPVersion
1617

1718
log = logging.getLogger(__name__)
1819

@@ -23,6 +24,9 @@ class HTTP11Response(object):
2324
provides access to the response headers and the entity body. The response
2425
is an iterable object and can be used in a with statement.
2526
"""
27+
28+
version = HTTPVersion.http11
29+
2630
def __init__(self, code, reason, headers, sock, connection=None):
2731
#: The reason phrase returned by the server.
2832
self.reason = reason

Diff for: hyper/http20/connection.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
from ..common.exceptions import ConnectionResetError
1515
from ..common.bufsocket import BufferedSocket
1616
from ..common.headers import HTTPHeaderMap
17-
from ..common.util import to_host_port_tuple, to_native_string, to_bytestring
17+
from ..common.util import (
18+
to_host_port_tuple, to_native_string, to_bytestring, HTTPVersion
19+
)
1820
from ..compat import unicode, bytes
1921
from .stream import Stream
2022
from .response import HTTP20Response, HTTP20Push
@@ -91,6 +93,9 @@ class HTTP20Connection(object):
9193
and one also isn't provided in the ``proxy`` parameter, defaults to
9294
8080.
9395
"""
96+
97+
version = HTTPVersion.http20
98+
9499
def __init__(self, host, port=None, secure=None, window_manager=None,
95100
enable_push=False, ssl_context=None, proxy_host=None,
96101
proxy_port=None, force_proto=None, **kwargs):

Diff for: hyper/http20/response.py

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from ..common.decoder import DeflateDecoder
1313
from ..common.headers import HTTPHeaderMap
14+
from ..common.util import HTTPVersion
1415

1516
log = logging.getLogger(__name__)
1617

@@ -36,6 +37,9 @@ class HTTP20Response(object):
3637
the persistent connections used in HTTP/2 this has no effect, and is done
3738
soley for compatibility).
3839
"""
40+
41+
version = HTTPVersion.http20
42+
3943
def __init__(self, headers, stream):
4044
#: The reason phrase returned by the server. This is not used in
4145
#: HTTP/2, and so is always the empty string.

Diff for: setup.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def run_tests(self):
9696
# module at lower than 1.0, because it doesn't support CFFI v1.0 yet.
9797
':platform_python_implementation == "PyPy" and python_full_version < "2.7.9"': [
9898
'cryptography<1.0'
99-
]
99+
],
100+
':python_version == "2.7" or python_version == "3.3"': ['enum34>=1.0.4, <2']
100101
}
101102
)

Diff for: test/test_http11.py

+13
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from hyper.http11.response import HTTP11Response
2020
from hyper.common.headers import HTTPHeaderMap
2121
from hyper.common.exceptions import ChunkedDecodeError, ConnectionResetError
22+
from hyper.common.util import HTTPVersion
2223
from hyper.compat import bytes, zlib_compressobj
2324

2425

@@ -838,6 +839,18 @@ def test_closing_chunked_reads_dont_call_close_callback(self):
838839
assert r._sock is None
839840
assert connection.close.call_count == 1
840841

842+
def test_connection_version(self):
843+
c = HTTP11Connection('httpbin.org')
844+
assert c.version is HTTPVersion.http11
845+
846+
def test_response_version(self):
847+
d = DummySocket()
848+
headers = {
849+
b'transfer-encoding': [b'chunked'], b'connection': [b'close']
850+
}
851+
r = HTTP11Response(200, 'OK', headers, d)
852+
assert r.version is HTTPVersion.http11
853+
841854

842855
class DummySocket(object):
843856
def __init__(self):

Diff for: test/test_hyper.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
combine_repeated_headers, split_repeated_headers, h2_safe_headers
1717
)
1818
from hyper.common.headers import HTTPHeaderMap
19-
from hyper.common.util import to_bytestring
19+
from hyper.common.util import to_bytestring, HTTPVersion
2020
from hyper.compat import zlib_compressobj, is_py2
2121
from hyper.contrib import HTTP20Adapter
2222
import hyper.http20.errors as errors
@@ -82,6 +82,10 @@ def test_connections_can_parse_ipv6_hosts_and_ports(self):
8282
assert c.proxy_host == 'ffff:aaaa::1'
8383
assert c.proxy_port == 8443
8484

85+
def test_connection_version(self):
86+
c = HTTP20Connection('www.google.com')
87+
assert c.version is HTTPVersion.http20
88+
8589
def test_ping(self, frame_buffer):
8690
def data_callback(chunk, **kwargs):
8791
frame_buffer.add_data(chunk)
@@ -1097,6 +1101,10 @@ def test_read_compressed_frames(self):
10971101

10981102
assert received == b'this is test data'
10991103

1104+
def test_response_version(self):
1105+
r = HTTP20Response(HTTPHeaderMap([(':status', '200')]), None)
1106+
assert r.version is HTTPVersion.http20
1107+
11001108

11011109
class TestHTTP20Adapter(object):
11021110
def test_adapter_reuses_connections(self):

Diff for: test/test_integration.py

+95
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from h2.frame_buffer import FrameBuffer
1616
from hyper.compat import ssl
1717
from hyper.contrib import HTTP20Adapter
18+
from hyper.common.util import HTTPVersion
1819
from hyperframe.frame import (
1920
Frame, SettingsFrame, WindowUpdateFrame, DataFrame, HeadersFrame,
2021
GoAwayFrame, RstStreamFrame
@@ -935,6 +936,100 @@ def socket_handler(listener):
935936

936937
self.tear_down()
937938

939+
def test_version_after_tls_upgrade(self, monkeypatch):
940+
self.set_up()
941+
942+
# We need to patch the ssl_wrap_socket method to ensure that we
943+
# forcefully upgrade.
944+
old_wrap_socket = hyper.http11.connection.wrap_socket
945+
946+
def wrap(*args):
947+
sock, _ = old_wrap_socket(*args)
948+
return sock, 'h2'
949+
950+
monkeypatch.setattr(hyper.http11.connection, 'wrap_socket', wrap)
951+
952+
send_event = threading.Event()
953+
954+
def socket_handler(listener):
955+
sock = listener.accept()[0]
956+
957+
receive_preamble(sock)
958+
959+
# Send the headers for the response. This response has no body.
960+
f = build_headers_frame(
961+
[(':status', '200'), ('content-length', '0')]
962+
)
963+
f.flags.add('END_STREAM')
964+
f.stream_id = 1
965+
sock.sendall(f.serialize())
966+
967+
# Wait for the message from the main thread.
968+
send_event.wait()
969+
sock.close()
970+
971+
self._start_server(socket_handler)
972+
c = hyper.HTTPConnection(self.host, self.port, secure=True)
973+
974+
assert c.version is HTTPVersion.http11
975+
assert c.version is not HTTPVersion.http20
976+
c.request('GET', '/')
977+
send_event.set()
978+
assert c.version is HTTPVersion.http20
979+
980+
self.tear_down()
981+
982+
def test_version_after_http_upgrade(self):
983+
self.set_up()
984+
self.secure = False
985+
986+
send_event = threading.Event()
987+
988+
def socket_handler(listener):
989+
sock = listener.accept()[0]
990+
991+
# We should get the initial request.
992+
data = b''
993+
while not data.endswith(b'\r\n\r\n'):
994+
data += sock.recv(65535)
995+
assert b'upgrade: h2c\r\n' in data
996+
997+
send_event.wait()
998+
999+
# We need to send back a response.
1000+
resp = (
1001+
b'HTTP/1.1 101 Upgrade\r\n'
1002+
b'Server: socket-level-server\r\n'
1003+
b'Content-Length: 0\r\n'
1004+
b'Connection: upgrade\r\n'
1005+
b'Upgrade: h2c\r\n'
1006+
b'\r\n'
1007+
)
1008+
sock.sendall(resp)
1009+
1010+
# We get a message for connection open, specifically the preamble.
1011+
receive_preamble(sock)
1012+
1013+
# Send the headers for the response. This response has a body.
1014+
f = build_headers_frame(
1015+
[(':status', '200'), ('content-length', '0')]
1016+
)
1017+
f.stream_id = 1
1018+
f.flags.add('END_STREAM')
1019+
sock.sendall(f.serialize())
1020+
1021+
self._start_server(socket_handler)
1022+
1023+
c = hyper.HTTPConnection(self.host, self.port)
1024+
assert c.version is HTTPVersion.http11
1025+
c.request('GET', '/')
1026+
send_event.set()
1027+
resp = c.get_response()
1028+
assert c.version is HTTPVersion.http20
1029+
assert resp.version is HTTPVersion.http20
1030+
1031+
self.tear_down()
1032+
9381033

9391034
class TestRequestsAdapter(SocketLevelTest):
9401035
# This uses HTTP/2.

0 commit comments

Comments
 (0)