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

Commit

Permalink
Merge pull request #296 from nateprewitt/285
Browse files Browse the repository at this point in the history
add HTTP version to connection and responses
  • Loading branch information
Lukasa committed Dec 7, 2016
2 parents b7d7a84 + bdcf38e commit 80ecee4
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 4 deletions.
10 changes: 10 additions & 0 deletions hyper/common/util.py
Expand Up @@ -5,6 +5,8 @@
General utility functions for use with hyper.
"""
from enum import Enum

from hyper.compat import unicode, bytes, imap
from ..packages.rfc3986.uri import URIReference
from ..compat import is_py3
Expand Down Expand Up @@ -57,3 +59,11 @@ def to_native_string(string, encoding='utf-8'):
return string

return string.decode(encoding) if is_py3 else string.encode(encoding)


class HTTPVersion(Enum):
"""
Collection of all HTTP versions used in hyper.
"""
http11 = "HTTP/1.1"
http20 = "HTTP/2"
5 changes: 4 additions & 1 deletion hyper/http11/connection.py
Expand Up @@ -20,7 +20,7 @@
from ..common.bufsocket import BufferedSocket
from ..common.exceptions import TLSUpgrade, HTTPUpgrade
from ..common.headers import HTTPHeaderMap
from ..common.util import to_bytestring, to_host_port_tuple
from ..common.util import to_bytestring, to_host_port_tuple, HTTPVersion
from ..compat import bytes

# We prefer pycohttpparser to the pure-Python interpretation
Expand Down Expand Up @@ -56,6 +56,9 @@ class HTTP11Connection(object):
and one also isn't provided in the ``proxy`` parameter,
defaults to 8080.
"""

version = HTTPVersion.http11

def __init__(self, host, port=None, secure=None, ssl_context=None,
proxy_host=None, proxy_port=None, **kwargs):
if port is None:
Expand Down
4 changes: 4 additions & 0 deletions hyper/http11/response.py
Expand Up @@ -13,6 +13,7 @@
from ..common.decoder import DeflateDecoder
from ..common.exceptions import ChunkedDecodeError, InvalidResponseError
from ..common.exceptions import ConnectionResetError
from ..common.util import HTTPVersion

log = logging.getLogger(__name__)

Expand All @@ -23,6 +24,9 @@ class HTTP11Response(object):
provides access to the response headers and the entity body. The response
is an iterable object and can be used in a with statement.
"""

version = HTTPVersion.http11

def __init__(self, code, reason, headers, sock, connection=None):
#: The reason phrase returned by the server.
self.reason = reason
Expand Down
7 changes: 6 additions & 1 deletion hyper/http20/connection.py
Expand Up @@ -14,7 +14,9 @@
from ..common.exceptions import ConnectionResetError
from ..common.bufsocket import BufferedSocket
from ..common.headers import HTTPHeaderMap
from ..common.util import to_host_port_tuple, to_native_string, to_bytestring
from ..common.util import (
to_host_port_tuple, to_native_string, to_bytestring, HTTPVersion
)
from ..compat import unicode, bytes
from .stream import Stream
from .response import HTTP20Response, HTTP20Push
Expand Down Expand Up @@ -91,6 +93,9 @@ class HTTP20Connection(object):
and one also isn't provided in the ``proxy`` parameter, defaults to
8080.
"""

version = HTTPVersion.http20

def __init__(self, host, port=None, secure=None, window_manager=None,
enable_push=False, ssl_context=None, proxy_host=None,
proxy_port=None, force_proto=None, **kwargs):
Expand Down
4 changes: 4 additions & 0 deletions hyper/http20/response.py
Expand Up @@ -11,6 +11,7 @@

from ..common.decoder import DeflateDecoder
from ..common.headers import HTTPHeaderMap
from ..common.util import HTTPVersion

log = logging.getLogger(__name__)

Expand All @@ -36,6 +37,9 @@ class HTTP20Response(object):
the persistent connections used in HTTP/2 this has no effect, and is done
soley for compatibility).
"""

version = HTTPVersion.http20

def __init__(self, headers, stream):
#: The reason phrase returned by the server. This is not used in
#: HTTP/2, and so is always the empty string.
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Expand Up @@ -96,6 +96,7 @@ def run_tests(self):
# module at lower than 1.0, because it doesn't support CFFI v1.0 yet.
':platform_python_implementation == "PyPy" and python_full_version < "2.7.9"': [
'cryptography<1.0'
]
],
':python_version == "2.7" or python_version == "3.3"': ['enum34>=1.0.4, <2']
}
)
13 changes: 13 additions & 0 deletions test/test_http11.py
Expand Up @@ -19,6 +19,7 @@
from hyper.http11.response import HTTP11Response
from hyper.common.headers import HTTPHeaderMap
from hyper.common.exceptions import ChunkedDecodeError, ConnectionResetError
from hyper.common.util import HTTPVersion
from hyper.compat import bytes, zlib_compressobj


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

def test_connection_version(self):
c = HTTP11Connection('httpbin.org')
assert c.version is HTTPVersion.http11

def test_response_version(self):
d = DummySocket()
headers = {
b'transfer-encoding': [b'chunked'], b'connection': [b'close']
}
r = HTTP11Response(200, 'OK', headers, d)
assert r.version is HTTPVersion.http11


class DummySocket(object):
def __init__(self):
Expand Down
10 changes: 9 additions & 1 deletion test/test_hyper.py
Expand Up @@ -16,7 +16,7 @@
combine_repeated_headers, split_repeated_headers, h2_safe_headers
)
from hyper.common.headers import HTTPHeaderMap
from hyper.common.util import to_bytestring
from hyper.common.util import to_bytestring, HTTPVersion
from hyper.compat import zlib_compressobj, is_py2
from hyper.contrib import HTTP20Adapter
import hyper.http20.errors as errors
Expand Down Expand Up @@ -82,6 +82,10 @@ def test_connections_can_parse_ipv6_hosts_and_ports(self):
assert c.proxy_host == 'ffff:aaaa::1'
assert c.proxy_port == 8443

def test_connection_version(self):
c = HTTP20Connection('www.google.com')
assert c.version is HTTPVersion.http20

def test_ping(self, frame_buffer):
def data_callback(chunk, **kwargs):
frame_buffer.add_data(chunk)
Expand Down Expand Up @@ -1097,6 +1101,10 @@ def test_read_compressed_frames(self):

assert received == b'this is test data'

def test_response_version(self):
r = HTTP20Response(HTTPHeaderMap([(':status', '200')]), None)
assert r.version is HTTPVersion.http20


class TestHTTP20Adapter(object):
def test_adapter_reuses_connections(self):
Expand Down
95 changes: 95 additions & 0 deletions test/test_integration.py
Expand Up @@ -15,6 +15,7 @@
from h2.frame_buffer import FrameBuffer
from hyper.compat import ssl
from hyper.contrib import HTTP20Adapter
from hyper.common.util import HTTPVersion
from hyperframe.frame import (
Frame, SettingsFrame, WindowUpdateFrame, DataFrame, HeadersFrame,
GoAwayFrame, RstStreamFrame
Expand Down Expand Up @@ -935,6 +936,100 @@ def socket_handler(listener):

self.tear_down()

def test_version_after_tls_upgrade(self, monkeypatch):
self.set_up()

# We need to patch the ssl_wrap_socket method to ensure that we
# forcefully upgrade.
old_wrap_socket = hyper.http11.connection.wrap_socket

def wrap(*args):
sock, _ = old_wrap_socket(*args)
return sock, 'h2'

monkeypatch.setattr(hyper.http11.connection, 'wrap_socket', wrap)

send_event = threading.Event()

def socket_handler(listener):
sock = listener.accept()[0]

receive_preamble(sock)

# Send the headers for the response. This response has no body.
f = build_headers_frame(
[(':status', '200'), ('content-length', '0')]
)
f.flags.add('END_STREAM')
f.stream_id = 1
sock.sendall(f.serialize())

# Wait for the message from the main thread.
send_event.wait()
sock.close()

self._start_server(socket_handler)
c = hyper.HTTPConnection(self.host, self.port, secure=True)

assert c.version is HTTPVersion.http11
assert c.version is not HTTPVersion.http20
c.request('GET', '/')
send_event.set()
assert c.version is HTTPVersion.http20

self.tear_down()

def test_version_after_http_upgrade(self):
self.set_up()
self.secure = False

send_event = threading.Event()

def socket_handler(listener):
sock = listener.accept()[0]

# We should get the initial request.
data = b''
while not data.endswith(b'\r\n\r\n'):
data += sock.recv(65535)
assert b'upgrade: h2c\r\n' in data

send_event.wait()

# We need to send back a response.
resp = (
b'HTTP/1.1 101 Upgrade\r\n'
b'Server: socket-level-server\r\n'
b'Content-Length: 0\r\n'
b'Connection: upgrade\r\n'
b'Upgrade: h2c\r\n'
b'\r\n'
)
sock.sendall(resp)

# We get a message for connection open, specifically the preamble.
receive_preamble(sock)

# Send the headers for the response. This response has a body.
f = build_headers_frame(
[(':status', '200'), ('content-length', '0')]
)
f.stream_id = 1
f.flags.add('END_STREAM')
sock.sendall(f.serialize())

self._start_server(socket_handler)

c = hyper.HTTPConnection(self.host, self.port)
assert c.version is HTTPVersion.http11
c.request('GET', '/')
send_event.set()
resp = c.get_response()
assert c.version is HTTPVersion.http20
assert resp.version is HTTPVersion.http20

self.tear_down()


class TestRequestsAdapter(SocketLevelTest):
# This uses HTTP/2.
Expand Down

0 comments on commit 80ecee4

Please sign in to comment.