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

Commit 4e78258

Browse files
committed
Turn read_chunked into a generator.
While we're there, add support for transparent content decompression.
1 parent 1ac2e20 commit 4e78258

File tree

2 files changed

+66
-26
lines changed

2 files changed

+66
-26
lines changed

hyper/http11/response.py

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,15 @@ def read(self, amt=None, decode_content=True):
154154

155155
return data
156156

157-
def read_chunked(self):
157+
def read_chunked(self, decode_content=True):
158158
"""
159-
Reads chunked transfer encoded bodies. Each read returns a single
160-
chunk.
159+
Reads chunked transfer encoded bodies. This method returns a generator:
160+
each iteration of which yields one chunk *unless* the chunks are
161+
compressed, in which case it yields whatever the decompressor provides
162+
for each chunk.
163+
164+
.. warning:: This may yield the empty string, without that being the
165+
end of the body!
161166
"""
162167
if not self._chunked:
163168
raise ChunkedDecodeError(
@@ -166,31 +171,42 @@ def read_chunked(self):
166171

167172
# Return early if possible.
168173
if self._sock is None:
169-
return b''
174+
return
170175

171-
# Read to the newline to get the chunk length. This is a hexadecimal
172-
# integer.
173-
chunk_length = int(self._sock.readline().tobytes().strip(), 16)
176+
while True:
177+
# Read to the newline to get the chunk length. This is a
178+
# hexadecimal integer.
179+
chunk_length = int(self._sock.readline().tobytes().strip(), 16)
180+
data = b''
174181

175-
# If the chunk length is zero, consume the newline and then we're done.
176-
if not chunk_length:
177-
self._sock.readline()
178-
return b''
182+
# If the chunk length is zero, consume the newline and then we're
183+
# done. If we were decompressing data, return the remaining data.
184+
if not chunk_length:
185+
self._sock.readline()
179186

180-
# Then read that many bytes.
181-
data = b''
187+
if decode_content and self._decompressobj:
188+
yield self._decompressobj.flush()
182189

183-
while chunk_length > 0:
184-
chunk = self._sock.recv(chunk_length).tobytes()
185-
data += chunk
186-
chunk_length -= len(chunk)
190+
break
187191

188-
assert chunk_length == 0
192+
# Then read that many bytes.
193+
while chunk_length > 0:
194+
chunk = self._sock.recv(chunk_length).tobytes()
195+
data += chunk
196+
chunk_length -= len(chunk)
189197

190-
# Now, consume the newline.
191-
self._sock.readline()
198+
assert chunk_length == 0
192199

193-
return data
200+
# Now, consume the newline.
201+
self._sock.readline()
202+
203+
# We may need to decode the body.
204+
if decode_content and self._decompressobj and data:
205+
data = self._decompressobj.decompress(data)
206+
207+
yield data
208+
209+
return
194210

195211
def close(self):
196212
"""

test/test_http11.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -402,24 +402,48 @@ def test_basic_chunked_read(self):
402402
b'well', b'well', b'what', b'have', b'we', b'hereabouts'
403403
]
404404

405-
for c in results:
406-
assert r.read_chunked() == c
405+
for c1, c2 in zip(results, r.read_chunked()):
406+
assert c1 == c2
407407

408-
assert not r.read_chunked()
408+
assert not list(r.read_chunked())
409409

410410
def test_chunked_read_of_non_chunked(self):
411411
r = HTTP11Response(200, 'OK', {b'content-length': [b'0']}, None)
412412

413413
with pytest.raises(ChunkedDecodeError):
414-
r.read_chunked()
414+
list(r.read_chunked())
415415

416416
def test_chunked_read_aborts_early(self):
417417
r = HTTP11Response(
418418
200, 'OK', {b'transfer-encoding': [b'chunked']}, None
419419
)
420420

421-
assert not r.read_chunked()
421+
assert not list(r.read_chunked())
422422

423+
def test_response_transparently_decrypts_chunked_gzip(self):
424+
d = DummySocket()
425+
headers = {
426+
b'content-encoding': [b'gzip'],
427+
b'transfer-encoding': [b'chunked'],
428+
}
429+
r = HTTP11Response(200, 'OK', headers, d)
430+
431+
c = zlib_compressobj(wbits=24)
432+
body = c.compress(b'this is test data')
433+
body += c.flush()
434+
435+
data = b''
436+
for index in range(0, len(body), 2):
437+
data += '2\r\n' + body[index:index+2] + '\r\n'
438+
439+
data += b'0\r\n\r\n'
440+
d._buffer = BytesIO(data)
441+
442+
received_body = b''
443+
for chunk in r.read_chunked():
444+
received_body += chunk
445+
446+
assert received_body == b'this is test data'
423447

424448
class DummySocket(object):
425449
def __init__(self):

0 commit comments

Comments
 (0)