Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b353c12

Browse files
committed
Issue #4631: Fix urlopen() result when an HTTP response uses chunked encoding.
1 parent 651453a commit b353c12

9 files changed

Lines changed: 50 additions & 21 deletions

File tree

Lib/http/client.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ def parse_headers(fp):
249249

250250
return email.parser.Parser(_class=HTTPMessage).parsestr(hstring)
251251

252-
class HTTPResponse:
252+
class HTTPResponse(io.RawIOBase):
253253

254254
# strict: If true, raise BadStatusLine if the status line can't be
255255
# parsed as a valid HTTP/1.0 or 1.1 status line. By default it is
@@ -471,8 +471,6 @@ def isclosed(self):
471471
# called, meaning self.isclosed() is meaningful.
472472
return self.fp is None
473473

474-
# XXX It would be nice to have readline and __iter__ for this, too.
475-
476474
def read(self, amt=None):
477475
if self.fp is None:
478476
return b""
@@ -585,6 +583,9 @@ def _safe_read(self, amt):
585583
amt -= len(chunk)
586584
return b"".join(s)
587585

586+
def fileno(self):
587+
return self.fp.fileno()
588+
588589
def getheader(self, name, default=None):
589590
if self.msg is None:
590591
raise ResponseNotReady()
@@ -596,6 +597,11 @@ def getheaders(self):
596597
raise ResponseNotReady()
597598
return list(self.msg.items())
598599

600+
# We override IOBase.__iter__ so that it doesn't check for closed-ness
601+
602+
def __iter__(self):
603+
return self
604+
599605

600606
class HTTPConnection:
601607

Lib/io.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,6 @@ def readline(self, limit: int = -1) -> bytes:
491491
terminator(s) recognized.
492492
"""
493493
# For backwards compatibility, a (slowish) readline().
494-
self._checkClosed()
495494
if hasattr(self, "peek"):
496495
def nreadahead():
497496
readahead = self.peek(1)
@@ -533,7 +532,6 @@ def readlines(self, hint=None):
533532
lines will be read if the total size (in bytes/characters) of all
534533
lines so far exceeds hint.
535534
"""
536-
self._checkClosed()
537535
if hint is None or hint <= 0:
538536
return list(self)
539537
n = 0

Lib/test/test_httplib.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ def readline(self, length=None):
4242
raise AssertionError('caller tried to read past EOF')
4343
return data
4444

45-
4645
class HeaderTests(TestCase):
4746
def test_auto_headers(self):
4847
# Some headers are added automatically, but should not be added by
@@ -245,7 +244,6 @@ def testTimeoutAttribute(self):
245244
self.assertEqual(httpConn.sock.gettimeout(), 30)
246245
httpConn.close()
247246

248-
249247
class HTTPSTimeoutTest(TestCase):
250248
# XXX Here should be tests for HTTPS, there isn't any right now!
251249

@@ -257,7 +255,7 @@ def test_attributes(self):
257255

258256
def test_main(verbose=None):
259257
support.run_unittest(HeaderTests, OfflineTest, BasicTest, TimeoutTest,
260-
HTTPSTimeoutTest)
258+
HTTPSTimeoutTest)
261259

262260
if __name__ == '__main__':
263261
test_main()

Lib/test/test_io.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,6 +1363,7 @@ def test_io_after_close(self):
13631363
self.assertRaises(ValueError, f.fileno)
13641364
self.assertRaises(ValueError, f.isatty)
13651365
self.assertRaises(ValueError, f.__iter__)
1366+
self.assertRaises(ValueError, next, f)
13661367
if hasattr(f, "peek"):
13671368
self.assertRaises(ValueError, f.peek, 1)
13681369
self.assertRaises(ValueError, f.read)

Lib/test/test_urllib2_localnet.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def send_head(self):
310310
self.send_response(response_code)
311311

312312
for (header, value) in headers:
313-
self.send_header(header, value % self.port)
313+
self.send_header(header, value % {'port':self.port})
314314
if body:
315315
self.send_header("Content-type", "text/plain")
316316
self.end_headers()
@@ -341,10 +341,17 @@ def tearDown(self):
341341
self.server.stop()
342342

343343
def urlopen(self, url, data=None):
344+
l = []
344345
f = urllib.request.urlopen(url, data)
345-
result = f.read()
346-
f.close()
347-
return result
346+
try:
347+
# Exercise various methods
348+
l.extend(f.readlines(200))
349+
l.append(f.readline())
350+
l.append(f.read(1024))
351+
l.append(f.read())
352+
finally:
353+
f.close()
354+
return b"".join(l)
348355

349356
def start_server(self, responses=None):
350357
if responses is None:
@@ -361,7 +368,8 @@ def start_server(self, responses=None):
361368
def test_redirection(self):
362369
expected_response = b"We got here..."
363370
responses = [
364-
(302, [("Location", "http://localhost:%s/somewhere_else")], ""),
371+
(302, [("Location", "http://localhost:%(port)s/somewhere_else")],
372+
""),
365373
(200, [], expected_response)
366374
]
367375

@@ -370,6 +378,20 @@ def test_redirection(self):
370378
self.assertEquals(data, expected_response)
371379
self.assertEquals(handler.requests, ["/", "/somewhere_else"])
372380

381+
def test_chunked(self):
382+
expected_response = b"hello world"
383+
chunked_start = (
384+
b'a\r\n'
385+
b'hello worl\r\n'
386+
b'1\r\n'
387+
b'd\r\n'
388+
b'0\r\n'
389+
)
390+
response = [(200, [("Transfer-Encoding", "chunked")], chunked_start)]
391+
handler = self.start_server(response)
392+
data = self.urlopen("http://localhost:%s/" % handler.port)
393+
self.assertEquals(data, expected_response)
394+
373395
def test_404(self):
374396
expected_response = b"Bad bad bad..."
375397
handler = self.start_server([(404, [], expected_response)])

Lib/test/test_urllib2net.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ class TimeoutTest(unittest.TestCase):
195195
def test_http_basic(self):
196196
self.assertTrue(socket.getdefaulttimeout() is None)
197197
u = _urlopen_with_retry("http://www.python.org")
198-
self.assertTrue(u.fp.raw._sock.gettimeout() is None)
198+
self.assertTrue(u.fp.fp.raw._sock.gettimeout() is None)
199199

200200
def test_http_default_timeout(self):
201201
self.assertTrue(socket.getdefaulttimeout() is None)
@@ -204,7 +204,7 @@ def test_http_default_timeout(self):
204204
u = _urlopen_with_retry("http://www.python.org")
205205
finally:
206206
socket.setdefaulttimeout(None)
207-
self.assertEqual(u.fp.raw._sock.gettimeout(), 60)
207+
self.assertEqual(u.fp.fp.raw._sock.gettimeout(), 60)
208208

209209
def test_http_no_timeout(self):
210210
self.assertTrue(socket.getdefaulttimeout() is None)
@@ -213,11 +213,11 @@ def test_http_no_timeout(self):
213213
u = _urlopen_with_retry("http://www.python.org", timeout=None)
214214
finally:
215215
socket.setdefaulttimeout(None)
216-
self.assertTrue(u.fp.raw._sock.gettimeout() is None)
216+
self.assertTrue(u.fp.fp.raw._sock.gettimeout() is None)
217217

218218
def test_http_timeout(self):
219219
u = _urlopen_with_retry("http://www.python.org", timeout=120)
220-
self.assertEqual(u.fp.raw._sock.gettimeout(), 120)
220+
self.assertEqual(u.fp.fp.raw._sock.gettimeout(), 120)
221221

222222
FTP_HOST = "ftp://ftp.mirror.nl/pub/mirror/gnu/"
223223

Lib/urllib/request.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,6 @@ def _call_chain(self, chain, kind, meth_name, *args):
333333
handlers = chain.get(kind, ())
334334
for handler in handlers:
335335
func = getattr(handler, meth_name)
336-
337336
result = func(*args)
338337
if result is not None:
339338
return result
@@ -1070,7 +1069,8 @@ def do_open(self, http_class, req):
10701069
except socket.error as err: # XXX what error?
10711070
raise URLError(err)
10721071

1073-
resp = addinfourl(r.fp, r.msg, req.get_full_url())
1072+
## resp = addinfourl(r.fp, r.msg, req.get_full_url())
1073+
resp = addinfourl(r, r.msg, req.get_full_url())
10741074
resp.code = r.status
10751075
resp.msg = r.reason
10761076
return resp
@@ -1606,7 +1606,7 @@ def _open_generic_http(self, connection_factory, url, data):
16061606
# According to RFC 2616, "2xx" code indicates that the client's
16071607
# request was successfully received, understood, and accepted.
16081608
if 200 <= response.status < 300:
1609-
return addinfourl(response.fp, response.msg, "http:" + url,
1609+
return addinfourl(response, response.msg, "http:" + url,
16101610
response.status)
16111611
else:
16121612
return self.http_error(

Lib/urllib/response.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ def __init__(self, fp):
1717
self.read = self.fp.read
1818
self.readline = self.fp.readline
1919
# TODO(jhylton): Make sure an object with readlines() is also iterable
20-
if hasattr(self.fp, "readlines"): self.readlines = self.fp.readlines
20+
if hasattr(self.fp, "readlines"):
21+
self.readlines = self.fp.readlines
2122
if hasattr(self.fp, "fileno"):
2223
self.fileno = self.fp.fileno
2324
else:

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ Core and Builtins
163163
Library
164164
-------
165165

166+
- Issue #4631: Fix urlopen() result when an HTTP response uses chunked
167+
encoding.
168+
166169
- Issue #5203: Fixed ctypes segfaults when passing a unicode string to a
167170
function without argtypes (only occurs if HAVE_USABLE_WCHAR_T is false).
168171

0 commit comments

Comments
 (0)