Skip to content

Commit beda52e

Browse files
cedkvstinner
authored andcommitted
bpo-35153: Add headers parameter to xmlrpc.client.ServerProxy (GH-10308)
Allow to add HTTP headers to XML-RPC requests sent to the server.
1 parent 513e9b4 commit beda52e

File tree

4 files changed

+86
-11
lines changed

4 files changed

+86
-11
lines changed

Doc/library/xmlrpc.client.rst

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,7 @@ between conformable Python objects and XML on the wire.
3434

3535
.. class:: ServerProxy(uri, transport=None, encoding=None, verbose=False, \
3636
allow_none=False, use_datetime=False, \
37-
use_builtin_types=False, *, context=None)
38-
39-
.. versionchanged:: 3.3
40-
The *use_builtin_types* flag was added.
37+
use_builtin_types=False, *, headers=(), context=None)
4138
4239
A :class:`ServerProxy` instance is an object that manages communication with a
4340
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.
5956
presented as :class:`bytes` objects; this flag is false by default.
6057
:class:`datetime.datetime`, :class:`bytes` and :class:`bytearray` objects
6158
may be passed to calls.
59+
The *headers* parameter is an optional sequence of HTTP headers to send with
60+
each request, expressed as a sequence of 2-tuples representing the header
61+
name and value. (e.g. `[('Header-Name', 'value')]`).
6262
The obsolete *use_datetime* flag is similar to *use_builtin_types* but it
6363
applies only to date/time values.
6464

65+
.. versionchanged:: 3.3
66+
The *use_builtin_types* flag was added.
67+
68+
.. versionchanged:: 3.8
69+
The *headers* parameter was added.
70+
6571
Both the HTTP and HTTPS transports support the URL syntax extension for HTTP
6672
Basic Authentication: ``http://user:pass@host:port/path``. The ``user:pass``
6773
portion will be base64-encoded as an HTTP 'Authorization' header, and sent to

Lib/test/test_xmlrpc.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1170,6 +1170,67 @@ def test_gzip_decode_limit(self):
11701170
xmlrpclib.gzip_decode(encoded, max_decode=-1)
11711171

11721172

1173+
class HeadersServerTestCase(BaseServerTestCase):
1174+
class RequestHandler(xmlrpc.server.SimpleXMLRPCRequestHandler):
1175+
test_headers = None
1176+
1177+
def do_POST(self):
1178+
self.__class__.test_headers = self.headers
1179+
return super().do_POST()
1180+
requestHandler = RequestHandler
1181+
standard_headers = [
1182+
'Host', 'Accept-Encoding', 'Content-Type', 'User-Agent',
1183+
'Content-Length']
1184+
1185+
def setUp(self):
1186+
self.RequestHandler.test_headers = None
1187+
return super().setUp()
1188+
1189+
def assertContainsAdditionalHeaders(self, headers, additional):
1190+
expected_keys = sorted(self.standard_headers + list(additional.keys()))
1191+
self.assertListEqual(sorted(headers.keys()), expected_keys)
1192+
1193+
for key, value in additional.items():
1194+
self.assertEqual(headers.get(key), value)
1195+
1196+
def test_header(self):
1197+
p = xmlrpclib.ServerProxy(URL, headers=[('X-Test', 'foo')])
1198+
self.assertEqual(p.pow(6, 8), 6**8)
1199+
1200+
headers = self.RequestHandler.test_headers
1201+
self.assertContainsAdditionalHeaders(headers, {'X-Test': 'foo'})
1202+
1203+
def test_header_many(self):
1204+
p = xmlrpclib.ServerProxy(
1205+
URL, headers=[('X-Test', 'foo'), ('X-Test-Second', 'bar')])
1206+
self.assertEqual(p.pow(6, 8), 6**8)
1207+
1208+
headers = self.RequestHandler.test_headers
1209+
self.assertContainsAdditionalHeaders(
1210+
headers, {'X-Test': 'foo', 'X-Test-Second': 'bar'})
1211+
1212+
def test_header_empty(self):
1213+
p = xmlrpclib.ServerProxy(URL, headers=[])
1214+
self.assertEqual(p.pow(6, 8), 6**8)
1215+
1216+
headers = self.RequestHandler.test_headers
1217+
self.assertContainsAdditionalHeaders(headers, {})
1218+
1219+
def test_header_tuple(self):
1220+
p = xmlrpclib.ServerProxy(URL, headers=(('X-Test', 'foo'),))
1221+
self.assertEqual(p.pow(6, 8), 6**8)
1222+
1223+
headers = self.RequestHandler.test_headers
1224+
self.assertContainsAdditionalHeaders(headers, {'X-Test': 'foo'})
1225+
1226+
def test_header_items(self):
1227+
p = xmlrpclib.ServerProxy(URL, headers={'X-Test': 'foo'}.items())
1228+
self.assertEqual(p.pow(6, 8), 6**8)
1229+
1230+
headers = self.RequestHandler.test_headers
1231+
self.assertContainsAdditionalHeaders(headers, {'X-Test': 'foo'})
1232+
1233+
11731234
#Test special attributes of the ServerProxy object
11741235
class ServerProxyTestCase(unittest.TestCase):
11751236
def setUp(self):
@@ -1396,7 +1457,7 @@ def test_main():
13961457
BinaryTestCase, FaultTestCase, UseBuiltinTypesTestCase,
13971458
SimpleServerTestCase, SimpleServerEncodingTestCase,
13981459
KeepaliveServerTestCase1, KeepaliveServerTestCase2,
1399-
GzipServerTestCase, GzipUtilTestCase,
1460+
GzipServerTestCase, GzipUtilTestCase, HeadersServerTestCase,
14001461
MultiPathServerTestCase, ServerProxyTestCase, FailingServerTestCase,
14011462
CGIHandlerTestCase, SimpleXMLRPCDispatcherTestCase)
14021463

Lib/xmlrpc/client.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,10 +1131,12 @@ class Transport:
11311131
# that they can decode such a request
11321132
encode_threshold = None #None = don't encode
11331133

1134-
def __init__(self, use_datetime=False, use_builtin_types=False):
1134+
def __init__(self, use_datetime=False, use_builtin_types=False,
1135+
*, headers=()):
11351136
self._use_datetime = use_datetime
11361137
self._use_builtin_types = use_builtin_types
11371138
self._connection = (None, None)
1139+
self._headers = list(headers)
11381140
self._extra_headers = []
11391141

11401142
##
@@ -1265,7 +1267,7 @@ def close(self):
12651267

12661268
def send_request(self, host, handler, request_body, debug):
12671269
connection = self.make_connection(host)
1268-
headers = self._extra_headers[:]
1270+
headers = self._headers + self._extra_headers
12691271
if debug:
12701272
connection.set_debuglevel(1)
12711273
if self.accept_gzip_encoding and gzip:
@@ -1347,9 +1349,11 @@ def parse_response(self, response):
13471349
class SafeTransport(Transport):
13481350
"""Handles an HTTPS transaction to an XML-RPC server."""
13491351

1350-
def __init__(self, use_datetime=False, use_builtin_types=False, *,
1351-
context=None):
1352-
super().__init__(use_datetime=use_datetime, use_builtin_types=use_builtin_types)
1352+
def __init__(self, use_datetime=False, use_builtin_types=False,
1353+
*, headers=(), context=None):
1354+
super().__init__(use_datetime=use_datetime,
1355+
use_builtin_types=use_builtin_types,
1356+
headers=headers)
13531357
self.context = context
13541358

13551359
# FIXME: mostly untested
@@ -1409,7 +1413,7 @@ class ServerProxy:
14091413

14101414
def __init__(self, uri, transport=None, encoding=None, verbose=False,
14111415
allow_none=False, use_datetime=False, use_builtin_types=False,
1412-
*, context=None):
1416+
*, headers=(), context=None):
14131417
# establish a "logical" server connection
14141418

14151419
# get the url
@@ -1429,6 +1433,7 @@ def __init__(self, uri, transport=None, encoding=None, verbose=False,
14291433
extra_kwargs = {}
14301434
transport = handler(use_datetime=use_datetime,
14311435
use_builtin_types=use_builtin_types,
1436+
headers=headers,
14321437
**extra_kwargs)
14331438
self.__transport = transport
14341439

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add *headers* optional keyword-only parameter to
2+
:class:`xmlrpc.client.ServerProxy`, :class:`xmlrpc.client.Transport` and
3+
:class:`xmlrpc.client.SafeTransport`. Patch by Cédric Krier.

0 commit comments

Comments
 (0)