Kaydet (Commit) 7bc0d872 authored tarafından Senthil Kumaran's avatar Senthil Kumaran

Issue3243 - Support iterable bodies in httplib. Patch contributions by Xuanji Li and Chris AtLee.

üst 8a60e948
...@@ -393,14 +393,18 @@ HTTPConnection Objects ...@@ -393,14 +393,18 @@ HTTPConnection Objects
string. string.
The *body* may also be an open :term:`file object`, in which case the The *body* may also be an open :term:`file object`, in which case the
contents of the file is sent; this file object should support contents of the file is sent; this file object should support ``fileno()``
``fileno()`` and ``read()`` methods. The header Content-Length is and ``read()`` methods. The header Content-Length is automatically set to
automatically set to the length of the file as reported by the length of the file as reported by stat. The *body* argument may also be
stat. an iterable and Contet-Length header should be explicitly provided when the
body is an iterable.
The *headers* argument should be a mapping of extra HTTP The *headers* argument should be a mapping of extra HTTP
headers to send with the request. headers to send with the request.
.. versionadded:: 3.2
*body* can be an iterable
.. method:: HTTPConnection.getresponse() .. method:: HTTPConnection.getresponse()
Should be called after a request is sent to get the response from the server. Should be called after a request is sent to get the response from the server.
......
...@@ -21,13 +21,14 @@ The :mod:`urllib.request` module defines the following functions: ...@@ -21,13 +21,14 @@ The :mod:`urllib.request` module defines the following functions:
:class:`Request` object. :class:`Request` object.
*data* may be a string specifying additional data to send to the *data* may be a string specifying additional data to send to the
server, or ``None`` if no such data is needed. Currently HTTP server, or ``None`` if no such data is needed. *data* may also be an
requests are the only ones that use *data*; the HTTP request will iterable object and in that case Content-Length value must be specified in
be a POST instead of a GET when the *data* parameter is provided. the headers. Currently HTTP requests are the only ones that use *data*; the
*data* should be a buffer in the standard HTTP request will be a POST instead of a GET when the *data* parameter is
provided. *data* should be a buffer in the standard
:mimetype:`application/x-www-form-urlencoded` format. The :mimetype:`application/x-www-form-urlencoded` format. The
:func:`urllib.parse.urlencode` function takes a mapping or sequence :func:`urllib.parse.urlencode` function takes a mapping or sequence of
of 2-tuples and returns a string in this format. urllib.request module uses 2-tuples and returns a string in this format. urllib.request module uses
HTTP/1.1 and includes ``Connection:close`` header in its HTTP requests. HTTP/1.1 and includes ``Connection:close`` header in its HTTP requests.
The optional *timeout* parameter specifies a timeout in seconds for The optional *timeout* parameter specifies a timeout in seconds for
...@@ -76,6 +77,9 @@ The :mod:`urllib.request` module defines the following functions: ...@@ -76,6 +77,9 @@ The :mod:`urllib.request` module defines the following functions:
HTTPS virtual hosts are now supported if possible (that is, if HTTPS virtual hosts are now supported if possible (that is, if
:data:`ssl.HAS_SNI` is true). :data:`ssl.HAS_SNI` is true).
.. versionadded:: 3.2
*data* can be an iterable object.
.. function:: install_opener(opener) .. function:: install_opener(opener)
Install an :class:`OpenerDirector` instance as the default global opener. Install an :class:`OpenerDirector` instance as the default global opener.
......
...@@ -71,6 +71,7 @@ import email.message ...@@ -71,6 +71,7 @@ import email.message
import io import io
import os import os
import socket import socket
import collections
from urllib.parse import urlsplit from urllib.parse import urlsplit
import warnings import warnings
...@@ -730,7 +731,11 @@ class HTTPConnection: ...@@ -730,7 +731,11 @@ class HTTPConnection:
self.__state = _CS_IDLE self.__state = _CS_IDLE
def send(self, data): def send(self, data):
"""Send `data' to the server.""" """Send `data' to the server.
``data`` can be a string object, a bytes object, an array object, a
file-like object that supports a .read() method, or an iterable object.
"""
if self.sock is None: if self.sock is None:
if self.auto_open: if self.auto_open:
self.connect() self.connect()
...@@ -762,8 +767,16 @@ class HTTPConnection: ...@@ -762,8 +767,16 @@ class HTTPConnection:
if encode: if encode:
datablock = datablock.encode("iso-8859-1") datablock = datablock.encode("iso-8859-1")
self.sock.sendall(datablock) self.sock.sendall(datablock)
else:
try:
self.sock.sendall(data) self.sock.sendall(data)
except TypeError:
if isinstance(data, collections.Iterable):
for d in data:
self.sock.sendall(d)
else:
raise TypeError("data should be byte-like object\
or an iterable, got %r " % type(it))
def _output(self, s): def _output(self, s):
"""Add a line of output to the current request buffer. """Add a line of output to the current request buffer.
......
...@@ -230,6 +230,22 @@ class BasicTest(TestCase): ...@@ -230,6 +230,22 @@ class BasicTest(TestCase):
conn.send(io.BytesIO(expected)) conn.send(io.BytesIO(expected))
self.assertEqual(expected, sock.data) 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): def test_chunked(self):
chunked_start = ( chunked_start = (
'HTTP/1.1 200 OK\r\n' 'HTTP/1.1 200 OK\r\n'
......
...@@ -4,6 +4,7 @@ from test import support ...@@ -4,6 +4,7 @@ from test import support
import os import os
import io import io
import socket import socket
import array
import urllib.request import urllib.request
from urllib.request import Request, OpenerDirector from urllib.request import Request, OpenerDirector
...@@ -765,7 +766,7 @@ class HandlerTests(unittest.TestCase): ...@@ -765,7 +766,7 @@ class HandlerTests(unittest.TestCase):
o = h.parent = MockOpener() o = h.parent = MockOpener()
url = "http://example.com/" url = "http://example.com/"
for method, data in [("GET", None), ("POST", "blah")]: for method, data in [("GET", None), ("POST", b"blah")]:
req = Request(url, data, {"Foo": "bar"}) req = Request(url, data, {"Foo": "bar"})
req.timeout = None req.timeout = None
req.add_unredirected_header("Spam", "eggs") req.add_unredirected_header("Spam", "eggs")
...@@ -795,7 +796,7 @@ class HandlerTests(unittest.TestCase): ...@@ -795,7 +796,7 @@ class HandlerTests(unittest.TestCase):
# check adding of standard headers # check adding of standard headers
o.addheaders = [("Spam", "eggs")] o.addheaders = [("Spam", "eggs")]
for data in "", None: # POST, GET for data in b"", None: # POST, GET
req = Request("http://example.com/", data) req = Request("http://example.com/", data)
r = MockResponse(200, "OK", {}, "") r = MockResponse(200, "OK", {}, "")
newreq = h.do_request_(req) newreq = h.do_request_(req)
...@@ -821,6 +822,50 @@ class HandlerTests(unittest.TestCase): ...@@ -821,6 +822,50 @@ class HandlerTests(unittest.TestCase):
self.assertEqual(req.unredirected_hdrs["Host"], "baz") self.assertEqual(req.unredirected_hdrs["Host"], "baz")
self.assertEqual(req.unredirected_hdrs["Spam"], "foo") self.assertEqual(req.unredirected_hdrs["Spam"], "foo")
# Check iterable body support
def iterable_body():
yield b"one"
yield b"two"
yield b"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)
# A file object
"""
file_obj = io.StringIO()
file_obj.write("Something\nSomething\nSomething\n")
for headers in {}, {"Content-Length": 30}:
req = Request("http://example.com/", file_obj, 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)
self.assertEqual(int(newreq.get_header('Content-length')),30)
file_obj.close()
# array.array Iterable - Content Length is calculated
iterable_array = array.array("I",[1,2,3,4])
for headers in {}, {"Content-Length": 16}:
req = Request("http://example.com/", iterable_array, headers)
newreq = h.do_request_(req)
self.assertEqual(int(newreq.get_header('Content-length')),16)
"""
def test_http_doubleslash(self): def test_http_doubleslash(self):
# Checks the presence of any unnecessary double slash in url does not # Checks the presence of any unnecessary double slash in url does not
# break anything. Previously, a double slash directly after the host # break anything. Previously, a double slash directly after the host
...@@ -828,7 +873,7 @@ class HandlerTests(unittest.TestCase): ...@@ -828,7 +873,7 @@ class HandlerTests(unittest.TestCase):
h = urllib.request.AbstractHTTPHandler() h = urllib.request.AbstractHTTPHandler()
o = h.parent = MockOpener() o = h.parent = MockOpener()
data = "" data = b""
ds_urls = [ ds_urls = [
"http://example.com/foo/bar/baz.html", "http://example.com/foo/bar/baz.html",
"http://example.com//foo/bar/baz.html", "http://example.com//foo/bar/baz.html",
......
...@@ -94,6 +94,7 @@ import re ...@@ -94,6 +94,7 @@ import re
import socket import socket
import sys import sys
import time import time
import collections
from urllib.error import URLError, HTTPError, ContentTooShortError from urllib.error import URLError, HTTPError, ContentTooShortError
from urllib.parse import ( from urllib.parse import (
...@@ -1053,8 +1054,17 @@ class AbstractHTTPHandler(BaseHandler): ...@@ -1053,8 +1054,17 @@ class AbstractHTTPHandler(BaseHandler):
'Content-type', 'Content-type',
'application/x-www-form-urlencoded') 'application/x-www-form-urlencoded')
if not request.has_header('Content-length'): if not request.has_header('Content-length'):
request.add_unredirected_header( try:
'Content-length', '%d' % len(data)) mv = memoryview(data)
except TypeError:
print(data)
if isinstance(data, collections.Iterable):
raise ValueError("Content-Length should be specified \
for iterable data of type %r %r" % (type(data),
data))
else:
request.add_unredirected_header(
'Content-length', '%d' % len(mv) * mv.itemsize)
sel_host = host sel_host = host
if request.has_proxy(): if request.has_proxy():
......
...@@ -23,6 +23,9 @@ Core and Builtins ...@@ -23,6 +23,9 @@ Core and Builtins
Library Library
------- -------
- Issue #3243: Support iterable bodies in httplib. Patch Contributions by
Xuanji Li and Chris AtLee.
- Issue #10611: SystemExit exception will no longer kill a unittest run. - Issue #10611: SystemExit exception will no longer kill a unittest run.
- Issue #9857: It is now possible to skip a test in a setUp, tearDown or clean - Issue #9857: It is now possible to skip a test in a setUp, tearDown or clean
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment