diff -r e548ab4ce71d Doc/library/http.client.rst --- a/Doc/library/http.client.rst Mon Feb 09 19:49:00 2015 +0000 +++ b/Doc/library/http.client.rst Thu Feb 12 21:38:47 2015 -0800 @@ -212,8 +212,10 @@ contents of the file is sent; this file object should support ``fileno()`` and ``read()`` methods. The header Content-Length is automatically set to the length of the file as reported by stat. The *body* argument may also be - an iterable and Content-Length header should be explicitly provided when the - body is an iterable. + an iterable. If the iterable is a tuple or list and all elements are bytes + objects, the Content-Length will automatically be set if not already + supplied in the request headers. In all other iterable cases, the + Content-Length header should be explicitly provided. The *headers* argument should be a mapping of extra HTTP headers to send with the request. @@ -221,6 +223,11 @@ .. versionadded:: 3.2 *body* can now be an iterable. + .. versionadded:: 3.5 + The Content-Length header will be set when *body* is a list or tuple + comprised only of bytes objects. + + .. method:: HTTPConnection.getresponse() Should be called after a request is sent to get the response from the server. diff -r e548ab4ce71d Lib/http/client.py --- a/Lib/http/client.py Mon Feb 09 19:49:00 2015 +0000 +++ b/Lib/http/client.py Thu Feb 12 21:38:47 2015 -0800 @@ -841,6 +841,8 @@ except TypeError: if isinstance(data, collections.Iterable): for d in data: + if hasattr(d, 'encode'): + d = d.encode('iso-8859-1') self.sock.sendall(d) else: raise TypeError("data should be a bytes-like object " @@ -1031,20 +1033,29 @@ def _set_content_length(self, body): # Set the content-length based on the body. - thelen = None - try: - thelen = str(len(body)) - except TypeError as te: - # If this is a file-like object, try to - # fstat its file descriptor + size = None + if isinstance(body, (list, tuple)): + # there are a couple of ways to do this: only calculate the + # content-length if all elements in a list or tuple are bytes, or + # worst case, keep a copy of the entire encoded body in memory and + # pass it along to send(). as the former is the least intrusive + # in terms of memory (iterables are encoded on the fly in + # send()), that's the path taken here. + if all(isinstance(line, bytes) for line in body): + size = sum(len(line) for line in body) + else: try: - thelen = str(os.fstat(body.fileno()).st_size) - except (AttributeError, OSError): - # Don't send a length if this failed - if self.debuglevel > 0: print("Cannot stat!!") + size = len(body) + except TypeError: + try: + size = os.fstat(body.fileno()).st_size + except (AttributeError, OSError): + if self.debuglevel > 0: + print("Cannot stat!!") + size = None - if thelen is not None: - self.putheader('Content-Length', thelen) + if size is not None: + self.putheader('Content-Length', size) def _send_request(self, method, url, body, headers): # Honor explicitly requested Host: and Accept-Encoding: headers. diff -r e548ab4ce71d Lib/test/test_httplib.py --- a/Lib/test/test_httplib.py Mon Feb 09 19:49:00 2015 +0000 +++ b/Lib/test/test_httplib.py Thu Feb 12 21:38:47 2015 -0800 @@ -1161,7 +1161,6 @@ def setUp(self): self.conn = client.HTTPConnection('example.com') self.conn.sock = self.sock = FakeSocket("") - self.conn.sock = self.sock def get_headers_and_fp(self): f = io.BytesIO(self.sock.data) @@ -1226,6 +1225,25 @@ self.assertEqual("5", message.get("content-length")) self.assertEqual(b'body\xc1', f.read()) + def test_list_body(self): + cases = ( + ([b'foo', b'bar'], b'foobar', '6'), + ((b'foo', b'bar'), b'foobar', '6'), + ((b'foo', 'bar'), b'foobar', None), + ([b'foo', 'bar'], b'foobar', None), + ) + with self.subTest(): + for body, expected, expected_len in cases: + self.conn = client.HTTPConnection('example.com') + self.conn.sock = self.sock = FakeSocket('') + + self.conn.request('PUT', '/url', body) + msg, f = self.get_headers_and_fp() + self.assertEqual('text/plain', msg.get_content_type()) + self.assertIsNone(msg.get_charset()) + self.assertEqual(expected_len, msg.get('content-length')) + self.assertEqual(expected, f.read()) + class HTTPResponseTest(TestCase):