bpo-35153: Add headers parameter to xmlrpc.client.ServerProxy (GH-10308)

Allow to add HTTP headers to XML-RPC requests sent to the server.
This commit is contained in:
Cédric Krier 2019-02-19 17:18:50 +01:00 committed by Victor Stinner
parent 513e9b4425
commit beda52ed36
4 changed files with 86 additions and 11 deletions

View file

@ -34,10 +34,7 @@ between conformable Python objects and XML on the wire.
.. class:: ServerProxy(uri, transport=None, encoding=None, verbose=False, \ .. class:: ServerProxy(uri, transport=None, encoding=None, verbose=False, \
allow_none=False, use_datetime=False, \ allow_none=False, use_datetime=False, \
use_builtin_types=False, *, context=None) use_builtin_types=False, *, headers=(), context=None)
.. versionchanged:: 3.3
The *use_builtin_types* flag was added.
A :class:`ServerProxy` instance is an object that manages communication with a A :class:`ServerProxy` instance is an object that manages communication with a
remote XML-RPC server. The required first argument is a URI (Uniform Resource remote XML-RPC server. The required first argument is a URI (Uniform Resource
@ -59,9 +56,18 @@ between conformable Python objects and XML on the wire.
presented as :class:`bytes` objects; this flag is false by default. presented as :class:`bytes` objects; this flag is false by default.
:class:`datetime.datetime`, :class:`bytes` and :class:`bytearray` objects :class:`datetime.datetime`, :class:`bytes` and :class:`bytearray` objects
may be passed to calls. may be passed to calls.
The *headers* parameter is an optional sequence of HTTP headers to send with
each request, expressed as a sequence of 2-tuples representing the header
name and value. (e.g. `[('Header-Name', 'value')]`).
The obsolete *use_datetime* flag is similar to *use_builtin_types* but it The obsolete *use_datetime* flag is similar to *use_builtin_types* but it
applies only to date/time values. applies only to date/time values.
.. versionchanged:: 3.3
The *use_builtin_types* flag was added.
.. versionchanged:: 3.8
The *headers* parameter was added.
Both the HTTP and HTTPS transports support the URL syntax extension for HTTP Both the HTTP and HTTPS transports support the URL syntax extension for HTTP
Basic Authentication: ``http://user:pass@host:port/path``. The ``user:pass`` Basic Authentication: ``http://user:pass@host:port/path``. The ``user:pass``
portion will be base64-encoded as an HTTP 'Authorization' header, and sent to portion will be base64-encoded as an HTTP 'Authorization' header, and sent to

View file

@ -1170,6 +1170,67 @@ def test_gzip_decode_limit(self):
xmlrpclib.gzip_decode(encoded, max_decode=-1) xmlrpclib.gzip_decode(encoded, max_decode=-1)
class HeadersServerTestCase(BaseServerTestCase):
class RequestHandler(xmlrpc.server.SimpleXMLRPCRequestHandler):
test_headers = None
def do_POST(self):
self.__class__.test_headers = self.headers
return super().do_POST()
requestHandler = RequestHandler
standard_headers = [
'Host', 'Accept-Encoding', 'Content-Type', 'User-Agent',
'Content-Length']
def setUp(self):
self.RequestHandler.test_headers = None
return super().setUp()
def assertContainsAdditionalHeaders(self, headers, additional):
expected_keys = sorted(self.standard_headers + list(additional.keys()))
self.assertListEqual(sorted(headers.keys()), expected_keys)
for key, value in additional.items():
self.assertEqual(headers.get(key), value)
def test_header(self):
p = xmlrpclib.ServerProxy(URL, headers=[('X-Test', 'foo')])
self.assertEqual(p.pow(6, 8), 6**8)
headers = self.RequestHandler.test_headers
self.assertContainsAdditionalHeaders(headers, {'X-Test': 'foo'})
def test_header_many(self):
p = xmlrpclib.ServerProxy(
URL, headers=[('X-Test', 'foo'), ('X-Test-Second', 'bar')])
self.assertEqual(p.pow(6, 8), 6**8)
headers = self.RequestHandler.test_headers
self.assertContainsAdditionalHeaders(
headers, {'X-Test': 'foo', 'X-Test-Second': 'bar'})
def test_header_empty(self):
p = xmlrpclib.ServerProxy(URL, headers=[])
self.assertEqual(p.pow(6, 8), 6**8)
headers = self.RequestHandler.test_headers
self.assertContainsAdditionalHeaders(headers, {})
def test_header_tuple(self):
p = xmlrpclib.ServerProxy(URL, headers=(('X-Test', 'foo'),))
self.assertEqual(p.pow(6, 8), 6**8)
headers = self.RequestHandler.test_headers
self.assertContainsAdditionalHeaders(headers, {'X-Test': 'foo'})
def test_header_items(self):
p = xmlrpclib.ServerProxy(URL, headers={'X-Test': 'foo'}.items())
self.assertEqual(p.pow(6, 8), 6**8)
headers = self.RequestHandler.test_headers
self.assertContainsAdditionalHeaders(headers, {'X-Test': 'foo'})
#Test special attributes of the ServerProxy object #Test special attributes of the ServerProxy object
class ServerProxyTestCase(unittest.TestCase): class ServerProxyTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
@ -1396,7 +1457,7 @@ def test_main():
BinaryTestCase, FaultTestCase, UseBuiltinTypesTestCase, BinaryTestCase, FaultTestCase, UseBuiltinTypesTestCase,
SimpleServerTestCase, SimpleServerEncodingTestCase, SimpleServerTestCase, SimpleServerEncodingTestCase,
KeepaliveServerTestCase1, KeepaliveServerTestCase2, KeepaliveServerTestCase1, KeepaliveServerTestCase2,
GzipServerTestCase, GzipUtilTestCase, GzipServerTestCase, GzipUtilTestCase, HeadersServerTestCase,
MultiPathServerTestCase, ServerProxyTestCase, FailingServerTestCase, MultiPathServerTestCase, ServerProxyTestCase, FailingServerTestCase,
CGIHandlerTestCase, SimpleXMLRPCDispatcherTestCase) CGIHandlerTestCase, SimpleXMLRPCDispatcherTestCase)

View file

@ -1131,10 +1131,12 @@ class Transport:
# that they can decode such a request # that they can decode such a request
encode_threshold = None #None = don't encode encode_threshold = None #None = don't encode
def __init__(self, use_datetime=False, use_builtin_types=False): def __init__(self, use_datetime=False, use_builtin_types=False,
*, headers=()):
self._use_datetime = use_datetime self._use_datetime = use_datetime
self._use_builtin_types = use_builtin_types self._use_builtin_types = use_builtin_types
self._connection = (None, None) self._connection = (None, None)
self._headers = list(headers)
self._extra_headers = [] self._extra_headers = []
## ##
@ -1265,7 +1267,7 @@ def close(self):
def send_request(self, host, handler, request_body, debug): def send_request(self, host, handler, request_body, debug):
connection = self.make_connection(host) connection = self.make_connection(host)
headers = self._extra_headers[:] headers = self._headers + self._extra_headers
if debug: if debug:
connection.set_debuglevel(1) connection.set_debuglevel(1)
if self.accept_gzip_encoding and gzip: if self.accept_gzip_encoding and gzip:
@ -1347,9 +1349,11 @@ def parse_response(self, response):
class SafeTransport(Transport): class SafeTransport(Transport):
"""Handles an HTTPS transaction to an XML-RPC server.""" """Handles an HTTPS transaction to an XML-RPC server."""
def __init__(self, use_datetime=False, use_builtin_types=False, *, def __init__(self, use_datetime=False, use_builtin_types=False,
context=None): *, headers=(), context=None):
super().__init__(use_datetime=use_datetime, use_builtin_types=use_builtin_types) super().__init__(use_datetime=use_datetime,
use_builtin_types=use_builtin_types,
headers=headers)
self.context = context self.context = context
# FIXME: mostly untested # FIXME: mostly untested
@ -1409,7 +1413,7 @@ class ServerProxy:
def __init__(self, uri, transport=None, encoding=None, verbose=False, def __init__(self, uri, transport=None, encoding=None, verbose=False,
allow_none=False, use_datetime=False, use_builtin_types=False, allow_none=False, use_datetime=False, use_builtin_types=False,
*, context=None): *, headers=(), context=None):
# establish a "logical" server connection # establish a "logical" server connection
# get the url # get the url
@ -1429,6 +1433,7 @@ def __init__(self, uri, transport=None, encoding=None, verbose=False,
extra_kwargs = {} extra_kwargs = {}
transport = handler(use_datetime=use_datetime, transport = handler(use_datetime=use_datetime,
use_builtin_types=use_builtin_types, use_builtin_types=use_builtin_types,
headers=headers,
**extra_kwargs) **extra_kwargs)
self.__transport = transport self.__transport = transport

View file

@ -0,0 +1,3 @@
Add *headers* optional keyword-only parameter to
:class:`xmlrpc.client.ServerProxy`, :class:`xmlrpc.client.Transport` and
:class:`xmlrpc.client.SafeTransport`. Patch by Cédric Krier.