[Python-checkins] cpython (2.7): #23539: Set Content-Length to 0 for PUT, POST, and PATCH if body is None.

r.david.murray python-checkins at python.org
Sun Mar 22 20:19:21 CET 2015


https://hg.python.org/cpython/rev/5c3dc817ffd7
changeset: 95129:5c3dc817ffd7
branch: 2.7
parent: 95124:4502e598fe26
user: R David Murray <rdmurray at bitdance.com>
date: Sun Mar 22 15:15:44 2015 -0400
summary:
 #23539: Set Content-Length to 0 for PUT, POST, and PATCH if body is None.
Some http servers will reject PUT, POST, and PATCH requests if they
do not have a Content-Length header.
Patch by James Rutherford.
files:
 Doc/library/httplib.rst | 13 ++++-
 Lib/httplib.py | 36 +++++++++-----
 Lib/test/test_httplib.py | 67 ++++++++++++++++++++++-----
 Misc/ACKS | 1 +
 Misc/NEWS | 4 +
 5 files changed, 91 insertions(+), 30 deletions(-)
diff --git a/Doc/library/httplib.rst b/Doc/library/httplib.rst
--- a/Doc/library/httplib.rst
+++ b/Doc/library/httplib.rst
@@ -433,9 +433,16 @@
 and the selector *url*. If the *body* argument is present, it should be a
 string of data to send after the headers are finished. Alternatively, it may
 be an open file object, in which case the contents of the file is sent; this
- file object should support ``fileno()`` and ``read()`` methods. The header
- Content-Length is automatically set to the correct value. The *headers*
- argument should be a mapping of extra HTTP headers to send with the request.
+ file object should support ``fileno()`` and ``read()`` methods. The
+ *headers* argument should be a mapping of extra HTTP headers to send with
+ the request.
+
+ If one is not provided in *headers*, a ``Content-Length`` header is added
+ automatically for all methods if the length of the body can be determined,
+ either from the length of the ``str`` representation, or from the reported
+ size of the file on disk. If *body* is ``None`` the header is not set except
+ for methods that expect a body (``PUT``, ``POST``, and ``PATCH``) in which
+ case it is set to ``0``.
 
 .. versionchanged:: 2.6
 *body* can be a file object.
diff --git a/Lib/httplib.py b/Lib/httplib.py
--- a/Lib/httplib.py
+++ b/Lib/httplib.py
@@ -247,6 +247,10 @@
 _is_legal_header_name = re.compile(r'\A[^:\s][^:\r\n]*\Z').match
 _is_illegal_header_value = re.compile(r'\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(mimetools.Message):
 
@@ -1043,19 +1047,25 @@
 """Send a complete request to the server."""
 self._send_request(method, url, body, headers)
 
- def _set_content_length(self, body):
- # Set the content-length based on the body.
+ def _set_content_length(self, body, method):
+ # 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
- try:
- thelen = str(len(body))
- except TypeError, te:
- # If this is a file-like object, try to
- # fstat its file descriptor
+ if body is None and method.upper() in _METHODS_EXPECTING_BODY:
+ thelen = '0'
+ elif body is not None:
 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!!"
+ thelen = str(len(body))
+ except TypeError:
+ # If this is a file-like object, try to
+ # 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:
 self.putheader('Content-Length', thelen)
@@ -1071,8 +1081,8 @@
 
 self.putrequest(method, url, **skips)
 
- if body is not None and 'content-length' not in header_names:
- self._set_content_length(body)
+ if 'content-length' not in header_names:
+ self._set_content_length(body, method)
 for hdr, value in headers.iteritems():
 self.putheader(hdr, value)
 self.endheaders(body)
diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py
--- a/Lib/test/test_httplib.py
+++ b/Lib/test/test_httplib.py
@@ -1,4 +1,5 @@
 import httplib
+import itertools
 import array
 import StringIO
 import socket
@@ -122,21 +123,59 @@
 self.content_length = kv[1].strip()
 list.append(self, item)
 
- # POST with empty body
- conn = httplib.HTTPConnection('example.com')
- conn.sock = FakeSocket(None)
- conn._buffer = ContentLengthChecker()
- conn.request('POST', '/', '')
- self.assertEqual(conn._buffer.content_length, '0',
- 'Header Content-Length not set')
+ # Here, we're testing that methods expecting a body get a
+ # content-length set to zero if the body is empty (either None or '')
+ bodies = (None, '')
+ methods_with_body = ('PUT', 'POST', 'PATCH')
+ for method, body in itertools.product(methods_with_body, bodies):
+ conn = httplib.HTTPConnection('example.com')
+ conn.sock = FakeSocket(None)
+ conn._buffer = ContentLengthChecker()
+ conn.request(method, '/', body)
+ self.assertEqual(
+ conn._buffer.content_length, '0',
+ 'Header Content-Length incorrect on {}'.format(method)
+ )
 
- # PUT request with empty body
- conn = httplib.HTTPConnection('example.com')
- conn.sock = FakeSocket(None)
- conn._buffer = ContentLengthChecker()
- conn.request('PUT', '/', '')
- self.assertEqual(conn._buffer.content_length, '0',
- 'Header Content-Length not set')
+ # For these methods, we make sure that content-length is not set when
+ # the body is None because it might cause unexpected behaviour on the
+ # server.
+ methods_without_body = (
+ 'GET', 'CONNECT', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE',
+ )
+ for method in methods_without_body:
+ conn = httplib.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 = httplib.HTTPConnection('example.com')
+ conn.sock = FakeSocket(None)
+ conn._buffer = ContentLengthChecker()
+ conn.request(method, '/', '')
+ self.assertEqual(
+ conn._buffer.content_length, '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 = httplib.HTTPConnection('example.com')
+ conn.sock = FakeSocket(None)
+ conn._buffer = ContentLengthChecker()
+ conn.request(method, '/', ' ')
+ self.assertEqual(
+ conn._buffer.content_length, '1',
+ 'Header Content-Length incorrect on {}'.format(method)
+ )
 
 def test_putheader(self):
 conn = httplib.HTTPConnection('example.com')
diff --git a/Misc/ACKS b/Misc/ACKS
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1171,6 +1171,7 @@
 Mark Russell
 Rusty Russell
 Nick Russo
+James Rutherford
 Chris Ryland
 Constantina S.
 Patrick Sabin
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -21,6 +21,10 @@
 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 #23136: _strptime now uniformly handles all days in week 0, including
 Dec 30 of previous year. Based on patch by Jim Carroll.
 
-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list

AltStyle によって変換されたページ (->オリジナル) /