python3.6: http.client.request support chunked_encoding
https://bugs.python.org/issue12319
This commit is contained in:
parent
ed79125c65
commit
7595a5a4b7
@ -849,6 +849,44 @@ class HTTPConnection:
|
|||||||
auto_open = 1
|
auto_open = 1
|
||||||
debuglevel = 0
|
debuglevel = 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_textIO(stream):
|
||||||
|
"""Test whether a file-like object is a text or a binary stream.
|
||||||
|
"""
|
||||||
|
return isinstance(stream, io.TextIOBase)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_content_length(body, method):
|
||||||
|
"""Get the content-length based on the body.
|
||||||
|
|
||||||
|
If the body is None, we set Content-Length: 0 for methods that expect
|
||||||
|
a body (RFC 7230, Section 3.3.2). We also set the Content-Length for
|
||||||
|
any method if the body is a str or bytes-like object and not a file.
|
||||||
|
"""
|
||||||
|
if body is None:
|
||||||
|
# do an explicit check for not None here to distinguish
|
||||||
|
# between unset and set but empty
|
||||||
|
if method.upper() in _METHODS_EXPECTING_BODY:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if hasattr(body, 'read'):
|
||||||
|
# file-like object.
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# does it implement the buffer protocol (bytes, bytearray, array)?
|
||||||
|
mv = memoryview(body)
|
||||||
|
return mv.nbytes
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if isinstance(body, str):
|
||||||
|
return len(body)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
|
def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
|
||||||
source_address=None):
|
source_address=None):
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
@ -1024,7 +1062,22 @@ class HTTPConnection:
|
|||||||
"""
|
"""
|
||||||
self._buffer.append(s)
|
self._buffer.append(s)
|
||||||
|
|
||||||
def _send_output(self, message_body=None):
|
def _read_readable(self, readable):
|
||||||
|
blocksize = 8192
|
||||||
|
if self.debuglevel > 0:
|
||||||
|
print("sendIng a read()able")
|
||||||
|
encode = self._is_textIO(readable)
|
||||||
|
if encode and self.debuglevel > 0:
|
||||||
|
print("encoding file using iso-8859-1")
|
||||||
|
while True:
|
||||||
|
datablock = readable.read(blocksize)
|
||||||
|
if not datablock:
|
||||||
|
break
|
||||||
|
if encode:
|
||||||
|
datablock = datablock.encode("iso-8859-1")
|
||||||
|
yield datablock
|
||||||
|
|
||||||
|
def _send_output(self, message_body=None, encode_chunked=False):
|
||||||
"""Send the currently buffered request and clear the buffer.
|
"""Send the currently buffered request and clear the buffer.
|
||||||
|
|
||||||
Appends an extra \\r\\n to the buffer.
|
Appends an extra \\r\\n to the buffer.
|
||||||
@ -1033,10 +1086,49 @@ class HTTPConnection:
|
|||||||
self._buffer.extend((b"", b""))
|
self._buffer.extend((b"", b""))
|
||||||
msg = b"\r\n".join(self._buffer)
|
msg = b"\r\n".join(self._buffer)
|
||||||
del self._buffer[:]
|
del self._buffer[:]
|
||||||
|
|
||||||
self.send(msg)
|
self.send(msg)
|
||||||
|
|
||||||
if message_body is not None:
|
if message_body is not None:
|
||||||
self.send(message_body)
|
|
||||||
|
# create a consistent interface to message_body
|
||||||
|
if hasattr(message_body, 'read'):
|
||||||
|
# Let file-like take precedence over byte-like. This
|
||||||
|
# is needed to allow the current position of mmap'ed
|
||||||
|
# files to be taken into account.
|
||||||
|
chunks = self._read_readable(message_body)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
# this is solely to check to see if message_body
|
||||||
|
# implements the buffer API. it /would/ be easier
|
||||||
|
# to capture if PyObject_CheckBuffer was exposed
|
||||||
|
# to Python.
|
||||||
|
memoryview(message_body)
|
||||||
|
except TypeError:
|
||||||
|
try:
|
||||||
|
chunks = iter(message_body)
|
||||||
|
except TypeError:
|
||||||
|
raise TypeError("message_body should be a bytes-like "
|
||||||
|
"object or an iterable, got %r"
|
||||||
|
% type(message_body))
|
||||||
|
else:
|
||||||
|
# the object implements the buffer interface and
|
||||||
|
# can be passed directly into socket methods
|
||||||
|
chunks = (message_body,)
|
||||||
|
|
||||||
|
for chunk in chunks:
|
||||||
|
if not chunk:
|
||||||
|
if self.debuglevel > 0:
|
||||||
|
print('Zero length chunk ignored')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if encode_chunked and self._http_vsn == 11:
|
||||||
|
# chunked encoding
|
||||||
|
chunk = '{0:X}\r\n'.format(len(chunk)).encode('ascii') + chunk + b'\r\n'
|
||||||
|
self.send(chunk)
|
||||||
|
|
||||||
|
if encode_chunked and self._http_vsn == 11:
|
||||||
|
# end chunked transfer
|
||||||
|
self.send(b'0\r\n\r\n')
|
||||||
|
|
||||||
def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0):
|
def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0):
|
||||||
"""Send a request to the server.
|
"""Send a request to the server.
|
||||||
@ -1189,24 +1281,23 @@ class HTTPConnection:
|
|||||||
header = header + b': ' + value
|
header = header + b': ' + value
|
||||||
self._output(header)
|
self._output(header)
|
||||||
|
|
||||||
def endheaders(self, message_body=None):
|
def endheaders(self, message_body=None, *, encode_chunked=False):
|
||||||
"""Indicate that the last header line has been sent to the server.
|
"""Indicate that the last header line has been sent to the server.
|
||||||
|
|
||||||
This method sends the request to the server. The optional message_body
|
This method sends the request to the server. The optional message_body
|
||||||
argument can be used to pass a message body associated with the
|
argument can be used to pass a message body associated with the
|
||||||
request. The message body will be sent in the same packet as the
|
request.
|
||||||
message headers if it is a string, otherwise it is sent as a separate
|
|
||||||
packet.
|
|
||||||
"""
|
"""
|
||||||
if self.__state == _CS_REQ_STARTED:
|
if self.__state == _CS_REQ_STARTED:
|
||||||
self.__state = _CS_REQ_SENT
|
self.__state = _CS_REQ_SENT
|
||||||
else:
|
else:
|
||||||
raise CannotSendHeader()
|
raise CannotSendHeader()
|
||||||
self._send_output(message_body)
|
self._send_output(message_body, encode_chunked=encode_chunked)
|
||||||
|
|
||||||
def request(self, method, url, body=None, headers={}):
|
def request(self, method, url, body=None, headers={}, *,
|
||||||
|
encode_chunked=False):
|
||||||
"""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, encode_chunked)
|
||||||
|
|
||||||
def _set_content_length(self, body, method):
|
def _set_content_length(self, body, method):
|
||||||
# Set the content-length based on the body. If the body is "empty", we
|
# Set the content-length based on the body. If the body is "empty", we
|
||||||
@ -1232,9 +1323,9 @@ class HTTPConnection:
|
|||||||
if thelen is not None:
|
if thelen is not None:
|
||||||
self.putheader('Content-Length', thelen)
|
self.putheader('Content-Length', thelen)
|
||||||
|
|
||||||
def _send_request(self, method, url, body, headers):
|
def _send_request(self, method, url, body, headers, encode_chunked):
|
||||||
# Honor explicitly requested Host: and Accept-Encoding: headers.
|
# Honor explicitly requested Host: and Accept-Encoding: headers.
|
||||||
header_names = dict.fromkeys([k.lower() for k in headers])
|
header_names = frozenset(k.lower() for k in headers)
|
||||||
skips = {}
|
skips = {}
|
||||||
if 'host' in header_names:
|
if 'host' in header_names:
|
||||||
skips['skip_host'] = 1
|
skips['skip_host'] = 1
|
||||||
@ -1243,15 +1334,40 @@ class HTTPConnection:
|
|||||||
|
|
||||||
self.putrequest(method, url, **skips)
|
self.putrequest(method, url, **skips)
|
||||||
|
|
||||||
|
# chunked encoding will happen if HTTP/1.1 is used and either
|
||||||
|
# the caller passes encode_chunked=True or the following
|
||||||
|
# conditions hold:
|
||||||
|
# 1. content-length has not been explicitly set
|
||||||
|
# 2. the body is a file or iterable, but not a str or bytes-like
|
||||||
|
# 3. Transfer-Encoding has NOT been explicitly set by the caller
|
||||||
|
|
||||||
if 'content-length' not in header_names:
|
if 'content-length' not in header_names:
|
||||||
self._set_content_length(body, method)
|
# only chunk body if not explicitly set for backwards
|
||||||
|
# compatibility, assuming the client code is already handling the
|
||||||
|
# chunking
|
||||||
|
if 'transfer-encoding' not in header_names:
|
||||||
|
# if content-length cannot be automatically determined, fall
|
||||||
|
# back to chunked encoding
|
||||||
|
encode_chunked = False
|
||||||
|
content_length = self._get_content_length(body, method)
|
||||||
|
if content_length is None:
|
||||||
|
if body is not None:
|
||||||
|
if self.debuglevel > 0:
|
||||||
|
print('Unable to determine size of %r' % body)
|
||||||
|
encode_chunked = True
|
||||||
|
self.putheader('Transfer-Encoding', 'chunked')
|
||||||
|
else:
|
||||||
|
self.putheader('Content-Length', str(content_length))
|
||||||
|
else:
|
||||||
|
encode_chunked = False
|
||||||
|
|
||||||
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):
|
||||||
# RFC 2616 Section 3.7.1 says that text default has a
|
# RFC 2616 Section 3.7.1 says that text default has a
|
||||||
# default charset of iso-8859-1.
|
# default charset of iso-8859-1.
|
||||||
body = _encode(body, 'body')
|
body = _encode(body, 'body')
|
||||||
self.endheaders(body)
|
self.endheaders(body, encode_chunked=encode_chunked)
|
||||||
|
|
||||||
def getresponse(self):
|
def getresponse(self):
|
||||||
"""Get the response from the server.
|
"""Get the response from the server.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import eventlet
|
||||||
from eventlet.support import six
|
from eventlet.support import six
|
||||||
import tests
|
import tests
|
||||||
|
|
||||||
@ -10,3 +11,14 @@ def test_green_http_doesnt_change_original_module():
|
|||||||
|
|
||||||
def test_green_httplib_doesnt_change_original_module():
|
def test_green_httplib_doesnt_change_original_module():
|
||||||
tests.run_isolated('green_httplib_doesnt_change_original_module.py')
|
tests.run_isolated('green_httplib_doesnt_change_original_module.py')
|
||||||
|
|
||||||
|
|
||||||
|
def test_http_request_encode_chunked_kwarg():
|
||||||
|
# https://bugs.python.org/issue12319
|
||||||
|
# As of 2017-01 this test only verifies encode_chunked kwarg is properly accepted.
|
||||||
|
# Stdlib http.client code was copied partially, chunked encoding may not work.
|
||||||
|
from eventlet.green.http import client
|
||||||
|
server_sock = eventlet.listen(('127.0.0.1', 0))
|
||||||
|
addr = server_sock.getsockname()
|
||||||
|
h = client.HTTPConnection(host=addr[0], port=addr[1])
|
||||||
|
h.request('GET', '/', encode_chunked=True)
|
||||||
|
Loading…
Reference in New Issue
Block a user