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

Skip to content

Commit 7bc0d87

Browse files
committed
Issue3243 - Support iterable bodies in httplib. Patch contributions by Xuanji Li and Chris AtLee.
1 parent 8a60e94 commit 7bc0d87

7 files changed

Lines changed: 112 additions & 17 deletions

File tree

Doc/library/http.client.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -393,14 +393,18 @@ HTTPConnection Objects
393393
string.
394394

395395
The *body* may also be an open :term:`file object`, in which case the
396-
contents of the file is sent; this file object should support
397-
``fileno()`` and ``read()`` methods. The header Content-Length is
398-
automatically set to the length of the file as reported by
399-
stat.
396+
contents of the file is sent; this file object should support ``fileno()``
397+
and ``read()`` methods. The header Content-Length is automatically set to
398+
the length of the file as reported by stat. The *body* argument may also be
399+
an iterable and Contet-Length header should be explicitly provided when the
400+
body is an iterable.
400401

401402
The *headers* argument should be a mapping of extra HTTP
402403
headers to send with the request.
403404

405+
.. versionadded:: 3.2
406+
*body* can be an iterable
407+
404408
.. method:: HTTPConnection.getresponse()
405409

406410
Should be called after a request is sent to get the response from the server.

Doc/library/urllib.request.rst

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ The :mod:`urllib.request` module defines the following functions:
2121
:class:`Request` object.
2222

2323
*data* may be a string specifying additional data to send to the
24-
server, or ``None`` if no such data is needed. Currently HTTP
25-
requests are the only ones that use *data*; the HTTP request will
26-
be a POST instead of a GET when the *data* parameter is provided.
27-
*data* should be a buffer in the standard
24+
server, or ``None`` if no such data is needed. *data* may also be an
25+
iterable object and in that case Content-Length value must be specified in
26+
the headers. Currently HTTP requests are the only ones that use *data*; the
27+
HTTP request will be a POST instead of a GET when the *data* parameter is
28+
provided. *data* should be a buffer in the standard
2829
:mimetype:`application/x-www-form-urlencoded` format. The
29-
:func:`urllib.parse.urlencode` function takes a mapping or sequence
30-
of 2-tuples and returns a string in this format. urllib.request module uses
30+
:func:`urllib.parse.urlencode` function takes a mapping or sequence of
31+
2-tuples and returns a string in this format. urllib.request module uses
3132
HTTP/1.1 and includes ``Connection:close`` header in its HTTP requests.
3233

3334
The optional *timeout* parameter specifies a timeout in seconds for
@@ -76,6 +77,9 @@ The :mod:`urllib.request` module defines the following functions:
7677
HTTPS virtual hosts are now supported if possible (that is, if
7778
:data:`ssl.HAS_SNI` is true).
7879

80+
.. versionadded:: 3.2
81+
*data* can be an iterable object.
82+
7983
.. function:: install_opener(opener)
8084

8185
Install an :class:`OpenerDirector` instance as the default global opener.

Lib/http/client.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
import io
7272
import os
7373
import socket
74+
import collections
7475
from urllib.parse import urlsplit
7576
import warnings
7677

@@ -730,7 +731,11 @@ def close(self):
730731
self.__state = _CS_IDLE
731732

732733
def send(self, data):
733-
"""Send `data' to the server."""
734+
"""Send `data' to the server.
735+
``data`` can be a string object, a bytes object, an array object, a
736+
file-like object that supports a .read() method, or an iterable object.
737+
"""
738+
734739
if self.sock is None:
735740
if self.auto_open:
736741
self.connect()
@@ -762,8 +767,16 @@ def send(self, data):
762767
if encode:
763768
datablock = datablock.encode("iso-8859-1")
764769
self.sock.sendall(datablock)
765-
else:
770+
771+
try:
766772
self.sock.sendall(data)
773+
except TypeError:
774+
if isinstance(data, collections.Iterable):
775+
for d in data:
776+
self.sock.sendall(d)
777+
else:
778+
raise TypeError("data should be byte-like object\
779+
or an iterable, got %r " % type(it))
767780

768781
def _output(self, s):
769782
"""Add a line of output to the current request buffer.

Lib/test/test_httplib.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,22 @@ def test_send(self):
230230
conn.send(io.BytesIO(expected))
231231
self.assertEqual(expected, sock.data)
232232

233+
def test_send_iter(self):
234+
expected = b'GET /foo HTTP/1.1\r\nHost: example.com\r\n' \
235+
b'Accept-Encoding: identity\r\nContent-Length: 11\r\n' \
236+
b'\r\nonetwothree'
237+
238+
def body():
239+
yield b"one"
240+
yield b"two"
241+
yield b"three"
242+
243+
conn = client.HTTPConnection('example.com')
244+
sock = FakeSocket("")
245+
conn.sock = sock
246+
conn.request('GET', '/foo', body(), {'Content-Length': '11'})
247+
self.assertEquals(sock.data, expected)
248+
233249
def test_chunked(self):
234250
chunked_start = (
235251
'HTTP/1.1 200 OK\r\n'

Lib/test/test_urllib2.py

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import io
66
import socket
7+
import array
78

89
import urllib.request
910
from urllib.request import Request, OpenerDirector
@@ -765,7 +766,7 @@ def test_http(self):
765766
o = h.parent = MockOpener()
766767

767768
url = "http://example.com/"
768-
for method, data in [("GET", None), ("POST", "blah")]:
769+
for method, data in [("GET", None), ("POST", b"blah")]:
769770
req = Request(url, data, {"Foo": "bar"})
770771
req.timeout = None
771772
req.add_unredirected_header("Spam", "eggs")
@@ -795,7 +796,7 @@ def test_http(self):
795796

796797
# check adding of standard headers
797798
o.addheaders = [("Spam", "eggs")]
798-
for data in "", None: # POST, GET
799+
for data in b"", None: # POST, GET
799800
req = Request("http://example.com/", data)
800801
r = MockResponse(200, "OK", {}, "")
801802
newreq = h.do_request_(req)
@@ -821,14 +822,58 @@ def test_http(self):
821822
self.assertEqual(req.unredirected_hdrs["Host"], "baz")
822823
self.assertEqual(req.unredirected_hdrs["Spam"], "foo")
823824

825+
# Check iterable body support
826+
def iterable_body():
827+
yield b"one"
828+
yield b"two"
829+
yield b"three"
830+
831+
for headers in {}, {"Content-Length": 11}:
832+
req = Request("http://example.com/", iterable_body(), headers)
833+
if not headers:
834+
# Having an iterable body without a Content-Length should
835+
# raise an exception
836+
self.assertRaises(ValueError, h.do_request_, req)
837+
else:
838+
newreq = h.do_request_(req)
839+
840+
# A file object
841+
842+
"""
843+
file_obj = io.StringIO()
844+
file_obj.write("Something\nSomething\nSomething\n")
845+
846+
for headers in {}, {"Content-Length": 30}:
847+
req = Request("http://example.com/", file_obj, headers)
848+
if not headers:
849+
# Having an iterable body without a Content-Length should
850+
# raise an exception
851+
self.assertRaises(ValueError, h.do_request_, req)
852+
else:
853+
newreq = h.do_request_(req)
854+
self.assertEqual(int(newreq.get_header('Content-length')),30)
855+
856+
file_obj.close()
857+
858+
# array.array Iterable - Content Length is calculated
859+
860+
iterable_array = array.array("I",[1,2,3,4])
861+
862+
for headers in {}, {"Content-Length": 16}:
863+
req = Request("http://example.com/", iterable_array, headers)
864+
newreq = h.do_request_(req)
865+
self.assertEqual(int(newreq.get_header('Content-length')),16)
866+
"""
867+
868+
824869
def test_http_doubleslash(self):
825870
# Checks the presence of any unnecessary double slash in url does not
826871
# break anything. Previously, a double slash directly after the host
827872
# could could cause incorrect parsing.
828873
h = urllib.request.AbstractHTTPHandler()
829874
o = h.parent = MockOpener()
830875

831-
data = ""
876+
data = b""
832877
ds_urls = [
833878
"http://example.com/foo/bar/baz.html",
834879
"http://example.com//foo/bar/baz.html",

Lib/urllib/request.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
import socket
9595
import sys
9696
import time
97+
import collections
9798

9899
from urllib.error import URLError, HTTPError, ContentTooShortError
99100
from urllib.parse import (
@@ -1053,8 +1054,17 @@ def do_request_(self, request):
10531054
'Content-type',
10541055
'application/x-www-form-urlencoded')
10551056
if not request.has_header('Content-length'):
1056-
request.add_unredirected_header(
1057-
'Content-length', '%d' % len(data))
1057+
try:
1058+
mv = memoryview(data)
1059+
except TypeError:
1060+
print(data)
1061+
if isinstance(data, collections.Iterable):
1062+
raise ValueError("Content-Length should be specified \
1063+
for iterable data of type %r %r" % (type(data),
1064+
data))
1065+
else:
1066+
request.add_unredirected_header(
1067+
'Content-length', '%d' % len(mv) * mv.itemsize)
10581068

10591069
sel_host = host
10601070
if request.has_proxy():

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ Core and Builtins
2323
Library
2424
-------
2525

26+
- Issue #3243: Support iterable bodies in httplib. Patch Contributions by
27+
Xuanji Li and Chris AtLee.
28+
2629
- Issue #10611: SystemExit exception will no longer kill a unittest run.
2730

2831
- Issue #9857: It is now possible to skip a test in a setUp, tearDown or clean

0 commit comments

Comments
 (0)