Index: Lib/http/client.py =================================================================== --- Lib/http/client.py (revision 86847) +++ Lib/http/client.py (working copy) @@ -763,6 +763,12 @@ if encode: datablock = datablock.encode("iso-8859-1") self.sock.sendall(datablock) + + elif hasattr(data,'__next__'): + if self.debuglevel > 0: print("sending an iterable") + for d in data: + self.sock.sendall(d) + else: self.sock.sendall(data) Index: Lib/urllib/request.py =================================================================== --- Lib/urllib/request.py (revision 86847) +++ Lib/urllib/request.py (working copy) @@ -1053,6 +1053,12 @@ 'Content-type', 'application/x-www-form-urlencoded') if not request.has_header('Content-length'): + # We can't tell ahead of time how much data is in an + # iterable object + if not hasattr(data, '__read__') and hasattr(data, '__next__'): + raise ValueError( + "No Content-Length specified for iterable body") + request.add_unredirected_header( 'Content-length', '%d' % len(data)) Index: Lib/test/test_httplib.py =================================================================== --- Lib/test/test_httplib.py (revision 86847) +++ Lib/test/test_httplib.py (working copy) @@ -229,7 +229,23 @@ sock.data = b'' conn.send(io.BytesIO(expected)) self.assertEqual(expected, sock.data) + + def test_send_iter(self): + expected = b'GET /foo HTTP/1.1\r\nHost: example.com\r\n' \ + b'Accept-Encoding: identity\r\nContent-Length: 11\r\n' \ + b'\r\nonetwothree' + def body(): + yield b"one" + yield b"two" + yield b"three" + + conn = client.HTTPConnection('example.com') + sock = FakeSocket("") + conn.sock = sock + conn.request('GET', '/foo', body(), {'Content-Length': '11'}) + self.assertEquals(sock.data, expected) + def test_chunked(self): chunked_start = ( 'HTTP/1.1 200 OK\r\n' Index: Lib/test/test_urllib2.py =================================================================== --- Lib/test/test_urllib2.py (revision 86847) +++ Lib/test/test_urllib2.py (working copy) @@ -820,7 +820,22 @@ self.assertEqual(req.unredirected_hdrs["Content-type"], "bar") self.assertEqual(req.unredirected_hdrs["Host"], "baz") self.assertEqual(req.unredirected_hdrs["Spam"], "foo") + + # Check iterable body support + def iterable_body(): + yield "one" + yield "two" + yield "three" + for headers in {}, {"Content-Length": 11}: + req = Request("http://example.com/", iterable_body(), headers) + if not headers: + # Having an iterable body without a Content-Length should + # raise an exception + self.assertRaises(ValueError, h.do_request_, req) + else: + newreq = h.do_request_(req) + def test_http_doubleslash(self): # Checks the presence of any unnecessary double slash in url does not # break anything. Previously, a double slash directly after the host