Merge: #23539: Set Content-Length to 0 for PUT, POST, and PATCH if body is None.
This commit is contained in:
commit
0a0d20edfb
@ -207,23 +207,33 @@ HTTPConnection Objects
|
|||||||
.. method:: HTTPConnection.request(method, url, body=None, headers={})
|
.. method:: HTTPConnection.request(method, url, body=None, headers={})
|
||||||
|
|
||||||
This will send a request to the server using the HTTP request
|
This will send a request to the server using the HTTP request
|
||||||
method *method* and the selector *url*. If the *body* argument is
|
method *method* and the selector *url*.
|
||||||
present, it should be string or bytes object of data to send after
|
|
||||||
the headers are finished. Strings are encoded as ISO-8859-1, the
|
|
||||||
default charset for HTTP. To use other encodings, pass a bytes
|
|
||||||
object. The Content-Length header is set to the length of the
|
|
||||||
string.
|
|
||||||
|
|
||||||
The *body* may also be an open :term:`file object`, in which case the
|
If *body* is specified, the specified data is sent after the headers are
|
||||||
contents of the file is sent; this file object should support ``fileno()``
|
finished. It may be a string, a :term:`bytes-like object`, an open
|
||||||
and ``read()`` methods. The header Content-Length is automatically set to
|
:term:`file object`, or an iterable of :term:`bytes-like object`\s. If
|
||||||
the length of the file as reported by stat. The *body* argument may also be
|
*body* is a string, it is encoded as ISO-8851-1, the default for HTTP. If
|
||||||
an iterable and Content-Length header should be explicitly provided when the
|
it is a bytes-like object the bytes are sent as is. If it is a :term:`file
|
||||||
body is an iterable.
|
object`, the contents of the file is sent; this file object should support
|
||||||
|
at least the ``read()`` method. If the file object has a ``mode``
|
||||||
|
attribute, the data returned by the ``read()`` method will be encoded as
|
||||||
|
ISO-8851-1 unless the ``mode`` attribute contains the substring ``b``,
|
||||||
|
otherwise the data returned by ``read()`` is sent as is. If *body* is an
|
||||||
|
iterable, the elements of the iterable are sent as is until the iterable is
|
||||||
|
exhausted.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
If *headers* does not contain a Content-Length item, one is added
|
||||||
|
automatically if possible. If *body* is ``None``, the Content-Length header
|
||||||
|
is set to ``0`` for methods that expect a body (``PUT``, ``POST``, and
|
||||||
|
``PATCH``). If *body* is a string or bytes object, the Content-Length
|
||||||
|
header is set to its length. If *body* is a :term:`file object` and it
|
||||||
|
works to call :func:`~os.fstat` on the result of its ``fileno()`` method,
|
||||||
|
then the Content-Length header is set to the ``st_size`` reported by the
|
||||||
|
``fstat`` call. Otherwise no Content-Length header is added.
|
||||||
|
|
||||||
.. versionadded:: 3.2
|
.. versionadded:: 3.2
|
||||||
*body* can now be an iterable.
|
*body* can now be an iterable.
|
||||||
|
|
||||||
|
@ -138,6 +138,10 @@ _MAXHEADERS = 100
|
|||||||
_is_legal_header_name = re.compile(rb'[^:\s][^:\r\n]*').fullmatch
|
_is_legal_header_name = re.compile(rb'[^:\s][^:\r\n]*').fullmatch
|
||||||
_is_illegal_header_value = re.compile(rb'\n(?![ \t])|\r(?![ \t\n])').search
|
_is_illegal_header_value = re.compile(rb'\n(?![ \t])|\r(?![ \t\n])').search
|
||||||
|
|
||||||
|
# We always set the Content-Length header for these methods because some
|
||||||
|
# servers will otherwise respond with a 411
|
||||||
|
_METHODS_EXPECTING_BODY = {'PATCH', 'POST', 'PUT'}
|
||||||
|
|
||||||
|
|
||||||
class HTTPMessage(email.message.Message):
|
class HTTPMessage(email.message.Message):
|
||||||
# XXX The only usage of this method is in
|
# XXX The only usage of this method is in
|
||||||
@ -1068,19 +1072,26 @@ class HTTPConnection:
|
|||||||
"""Send a complete request to the server."""
|
"""Send a complete request to the server."""
|
||||||
self._send_request(method, url, body, headers)
|
self._send_request(method, url, body, headers)
|
||||||
|
|
||||||
def _set_content_length(self, body):
|
def _set_content_length(self, body, method):
|
||||||
# Set the content-length based on the body.
|
# Set the content-length based on the body. If the body is "empty", we
|
||||||
|
# set Content-Length: 0 for methods that expect a body (RFC 7230,
|
||||||
|
# Section 3.3.2). If the body is set for other methods, we set the
|
||||||
|
# header provided we can figure out what the length is.
|
||||||
thelen = None
|
thelen = None
|
||||||
try:
|
method_expects_body = method.upper() in _METHODS_EXPECTING_BODY
|
||||||
thelen = str(len(body))
|
if body is None and method_expects_body:
|
||||||
except TypeError as te:
|
thelen = '0'
|
||||||
# If this is a file-like object, try to
|
elif body is not None:
|
||||||
# fstat its file descriptor
|
|
||||||
try:
|
try:
|
||||||
thelen = str(os.fstat(body.fileno()).st_size)
|
thelen = str(len(body))
|
||||||
except (AttributeError, OSError):
|
except TypeError:
|
||||||
# Don't send a length if this failed
|
# If this is a file-like object, try to
|
||||||
if self.debuglevel > 0: print("Cannot stat!!")
|
# fstat its file descriptor
|
||||||
|
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!!")
|
||||||
|
|
||||||
if thelen is not None:
|
if thelen is not None:
|
||||||
self.putheader('Content-Length', thelen)
|
self.putheader('Content-Length', thelen)
|
||||||
@ -1096,8 +1107,8 @@ class HTTPConnection:
|
|||||||
|
|
||||||
self.putrequest(method, url, **skips)
|
self.putrequest(method, url, **skips)
|
||||||
|
|
||||||
if body is not None and ('content-length' not in header_names):
|
if 'content-length' not in header_names:
|
||||||
self._set_content_length(body)
|
self._set_content_length(body, method)
|
||||||
for hdr, value in headers.items():
|
for hdr, value in headers.items():
|
||||||
self.putheader(hdr, value)
|
self.putheader(hdr, value)
|
||||||
if isinstance(body, str):
|
if isinstance(body, str):
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import errno
|
import errno
|
||||||
from http import client
|
from http import client
|
||||||
import io
|
import io
|
||||||
|
import itertools
|
||||||
import os
|
import os
|
||||||
import array
|
import array
|
||||||
import socket
|
import socket
|
||||||
@ -148,21 +149,59 @@ class HeaderTests(TestCase):
|
|||||||
self.content_length = kv[1].strip()
|
self.content_length = kv[1].strip()
|
||||||
list.append(self, item)
|
list.append(self, item)
|
||||||
|
|
||||||
# POST with empty body
|
# Here, we're testing that methods expecting a body get a
|
||||||
conn = client.HTTPConnection('example.com')
|
# content-length set to zero if the body is empty (either None or '')
|
||||||
conn.sock = FakeSocket(None)
|
bodies = (None, '')
|
||||||
conn._buffer = ContentLengthChecker()
|
methods_with_body = ('PUT', 'POST', 'PATCH')
|
||||||
conn.request('POST', '/', '')
|
for method, body in itertools.product(methods_with_body, bodies):
|
||||||
self.assertEqual(conn._buffer.content_length, b'0',
|
conn = client.HTTPConnection('example.com')
|
||||||
'Header Content-Length not set')
|
conn.sock = FakeSocket(None)
|
||||||
|
conn._buffer = ContentLengthChecker()
|
||||||
|
conn.request(method, '/', body)
|
||||||
|
self.assertEqual(
|
||||||
|
conn._buffer.content_length, b'0',
|
||||||
|
'Header Content-Length incorrect on {}'.format(method)
|
||||||
|
)
|
||||||
|
|
||||||
# PUT request with empty body
|
# For these methods, we make sure that content-length is not set when
|
||||||
conn = client.HTTPConnection('example.com')
|
# the body is None because it might cause unexpected behaviour on the
|
||||||
conn.sock = FakeSocket(None)
|
# server.
|
||||||
conn._buffer = ContentLengthChecker()
|
methods_without_body = (
|
||||||
conn.request('PUT', '/', '')
|
'GET', 'CONNECT', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE',
|
||||||
self.assertEqual(conn._buffer.content_length, b'0',
|
)
|
||||||
'Header Content-Length not set')
|
for method in methods_without_body:
|
||||||
|
conn = client.HTTPConnection('example.com')
|
||||||
|
conn.sock = FakeSocket(None)
|
||||||
|
conn._buffer = ContentLengthChecker()
|
||||||
|
conn.request(method, '/', None)
|
||||||
|
self.assertEqual(
|
||||||
|
conn._buffer.content_length, None,
|
||||||
|
'Header Content-Length set for empty body on {}'.format(method)
|
||||||
|
)
|
||||||
|
|
||||||
|
# If the body is set to '', that's considered to be "present but
|
||||||
|
# empty" rather than "missing", so content length would be set, even
|
||||||
|
# for methods that don't expect a body.
|
||||||
|
for method in methods_without_body:
|
||||||
|
conn = client.HTTPConnection('example.com')
|
||||||
|
conn.sock = FakeSocket(None)
|
||||||
|
conn._buffer = ContentLengthChecker()
|
||||||
|
conn.request(method, '/', '')
|
||||||
|
self.assertEqual(
|
||||||
|
conn._buffer.content_length, b'0',
|
||||||
|
'Header Content-Length incorrect on {}'.format(method)
|
||||||
|
)
|
||||||
|
|
||||||
|
# If the body is set, make sure Content-Length is set.
|
||||||
|
for method in itertools.chain(methods_without_body, methods_with_body):
|
||||||
|
conn = client.HTTPConnection('example.com')
|
||||||
|
conn.sock = FakeSocket(None)
|
||||||
|
conn._buffer = ContentLengthChecker()
|
||||||
|
conn.request(method, '/', ' ')
|
||||||
|
self.assertEqual(
|
||||||
|
conn._buffer.content_length, b'1',
|
||||||
|
'Header Content-Length incorrect on {}'.format(method)
|
||||||
|
)
|
||||||
|
|
||||||
def test_putheader(self):
|
def test_putheader(self):
|
||||||
conn = client.HTTPConnection('example.com')
|
conn = client.HTTPConnection('example.com')
|
||||||
|
@ -1201,6 +1201,7 @@ Sam Rushing
|
|||||||
Mark Russell
|
Mark Russell
|
||||||
Rusty Russell
|
Rusty Russell
|
||||||
Nick Russo
|
Nick Russo
|
||||||
|
James Rutherford
|
||||||
Chris Ryland
|
Chris Ryland
|
||||||
Constantina S.
|
Constantina S.
|
||||||
Patrick Sabin
|
Patrick Sabin
|
||||||
|
@ -23,6 +23,10 @@ Core and Builtins
|
|||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #23539: If body is None, http.client.HTTPConnection.request now sets
|
||||||
|
Content-Length to 0 for PUT, POST, and PATCH headers to avoid 411 errors from
|
||||||
|
some web servers.
|
||||||
|
|
||||||
- Issue #22351: The nntplib.NNTP constructor no longer leaves the connection
|
- Issue #22351: The nntplib.NNTP constructor no longer leaves the connection
|
||||||
and socket open until the garbage collector cleans them up. Patch by
|
and socket open until the garbage collector cleans them up. Patch by
|
||||||
Martin Panter.
|
Martin Panter.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user