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

Commit f219416

Browse files
author
Tim Emiola
committed
Merge branch 'development' of github.com:Lukasa/hyper into concurrency
- syncs the concurrency branch with development branch - modifies the concurrency implementation to reflect the use of hyper-h
2 parents 7e671d0 + 065b539 commit f219416

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1642
-3604
lines changed

.coveragerc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
[run]
2+
source = hyper
23
omit =
34
hyper/compat.py
45
hyper/httplib_compat.py
56
hyper/ssl_compat.py
67
hyper/packages/*
8+
9+
[report]
10+
fail_under = 100
11+
show_missing = True
12+
13+
[paths]
14+
source =
15+
hyper/
16+
.tox/*/lib/python*/site-packages/hyper
17+
.tox/pypy*/site-packages/hyper

.travis.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ language: python
22

33
python:
44
- "2.7"
5-
- "3.3"
65
- "3.4"
6+
- "3.5"
77
- pypy
88

99
env:
@@ -23,6 +23,8 @@ matrix:
2323

2424
install:
2525
- ".travis/install.sh"
26+
before_script: "flake8 --max-complexity 15 --exclude 'hyper/packages/*' hyper test"
27+
2628
script:
2729
- >
2830
if [[ "$TEST_RELEASE" == true ]]; then
@@ -31,7 +33,8 @@ script:
3133
if [[ $TRAVIS_PYTHON_VERSION == pypy ]]; then
3234
py.test test/
3335
else
34-
py.test -n 4 --cov hyper test/
35-
coverage report -m --fail-under 100
36+
coverage run -m py.test test/
37+
coverage combine
38+
coverage report
3639
fi
3740
fi

.travis/install.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,4 @@ fi
4848

4949
pip install .
5050
pip install -r test_requirements.txt
51+
pip install flake8

HISTORY.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
Release History
22
===============
33

4+
dev
5+
---
6+
7+
*Bugfixes*
8+
9+
- Overriding HTTP/2 special headers no longer leads to ill-formed header blocks
10+
with special headers at the end.
11+
412
0.5.0 (2015-10-11)
513
------------------
614

hyper/__init__.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
A module for providing an abstraction layer over the differences between
77
HTTP/1.1 and HTTP/2.
88
"""
9-
__version__ = '0.5.0'
9+
import logging
1010

1111
from .common.connection import HTTPConnection
1212
from .http20.connection import HTTP20Connection
@@ -16,8 +16,10 @@
1616

1717
# Throw import errors on Python <2.7 and 3.0-3.2.
1818
import sys as _sys
19-
if _sys.version_info < (2,7) or (3,0) <= _sys.version_info < (3,3):
20-
raise ImportError("hyper only supports Python 2.7 and Python 3.3 or higher.")
19+
if _sys.version_info < (2, 7) or (3, 0) <= _sys.version_info < (3, 3):
20+
raise ImportError(
21+
"hyper only supports Python 2.7 and Python 3.3 or higher."
22+
)
2123

2224
__all__ = [
2325
HTTPConnection,
@@ -29,5 +31,6 @@
2931
]
3032

3133
# Set default logging handler.
32-
import logging
3334
logging.getLogger(__name__).addHandler(logging.NullHandler())
35+
36+
__version__ = '0.5.0'

hyper/cli.py

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -108,26 +108,30 @@ def make_troubleshooting_argument(parser):
108108
help='Show debugging information (loglevel=DEBUG)')
109109
parser.add_argument(
110110
'--h2', action='store_true', default=False,
111-
help="Do HTTP/2 directly in plaintext: skip plaintext upgrade")
111+
help="Do HTTP/2 directly, skipping plaintext upgrade and ignoring "
112+
"NPN/ALPN."
113+
)
112114

113115

114-
def set_url_info(args):
115-
def split_host_and_port(hostname):
116-
if ':' in hostname:
117-
return to_host_port_tuple(hostname, default_port=443)
118-
return hostname, None
119-
120-
class UrlInfo(object):
121-
def __init__(self):
122-
self.fragment = None
123-
self.host = 'localhost'
124-
self.netloc = None
125-
self.path = '/'
126-
self.port = 443
127-
self.query = None
128-
self.scheme = 'https'
129-
self.secure = False
116+
def split_host_and_port(hostname):
117+
if ':' in hostname:
118+
return to_host_port_tuple(hostname, default_port=443)
119+
return hostname, None
120+
121+
122+
class UrlInfo(object):
123+
def __init__(self):
124+
self.fragment = None
125+
self.host = 'localhost'
126+
self.netloc = None
127+
self.path = '/'
128+
self.port = 443
129+
self.query = None
130+
self.scheme = 'https'
131+
self.secure = False
130132

133+
134+
def set_url_info(args):
131135
info = UrlInfo()
132136
_result = urlsplit(args._url)
133137
for attr in vars(info).keys():
@@ -164,7 +168,14 @@ def set_request_data(args):
164168
body, headers, params = {}, {}, {}
165169
for i in args.items:
166170
if i.sep == SEP_HEADERS:
167-
headers[i.key] = i.value
171+
if i.key:
172+
headers[i.key] = i.value
173+
else:
174+
# when overriding a HTTP/2 special header there will be a
175+
# leading colon, which tricks the command line parser into
176+
# thinking the header is empty
177+
k, v = i.value.split(':', 1)
178+
headers[':' + k] = v
168179
elif i.sep == SEP_QUERY:
169180
params[i.key] = i.value
170181
elif i.sep == SEP_DATA:
@@ -227,7 +238,10 @@ def request(args):
227238
)
228239
else: # pragma: no cover
229240
conn = HTTP20Connection(
230-
args.url.host, args.url.port, secure=args.url.secure
241+
args.url.host,
242+
args.url.port,
243+
secure=args.url.secure,
244+
force_proto='h2'
231245
)
232246

233247
conn.request(args.method, args.url.path, args.body, args.headers)

hyper/common/bufsocket.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import select
1414
from .exceptions import ConnectionResetError, LineTooLongError
1515

16+
1617
class BufferedSocket(object):
1718
"""
1819
A buffered socket wrapper.
@@ -67,9 +68,6 @@ def can_read(self):
6768
"""
6869
Whether or not there is more data to read from the socket.
6970
"""
70-
if self._bytes_in_buffer:
71-
return True
72-
7371
read = select.select([self._sck], [], [], 0)[0]
7472
if read:
7573
return True
@@ -140,8 +138,7 @@ def recv(self, amt):
140138
else:
141139
should_read = True
142140

143-
if ((self._remaining_capacity > self._bytes_in_buffer) and
144-
(should_read)):
141+
if (self._remaining_capacity > self._bytes_in_buffer and should_read):
145142
count = self._sck.recv_into(self._buffer_view[self._buffer_end:])
146143

147144
# The socket just got closed. We should throw an exception if we
@@ -175,7 +172,6 @@ def fill(self):
175172

176173
return
177174

178-
179175
def readline(self):
180176
"""
181177
Read up to a newline from the network and returns it. The implicit

hyper/common/connection.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from ..http20.connection import HTTP20Connection
1111
from ..tls import H2_NPN_PROTOCOLS, H2C_PROTOCOL
1212

13+
1314
class HTTPConnection(object):
1415
"""
1516
An object representing a single HTTP connection to a server.
@@ -24,26 +25,27 @@ class HTTPConnection(object):
2425
:param host: The host to connect to. This may be an IP address or a
2526
hostname, and optionally may include a port: for example,
2627
``'http2bin.org'``, ``'http2bin.org:443'`` or ``'127.0.0.1'``.
27-
:param port: (optional) The port to connect to. If not provided and one also
28-
isn't provided in the ``host`` parameter, defaults to 443.
28+
:param port: (optional) The port to connect to. If not provided and one
29+
also isn't provided in the ``host`` parameter, defaults to 80.
2930
:param secure: (optional) Whether the request should use TLS.
3031
Defaults to ``False`` for most requests, but to ``True`` for any
3132
request issued to port 443.
3233
:param window_manager: (optional) The class to use to manage flow control
3334
windows. This needs to be a subclass of the
34-
:class:`BaseFlowControlManager <hyper.http20.window.BaseFlowControlManager>`.
35-
If not provided,
35+
:class:`BaseFlowControlManager
36+
<hyper.http20.window.BaseFlowControlManager>`. If not provided,
3637
:class:`FlowControlManager <hyper.http20.window.FlowControlManager>`
3738
will be used.
3839
:param enable_push: (optional) Whether the server is allowed to push
3940
resources to the client (see
4041
:meth:`get_pushes() <hyper.HTTP20Connection.get_pushes>`).
4142
:param ssl_context: (optional) A class with custom certificate settings.
4243
If not provided then hyper's default ``SSLContext`` is used instead.
43-
:param proxy_host: (optional) The proxy to connect to. This can be an IP address
44-
or a host name and may include a port.
45-
:param proxy_port: (optional) The proxy port to connect to. If not provided
46-
and one also isn't provided in the ``proxy`` parameter, defaults to 8080.
44+
:param proxy_host: (optional) The proxy to connect to. This can be an IP
45+
address or a host name and may include a port.
46+
:param proxy_port: (optional) The proxy port to connect to. If not provided
47+
and one also isn't provided in the ``proxy`` parameter, defaults to
48+
8080.
4749
"""
4850
def __init__(self,
4951
host,
@@ -59,12 +61,12 @@ def __init__(self,
5961
self._host = host
6062
self._port = port
6163
self._h1_kwargs = {
62-
'secure': secure, 'ssl_context': ssl_context,
63-
'proxy_host': proxy_host, 'proxy_port': proxy_port
64+
'secure': secure, 'ssl_context': ssl_context,
65+
'proxy_host': proxy_host, 'proxy_port': proxy_port
6466
}
6567
self._h2_kwargs = {
6668
'window_manager': window_manager, 'enable_push': enable_push,
67-
'secure': secure, 'ssl_context': ssl_context,
69+
'secure': secure, 'ssl_context': ssl_context,
6870
'proxy_host': proxy_host, 'proxy_port': proxy_port
6971
}
7072

@@ -76,7 +78,7 @@ def __init__(self,
7678
self._host, self._port, **self._h1_kwargs
7779
)
7880

79-
def request(self, method, url, body=None, headers={}):
81+
def request(self, method, url, body=None, headers=None):
8082
"""
8183
This will send a request to the server using the HTTP request method
8284
``method`` and the selector ``url``. If the ``body`` argument is
@@ -93,6 +95,9 @@ def request(self, method, url, body=None, headers={}):
9395
:returns: A stream ID for the request, or ``None`` if the request is
9496
made over HTTP/1.1.
9597
"""
98+
99+
headers = headers or {}
100+
96101
try:
97102
return self._conn.request(
98103
method=method, url=url, body=body, headers=headers

hyper/common/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
66
Contains hyper's exceptions.
77
"""
8+
9+
810
class ChunkedDecodeError(Exception):
911
"""
1012
An error was encountered while decoding a chunked response.
@@ -43,6 +45,7 @@ class ConnectionResetError(Exception):
4345
A HTTP connection was unexpectedly reset.
4446
"""
4547

48+
4649
class TLSUpgrade(Exception):
4750
"""
4851
We upgraded to a new protocol in the NPN/ALPN handshake.
@@ -52,6 +55,7 @@ def __init__(self, negotiated, sock):
5255
self.negotiated = negotiated
5356
self.sock = sock
5457

58+
5559
class HTTPUpgrade(Exception):
5660
"""
5761
We upgraded to a new protocol via the HTTP Upgrade response.

hyper/common/headers.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -184,15 +184,28 @@ def iter_raw(self):
184184
def replace(self, key, value):
185185
"""
186186
Replace existing header with new value. If header doesn't exist this
187-
method work like ``__setitem__``. Replacing leads to deletion of all
188-
exsiting headers with the same name.
187+
method work like ``__setitem__``. Replacing leads to deletion of all
188+
existing headers with the same name.
189189
"""
190-
try:
191-
del self[key]
192-
except KeyError:
193-
pass
190+
key, value = to_bytestring_tuple(key, value)
191+
indices = []
192+
for (i, (k, v)) in enumerate(self._items):
193+
if _keys_equal(k, key):
194+
indices.append(i)
195+
196+
# If the key isn't present, this is easy: just append and abort early.
197+
if not indices:
198+
self._items.append((key, value))
199+
return
200+
201+
# Delete all but the first. I swear, this is the correct slicing
202+
# syntax!
203+
base_index = indices[0]
204+
for i in indices[:0:-1]:
205+
self._items.pop(i)
194206

195-
self[key] = value
207+
del self._items[base_index]
208+
self._items.insert(base_index, (key, value))
196209

197210
def merge(self, other):
198211
"""

0 commit comments

Comments
 (0)