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

Commit 01d412c

Browse files
committed
Further HTTP/1.1 work.
1 parent e7852f3 commit 01d412c

File tree

4 files changed

+80
-36
lines changed

4 files changed

+80
-36
lines changed

hyper/common/headers.py

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"""
88
import collections
99

10-
from hyper.compat import unicode, bytes, imap
10+
from hyper.common.util import to_bytestring, to_bytestring_tuple
1111

1212

1313
class HTTPHeaderMap(collections.MutableMapping):
@@ -64,18 +64,18 @@ def __init__(self, *args, **kwargs):
6464
self._items = []
6565

6666
for arg in args:
67-
self._items.extend(map(lambda x: _to_bytestring_tuple(*x), arg))
67+
self._items.extend(map(lambda x: to_bytestring_tuple(*x), arg))
6868

6969
for k, v in kwargs.items():
70-
self._items.append(_to_bytestring_tuple(k, v))
70+
self._items.append(to_bytestring_tuple(k, v))
7171

7272
def __getitem__(self, key):
7373
"""
7474
Unlike the dict __getitem__, this returns a list of items in the order
7575
they were added. These items are returned in 'canonical form', meaning
7676
that comma-separated values are split into multiple values.
7777
"""
78-
key = _to_bytestring(key)
78+
key = to_bytestring(key)
7979
values = []
8080

8181
for k, v in self._items:
@@ -91,15 +91,15 @@ def __setitem__(self, key, value):
9191
"""
9292
Unlike the dict __setitem__, this appends to the list of items.
9393
"""
94-
self._items.append(_to_bytestring_tuple(key, value))
94+
self._items.append(to_bytestring_tuple(key, value))
9595

9696
def __delitem__(self, key):
9797
"""
9898
Sadly, __delitem__ is kind of stupid here, but the best we can do is
9999
delete all headers with a given key. To correctly achieve the 'KeyError
100100
on missing key' logic from dictionaries, we need to do this slowly.
101101
"""
102-
key = _to_bytestring(key)
102+
key = to_bytestring(key)
103103
indices = []
104104
for (i, (k, v)) in enumerate(self._items):
105105
if _keys_equal(k, key):
@@ -135,7 +135,7 @@ def __contains__(self, key):
135135
"""
136136
If any header is present with this key, returns True.
137137
"""
138-
key = _to_bytestring(key)
138+
key = to_bytestring(key)
139139
return any(_keys_equal(key, k) for k, _ in self._items)
140140

141141
def keys(self):
@@ -205,26 +205,6 @@ def canonical_form(k, v):
205205
yield k, sub_val.strip()
206206

207207

208-
def _to_bytestring(element):
209-
"""
210-
Converts a single string to a bytestring, encoding via UTF-8 if needed.
211-
"""
212-
if isinstance(element, unicode):
213-
return element.encode('utf-8')
214-
elif isinstance(element, bytes):
215-
return element
216-
else:
217-
raise ValueError("Non string type.")
218-
219-
220-
def _to_bytestring_tuple(*x):
221-
"""
222-
Converts the given strings to a bytestring if necessary, returning a
223-
tuple.
224-
"""
225-
return tuple(imap(_to_bytestring, x))
226-
227-
228208
def _keys_equal(x, y):
229209
"""
230210
Returns 'True' if the two keys are equal by the laws of HTTP headers.

hyper/common/util.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
hyper/common/util
4+
~~~~~~~~~~~~~~~~~
5+
6+
General utility functions for use with hyper.
7+
"""
8+
from hyper.compat import unicode, bytes, imap
9+
10+
def to_bytestring(element):
11+
"""
12+
Converts a single string to a bytestring, encoding via UTF-8 if needed.
13+
"""
14+
if isinstance(element, unicode):
15+
return element.encode('utf-8')
16+
elif isinstance(element, bytes):
17+
return element
18+
else:
19+
raise ValueError("Non string type.")
20+
21+
22+
def to_bytestring_tuple(*x):
23+
"""
24+
Converts the given strings to a bytestring if necessary, returning a
25+
tuple. Uses ``to_bytestring``.
26+
"""
27+
return tuple(imap(to_bytestring, x))

hyper/http11/connection.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,26 @@
77
"""
88
import io
99
import logging
10+
import re
1011
import socket
1112

1213
from .response import HTTP11Response
1314
from ..http20.bufsocket import BufferedSocket
15+
from ..common.headers import HTTPHeaderMap
16+
from ..common.util import to_bytestring
1417

1518
log = logging.getLogger(__name__)
1619

20+
# This regular expression provides a fairly generous parsing of a HTTP status
21+
# line. It matches any amount of leading LWS, followed by a three-digit status
22+
# code, followed by LWS, followed by a reason phrase (allowing only space
23+
# separators), followed by the version (must be HTTP/1.1).
24+
#
25+
# For now this is a 'good enough' way to parse the status line. We may want a
26+
# dedicated parsing implementation later on, though it'd have to be faster than
27+
# this regex to be worthwhile.
28+
STATUS_LINE_REGEX = re.compile(rb'[ \t]*(?P<code>\d{3})[ \t]+(?P<reason>[\S ]+)[ \t]+HTTP/1\.1[ \t]*\r?\n')
29+
1730

1831
class HTTP11Connection(object):
1932
"""
@@ -72,6 +85,9 @@ def request(self, method, url, body=None, headers={}):
7285
:param headers: (optional) The headers to send on the request.
7386
:returns: Nothing.
7487
"""
88+
method = to_bytestring(method)
89+
url = to_bytestring(url)
90+
7591
if self._sock is None:
7692
self.connect()
7793

@@ -101,10 +117,18 @@ def get_response(self):
101117
This is an early beta, so the response object is pretty stupid. That's
102118
ok, we'll fix it later.
103119
"""
104-
headers = {}
105-
106-
# First read the header line and drop it on the floor.
107-
self._sock.readline()
120+
headers = HTTPHeaderMap()
121+
122+
# First read the header line and 'parse' it. This particular part of
123+
# the response can safely be parsed by regular expression, so do that.
124+
status_line = self._sock.readline()
125+
match = STATUS_LINE_REGEX.match(status_line)
126+
if match is None:
127+
raise RuntimeError("Invalid status line")
128+
129+
code, reason = int(match.group('code')), match.group('reason')
130+
print(code)
131+
print(reason)
108132

109133
while True:
110134
line = self._sock.readline().tobytes()

hyper/http11/response.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ def __init__(self, headers, sock):
6464
#: The status code returned by the server.
6565
self.status = 0
6666

67-
# The response headers. These are determined upon creation, assigned
68-
# once, and never assigned again.
69-
self._headers = headers
67+
#: The response headers. These are determined upon creation, assigned
68+
#: once, and never assigned again.
69+
self.headers = headers
7070

7171
# The response trailers. These are always intially ``None``.
7272
self._trailers = None
@@ -78,6 +78,19 @@ def __init__(self, headers, sock):
7878
# may need to buffer some for incomplete reads.
7979
self._data_buffer = b''
8080

81+
# This object is used for decompressing gzipped request bodies. Right
82+
# now we only support gzip because that's all the RFC mandates of us.
83+
# Later we'll add support for more encodings.
84+
# This 16 + MAX_WBITS nonsense is to force gzip. See this
85+
# Stack Overflow answer for more:
86+
# http://stackoverflow.com/a/2695466/1401686
87+
if b'gzip' in self.headers.get('content-encoding', []):
88+
self._decompressobj = zlib.decompressobj(16 + zlib.MAX_WBITS)
89+
elif b'deflate' in self.headers.get('content-encoding', []):
90+
self._decompressobj = DeflateDecoder()
91+
else:
92+
self._decompressobj = None
93+
8194
def read(self, amt=None, decode_content=True):
8295
"""
8396
Reads the response body, or up to the next ``amt`` bytes.
@@ -94,13 +107,13 @@ def read(self, amt=None, decode_content=True):
94107
# then, read content-length. This obviously doesn't work longer term,
95108
# we need to do some content-length processing there.
96109
if amt is None:
97-
amt = self.headers.get(b'content-length', 0)
110+
amt = int(self.headers.get(b'content-length', [0])[0])
98111

99112
# Return early if we've lost our connection.
100113
if self._sock is None:
101114
return b''
102115

103-
data = self._sock.read(amt)
116+
data = self._sock.recv(amt).tobytes()
104117

105118
# We may need to decode the body.
106119
if decode_content and self._decompressobj and data:

0 commit comments

Comments
 (0)