diff --git a/Doc/library/xmlrpc.server.rst b/Doc/library/xmlrpc.server.rst --- a/Doc/library/xmlrpc.server.rst +++ b/Doc/library/xmlrpc.server.rst @@ -38,105 +38,108 @@ servers written in Python. Servers can from the server. The *bind_and_activate* parameter controls whether :meth:`server_bind` and :meth:`server_activate` are called immediately by the constructor; it defaults to true. Setting it to false allows code to manipulate the *allow_reuse_address* class variable before the address is bound. The *use_builtin_types* parameter is passed to the :func:`~xmlrpc.client.loads` function and controls which types are processed when date/times values or binary data are received; it defaults to false. + .. method:: SimpleXMLRPCServer.register_function(function, name=None) + + Register a function that can respond to XML-RPC requests. If *name* is given, + it will be the method name associated with *function*, otherwise + ``function.__name__`` will be used. *name* can be either a normal or Unicode + string, and may contain characters not legal in Python identifiers, including + the period character. + + .. method:: SimpleXMLRPCServer.register_instance(instance, allow_dotted_names=False) + + Register an object which is used to expose method names which have not been + registered using :meth:`register_function`. If *instance* contains a + :meth:`_dispatch` method, it is called with the requested method name and the + parameters from the request. Its API is ``def _dispatch(self, method, params)`` + (note that *params* does not represent a variable argument list). If it calls + an underlying function to perform its task, that function is called as + ``func(*params)``, expanding the parameter list. The return value from + :meth:`_dispatch` is returned to the client as the result. If *instance* does + not have a :meth:`_dispatch` method, it is searched for an attribute matching + the name of the requested method. + + If the optional *allow_dotted_names* argument is true and the instance does not + have a :meth:`_dispatch` method, then if the requested method name contains + periods, each component of the method name is searched for individually, with + the effect that a simple hierarchical search is performed. The value found from + this search is then called with the parameters from the request, and the return + value is passed back to the client. + + .. warning:: + + Enabling the *allow_dotted_names* option allows intruders to access your + module's global variables and may allow intruders to execute arbitrary code on + your machine. Only use this option on a secure, closed network. + + .. method:: SimpleXMLRPCServer.register_introspection_functions() + + Registers the XML-RPC introspection functions ``system.listMethods``, + ``system.methodHelp`` and ``system.methodSignature``. + + .. method:: SimpleXMLRPCServer.register_multicall_functions() + + Registers the XML-RPC multicall function system.multicall. + + .. attribute:: SimpleXMLRPCServer.rpc_paths + + An attribute value that must be a tuple listing valid path portions of the URL + for receiving XML-RPC requests. Requests posted to other paths will result in a + 404 "no such page" HTTP error. If this tuple is empty, all paths will be + considered valid. The default value is ``('/', '/RPC2')``. + .. versionchanged:: 3.3 The *use_builtin_types* flag was added. +.. class:: SimpleXMLRPCRequestHandler() + + Create a new request handler instance. This request handler supports ``POST`` + requests and modifies logging so that the *logRequests* parameter to the + :class:`SimpleXMLRPCServer` constructor parameter is honored. + + .. class:: CGIXMLRPCRequestHandler(allow_none=False, encoding=None,\ use_builtin_types=False) Create a new instance to handle XML-RPC requests in a CGI environment. The *allow_none* and *encoding* parameters are passed on to :mod:`xmlrpc.client` and control the XML-RPC responses that will be returned from the server. The *use_builtin_types* parameter is passed to the :func:`~xmlrpc.client.loads` function and controls which types are processed when date/times values or binary data are received; it defaults to false. .. versionchanged:: 3.3 The *use_builtin_types* flag was added. -.. class:: SimpleXMLRPCRequestHandler() +.. class:: WSGIXMLRPCRequestHandler(allow_none=False, encoding=None,\ + use_builtin_types=False) - Create a new request handler instance. This request handler supports ``POST`` - requests and modifies logging so that the *logRequests* parameter to the - :class:`SimpleXMLRPCServer` constructor parameter is honored. + Create a WSGI application to handle XML-RPC requests in a WSGI environment. + The *allow_none* and *encoding* parameters are passed on to + :mod:`xmlrpc.client` and control the XML-RPC responses that will be returned + from the server. The *use_builtin_types* parameter is passed to the + :func:`~xmlrpc.client.loads` function and controls which types are processed + when date/times values or binary data are received; it defaults to false. + .. versionadded:: 3.5 -.. _simple-xmlrpc-servers: -SimpleXMLRPCServer Objects --------------------------- +.. _simple-xmlrpc-examples: -The :class:`SimpleXMLRPCServer` class is based on -:class:`socketserver.TCPServer` and provides a means of creating simple, stand -alone XML-RPC servers. - - -.. method:: SimpleXMLRPCServer.register_function(function, name=None) - - Register a function that can respond to XML-RPC requests. If *name* is given, - it will be the method name associated with *function*, otherwise - ``function.__name__`` will be used. *name* can be either a normal or Unicode - string, and may contain characters not legal in Python identifiers, including - the period character. - - -.. method:: SimpleXMLRPCServer.register_instance(instance, allow_dotted_names=False) - - Register an object which is used to expose method names which have not been - registered using :meth:`register_function`. If *instance* contains a - :meth:`_dispatch` method, it is called with the requested method name and the - parameters from the request. Its API is ``def _dispatch(self, method, params)`` - (note that *params* does not represent a variable argument list). If it calls - an underlying function to perform its task, that function is called as - ``func(*params)``, expanding the parameter list. The return value from - :meth:`_dispatch` is returned to the client as the result. If *instance* does - not have a :meth:`_dispatch` method, it is searched for an attribute matching - the name of the requested method. - - If the optional *allow_dotted_names* argument is true and the instance does not - have a :meth:`_dispatch` method, then if the requested method name contains - periods, each component of the method name is searched for individually, with - the effect that a simple hierarchical search is performed. The value found from - this search is then called with the parameters from the request, and the return - value is passed back to the client. - - .. warning:: - - Enabling the *allow_dotted_names* option allows intruders to access your - module's global variables and may allow intruders to execute arbitrary code on - your machine. Only use this option on a secure, closed network. - - -.. method:: SimpleXMLRPCServer.register_introspection_functions() - - Registers the XML-RPC introspection functions ``system.listMethods``, - ``system.methodHelp`` and ``system.methodSignature``. - - -.. method:: SimpleXMLRPCServer.register_multicall_functions() - - Registers the XML-RPC multicall function system.multicall. - - -.. attribute:: SimpleXMLRPCRequestHandler.rpc_paths - - An attribute value that must be a tuple listing valid path portions of the URL - for receiving XML-RPC requests. Requests posted to other paths will result in a - 404 "no such page" HTTP error. If this tuple is empty, all paths will be - considered valid. The default value is ``('/', '/RPC2')``. - +Examples +-------- .. _simplexmlrpcserver-example: SimpleXMLRPCServer Example ^^^^^^^^^^^^^^^^^^^^^^^^^^ Server code:: from xmlrpc.server import SimpleXMLRPCServer @@ -244,159 +247,130 @@ The client that interacts with the above except Error as v: print("ERROR", v) This client which interacts with the demo XMLRPC server can be invoked as:: python -m xmlrpc.client -CGIXMLRPCRequestHandler ------------------------ +.. _cgixmlrpcrequesthandler-example: -The :class:`CGIXMLRPCRequestHandler` class can be used to handle XML-RPC -requests sent to Python CGI scripts. +CGIXMLRPCRequestHandler Example +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. method:: CGIXMLRPCRequestHandler.register_function(function, name=None) - - Register a function that can respond to XML-RPC requests. If *name* is given, - it will be the method name associated with function, otherwise - *function.__name__* will be used. *name* can be either a normal or Unicode - string, and may contain characters not legal in Python identifiers, including - the period character. - - -.. method:: CGIXMLRPCRequestHandler.register_instance(instance) - - Register an object which is used to expose method names which have not been - registered using :meth:`register_function`. If instance contains a - :meth:`_dispatch` method, it is called with the requested method name and the - parameters from the request; the return value is returned to the client as the - result. If instance does not have a :meth:`_dispatch` method, it is searched - for an attribute matching the name of the requested method; if the requested - method name contains periods, each component of the method name is searched for - individually, with the effect that a simple hierarchical search is performed. - The value found from this search is then called with the parameters from the - request, and the return value is passed back to the client. - - -.. method:: CGIXMLRPCRequestHandler.register_introspection_functions() - - Register the XML-RPC introspection functions ``system.listMethods``, - ``system.methodHelp`` and ``system.methodSignature``. - - -.. method:: CGIXMLRPCRequestHandler.register_multicall_functions() - - Register the XML-RPC multicall function ``system.multicall``. - - -.. method:: CGIXMLRPCRequestHandler.handle_request(request_text=None) - - Handle a XML-RPC request. If *request_text* is given, it should be the POST - data provided by the HTTP server, otherwise the contents of stdin will be used. - -Example:: +:: class MyFuncs: def mul(self, x, y): return x * y handler = CGIXMLRPCRequestHandler() handler.register_function(pow) handler.register_function(lambda x,y: x+y, 'add') handler.register_introspection_functions() handler.register_instance(MyFuncs()) handler.handle_request() +.. _wsgixmlrpcrequesthandler-example: + +WSGIXMLRPCRequestHandler Example +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + from xmlrpc.server import WSGIXMLRPCRequestHandler + from wsgiref.simple_server import make_server + + wsgi_app = WSGIXMLRPCRequestHandler() + wsgi_app.register_introspection_functions() + + # Register pow() function; this will use the value of + # pow.__name__ as the name, which is just 'pow'. + wsgi_app.register_function(pow) + + # Register a function under a different name + def adder_function(x,y): + return x + y + wsgi_app.register_function(adder_function, 'add') + + # Register an instance; all the methods of the instance are + # published as XML-RPC methods (in this case, just 'mul'). + class MyFuncs: + def mul(self, x, y): + return x * y + wsgi_app.register_instance(MyFuncs()) + + server = make_server('',8000, wsgi_app) + server.serve_forever() + +The following client code will call the methods made available by the preceding +server:: + + import xmlrpc.client + + s = xmlrpc.client.ServerProxy('http://localhost:8000') + print(s.pow(2,3)) # Returns 2**3 = 8 + print(s.add(2,3)) # Returns 5 + print(s.mul(5,2)) # Returns 5*2 = 10 + print(s.system.listMethods()) + + Documenting XMLRPC server ------------------------- These classes extend the above classes to serve HTML documentation in response -to HTTP GET requests. Servers can either be free standing, using -:class:`DocXMLRPCServer`, or embedded in a CGI environment, using -:class:`DocCGIXMLRPCRequestHandler`. +to HTTP GET requests. Servers can be free standing using +:class:`DocXMLRPCServer`, embedded in a CGI environment using +:class:`DocCGIXMLRPCRequestHandler`, or run as a WSGI application using +:class:`DocWSGIXMLRPCRequestHandler`. .. class:: DocXMLRPCServer(addr, requestHandler=DocXMLRPCRequestHandler,\ logRequests=True, allow_none=False, encoding=None,\ bind_and_activate=True, use_builtin_types=True) Create a new server instance. All parameters have the same meaning as for :class:`SimpleXMLRPCServer`; *requestHandler* defaults to :class:`DocXMLRPCRequestHandler`. + The :class:`DocXMLRPCServer` class is derived from :class:`SimpleXMLRPCServer` + and provides a means of creating self-documenting, stand alone XML-RPC + servers. HTTP POST requests are handled as XML-RPC method calls. HTTP GET + requests are handled by generating pydoc-style HTML documentation. This allows a + server to provide its own web-based documentation. + + .. method:: DocXMLRPCServer.set_server_title(server_title) + + Set the title used in the generated HTML documentation. This title will be used + inside the HTML "title" element. + + .. method:: DocXMLRPCServer.set_server_name(server_name) + + Set the name used in the generated HTML documentation. This name will appear at + the top of the generated documentation inside a "h1" element. + + .. method:: DocXMLRPCServer.set_server_documentation(server_documentation) + + Set the description used in the generated HTML documentation. This description + will appear as a paragraph, below the server name, in the documentation. + .. versionchanged:: 3.3 The *use_builtin_types* flag was added. - -.. class:: DocCGIXMLRPCRequestHandler() - - Create a new instance to handle XML-RPC requests in a CGI environment. - - .. class:: DocXMLRPCRequestHandler() Create a new request handler instance. This request handler supports XML-RPC POST requests, documentation GET requests, and modifies logging so that the *logRequests* parameter to the :class:`DocXMLRPCServer` constructor parameter is honored. +.. class:: DocCGIXMLRPCRequestHandler() -.. _doc-xmlrpc-servers: + Create a new instance to handle XML-RPC requests in a CGI environment. -DocXMLRPCServer Objects ------------------------ +.. class:: DocWSGIXMLRPCRequestHandler() -The :class:`DocXMLRPCServer` class is derived from :class:`SimpleXMLRPCServer` -and provides a means of creating self-documenting, stand alone XML-RPC -servers. HTTP POST requests are handled as XML-RPC method calls. HTTP GET -requests are handled by generating pydoc-style HTML documentation. This allows a -server to provide its own web-based documentation. + Create a documenting WSGI application to handle XML-RPC requests. - -.. method:: DocXMLRPCServer.set_server_title(server_title) - - Set the title used in the generated HTML documentation. This title will be used - inside the HTML "title" element. - - -.. method:: DocXMLRPCServer.set_server_name(server_name) - - Set the name used in the generated HTML documentation. This name will appear at - the top of the generated documentation inside a "h1" element. - - -.. method:: DocXMLRPCServer.set_server_documentation(server_documentation) - - Set the description used in the generated HTML documentation. This description - will appear as a paragraph, below the server name, in the documentation. - - -DocCGIXMLRPCRequestHandler --------------------------- - -The :class:`DocCGIXMLRPCRequestHandler` class is derived from -:class:`CGIXMLRPCRequestHandler` and provides a means of creating -self-documenting, XML-RPC CGI scripts. HTTP POST requests are handled as XML-RPC -method calls. HTTP GET requests are handled by generating pydoc-style HTML -documentation. This allows a server to provide its own web-based documentation. - - -.. method:: DocCGIXMLRPCRequestHandler.set_server_title(server_title) - - Set the title used in the generated HTML documentation. This title will be used - inside the HTML "title" element. - - -.. method:: DocCGIXMLRPCRequestHandler.set_server_name(server_name) - - Set the name used in the generated HTML documentation. This name will appear at - the top of the generated documentation inside a "h1" element. - - -.. method:: DocCGIXMLRPCRequestHandler.set_server_documentation(server_documentation) - - Set the description used in the generated HTML documentation. This description - will appear as a paragraph, below the server name, in the documentation. + .. versionadded:: 3.5 diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py --- a/Lib/test/test_xmlrpc.py +++ b/Lib/test/test_xmlrpc.py @@ -1101,16 +1101,77 @@ class CGIHandlerTestCase(unittest.TestCa content = handle[handle.find("400 Bad request" + "

400 Bad request

") + + def _wsgi_request(self, **environ): + start_response = MockStartResponse() + response = self.handler()(environ, start_response) + return start_response, dict(start_response.response_headers), response + + def test_wsgi_head_request(self): + start_response, headers, response = self._wsgi_request(REQUEST_METHOD='HEAD') + self.assertEqual(start_response.status, '400 Bad Request') + self.assertEqual(response, []) + self.assertEqual(len(start_response.response_headers), 2) + self.assertIn('Content-Length', headers) + self.assertEqual(headers['Content-Length'], str(len(self.test_data))) + + def test_wsgi_non_post_requests(self): + for method in {'GET', 'PUT', 'DELETE'}: + with self.subTest(method=method): + start_response, headers, response = self._wsgi_request(REQUEST_METHOD=method) + self.assertEqual(start_response.status, '400 Bad Request') + self.assertEqual(response, [self.test_data]) + + def test_wsgi_xmlrpc_response(self): + data = """ + + test_method + + + foo + + + bar + + + + """ + body = io.StringIO(initial_value=data) + environ = { + 'REQUEST_METHOD': 'POST', + 'wsgi.input': body, + 'CONTENT_LENGTH': str(len(data)), + } + start_response, headers, response = self._wsgi_request(**environ) + # test that we get an XML Response back + self.assertEqual(start_response.status, '200 OK') + self.assertEqual(str(len(response[0])), headers['Content-Length']) + self.assertEqual(headers['Content-Type'], 'text/xml') + # test that we got an exception since test_method is not registered + self.assertRaises(xmlrpclib.Fault, xmlrpclib.loads, response[0]) + + class UseBuiltinTypesTestCase(unittest.TestCase): def test_use_builtin_types(self): # SimpleXMLRPCDispatcher.__init__ accepts use_builtin_types, which # makes all dispatch of binary data as bytes instances, and all # dispatch of datetime argument as datetime.datetime instances. self.log = [] expected_bytes = b"my dog has fleas" @@ -1136,18 +1197,20 @@ class UseBuiltinTypesTestCase(unittest.T server = xmlrpc.server.SimpleXMLRPCServer(("localhost", 0), use_builtin_types=True) server.server_close() self.assertTrue(server.use_builtin_types) @support.reap_threads def test_main(): - support.run_unittest(XMLRPCTestCase, HelperTestCase, DateTimeTestCase, - BinaryTestCase, FaultTestCase, UseBuiltinTypesTestCase, - SimpleServerTestCase, KeepaliveServerTestCase1, - KeepaliveServerTestCase2, GzipServerTestCase, GzipUtilTestCase, - MultiPathServerTestCase, ServerProxyTestCase, FailingServerTestCase, - CGIHandlerTestCase) + support.run_unittest( + XMLRPCTestCase, HelperTestCase, DateTimeTestCase, + BinaryTestCase, FaultTestCase, UseBuiltinTypesTestCase, + SimpleServerTestCase, KeepaliveServerTestCase1, + KeepaliveServerTestCase2, GzipServerTestCase, GzipUtilTestCase, + MultiPathServerTestCase, ServerProxyTestCase, FailingServerTestCase, + CGIHandlerTestCase, WSGIHandlerTestCase, + ) if __name__ == "__main__": test_main() diff --git a/Lib/xmlrpc/server.py b/Lib/xmlrpc/server.py --- a/Lib/xmlrpc/server.py +++ b/Lib/xmlrpc/server.py @@ -686,16 +686,39 @@ class CGIXMLRPCRequestHandler(SimpleXMLR except (ValueError, TypeError): length = -1 if request_text is None: request_text = sys.stdin.read(length) self.handle_xmlrpc(request_text) +class WSGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher): + """WSGI application to handle XML-RPC requests in a WSGI environment.""" + + def __call__(self, environ, start_response): + if environ['REQUEST_METHOD'] != 'POST': + status = '400 Bad Request' + headers = [('Content-Type', 'text/html')] + data = ('400 Bad request' + '

400 Bad request

') + headers.append(('Content-Length', str(len(data)))) + start_response(status, headers) + if environ['REQUEST_METHOD'] == 'HEAD': + return [] + return [data] + content_length = int(environ['CONTENT_LENGTH']) + request = environ['wsgi.input'].read(content_length) + response = self._marshaled_dispatch(request) + headers = [('Content-Type', 'text/xml'), + ('Content-Length', str(len(response)))] + start_response('200 OK', headers) + return [response] + + # ----------------------------------------------------------------------------- # Self documenting XML-RPC Server. class ServerHTMLDoc(pydoc.HTMLDoc): """Class used to generate pydoc HTML document for a server""" def markup(self, text, escape=None, funcs={}, classes={}, methods={}): """Mark up some plain text, given a context of symbols to look for. @@ -954,16 +977,36 @@ class DocCGIXMLRPCRequestHandler( CGIX sys.stdout.buffer.write(response) sys.stdout.buffer.flush() def __init__(self): CGIXMLRPCRequestHandler.__init__(self) XMLRPCDocGenerator.__init__(self) +class DocWSGIXMLRPCRequestHandler(WSGIXMLRPCRequestHandler, + XMLRPCDocGenerator): + """Handler for XML-RPC data and documentation requests for WSGI.""" + + def __call__(self, environ, start_response): + """Handles the HTTP GET request. + + Interpret all HTTP GET requests as requests for server + documentation. + """ + if environ['REQUEST_METHOD'] == 'GET': + response = self.generate_html_documentation().encode('utf-8') + status = '200 OK' + headers = [('Content-Type', 'text/html'), + ('Content-Length', str(len(response)))] + start_response(status, headers) + return [response] + return WSGIXMLRPCRequestHandler.__call__(self, environ, start_response) + + if __name__ == '__main__': import datetime class ExampleService: def getData(self): return '42' class currentTime: diff --git a/Misc/ACKS b/Misc/ACKS --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1039,16 +1039,17 @@ M. Papillon Peter Parente Alexandre Parenteau Dan Parisien William Park Claude Paroz Heikki Partanen Harri Pasanen Gaƫl Pasgrimaud +Sanjeev Paskaradevan Ashish Nitin Patil Randy Pausch Samuele Pedroni Justin Peel Marcel van der Peijl Berker Peksag Andreas Pelme Steven Pemberton