From 38eceaaf0c207709cfbb44c7a1b9715699622848 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 26 May 2008 11:14:17 +0000 Subject: Create xmlrpc package. Issue #2886. --- Doc/library/docxmlrpcserver.rst | 95 --- Doc/library/internet.rst | 5 +- Doc/library/persistence.rst | 1 + Doc/library/simplexmlrpcserver.rst | 222 ------ Doc/library/xmlrpc.client.rst | 534 ++++++++++++++ Doc/library/xmlrpc.server.rst | 307 ++++++++ Doc/library/xmlrpclib.rst | 560 -------------- Doc/license.rst | 2 +- Doc/tutorial/stdlib.rst | 2 +- Lib/DocXMLRPCServer.py | 283 ------- Lib/SimpleXMLRPCServer.py | 593 --------------- Lib/test/test_anydbm.py | 147 ---- Lib/test/test_docxmlrpc.py | 3 +- Lib/test/test_xmlrpc.py | 34 +- Lib/test/test_xmlrpc_net.py | 2 +- Lib/xmlrpc/__init__.py | 1 + Lib/xmlrpc/client.py | 1402 +++++++++++++++++++++++++++++++++++ Lib/xmlrpc/server.py | 868 ++++++++++++++++++++++ Lib/xmlrpclib.py | 1429 ------------------------------------ Misc/NEWS | 11 + Misc/cheatsheet | 3 +- 21 files changed, 3149 insertions(+), 3355 deletions(-) delete mode 100644 Doc/library/docxmlrpcserver.rst delete mode 100644 Doc/library/simplexmlrpcserver.rst create mode 100644 Doc/library/xmlrpc.client.rst create mode 100644 Doc/library/xmlrpc.server.rst delete mode 100644 Doc/library/xmlrpclib.rst delete mode 100644 Lib/DocXMLRPCServer.py delete mode 100644 Lib/SimpleXMLRPCServer.py delete mode 100644 Lib/test/test_anydbm.py create mode 100644 Lib/xmlrpc/__init__.py create mode 100644 Lib/xmlrpc/client.py create mode 100644 Lib/xmlrpc/server.py delete mode 100644 Lib/xmlrpclib.py diff --git a/Doc/library/docxmlrpcserver.rst b/Doc/library/docxmlrpcserver.rst deleted file mode 100644 index 8169684..0000000 --- a/Doc/library/docxmlrpcserver.rst +++ /dev/null @@ -1,95 +0,0 @@ - -:mod:`DocXMLRPCServer` --- Self-documenting XML-RPC server -========================================================== - -.. module:: DocXMLRPCServer - :synopsis: Self-documenting XML-RPC server implementation. -.. moduleauthor:: Brian Quinlan -.. sectionauthor:: Brian Quinlan - - -The :mod:`DocXMLRPCServer` module extends the classes found in -:mod:`SimpleXMLRPCServer` 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`. - - -.. class:: DocXMLRPCServer(addr[, requestHandler[, logRequests[, allow_none[, encoding[, bind_and_activate]]]]]) - - Create a new server instance. All parameters have the same meaning as for - :class:`SimpleXMLRPCServer.SimpleXMLRPCServer`; *requestHandler* defaults to - :class:`DocXMLRPCRequestHandler`. - - -.. 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. - - -.. _doc-xmlrpc-servers: - -DocXMLRPCServer Objects ------------------------ - -The :class:`DocXMLRPCServer` class is derived from -:class:`SimpleXMLRPCServer.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. - - -DocCGIXMLRPCRequestHandler --------------------------- - -The :class:`DocCGIXMLRPCRequestHandler` class is derived from -:class:`SimpleXMLRPCServer.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. - diff --git a/Doc/library/internet.rst b/Doc/library/internet.rst index 16b0a44..a5f6d22 100644 --- a/Doc/library/internet.rst +++ b/Doc/library/internet.rst @@ -42,6 +42,5 @@ is currently supported on most popular platforms. Here is an overview: cgihttpserver.rst cookielib.rst cookie.rst - xmlrpclib.rst - simplexmlrpcserver.rst - docxmlrpcserver.rst + xmlrpc.client.rst + xmlrpc.server.rst diff --git a/Doc/library/persistence.rst b/Doc/library/persistence.rst index c5c2aa4..8d9fa3a 100644 --- a/Doc/library/persistence.rst +++ b/Doc/library/persistence.rst @@ -23,4 +23,5 @@ The list of modules described in this chapter is: shelve.rst marshal.rst dbm.rst + bsddb.rst sqlite3.rst diff --git a/Doc/library/simplexmlrpcserver.rst b/Doc/library/simplexmlrpcserver.rst deleted file mode 100644 index f6b64b3..0000000 --- a/Doc/library/simplexmlrpcserver.rst +++ /dev/null @@ -1,222 +0,0 @@ - -:mod:`SimpleXMLRPCServer` --- Basic XML-RPC server -================================================== - -.. module:: SimpleXMLRPCServer - :synopsis: Basic XML-RPC server implementation. -.. moduleauthor:: Brian Quinlan -.. sectionauthor:: Fred L. Drake, Jr. - - -The :mod:`SimpleXMLRPCServer` module provides a basic server framework for -XML-RPC servers written in Python. Servers can either be free standing, using -:class:`SimpleXMLRPCServer`, or embedded in a CGI environment, using -:class:`CGIXMLRPCRequestHandler`. - - -.. class:: SimpleXMLRPCServer(addr[, requestHandler[, logRequests[, allow_none[, encoding[, bind_and_activate]]]]]) - - Create a new server instance. This class provides methods for registration of - functions that can be called by the XML-RPC protocol. The *requestHandler* - parameter should be a factory for request handler instances; it defaults to - :class:`SimpleXMLRPCRequestHandler`. The *addr* and *requestHandler* parameters - are passed to the :class:`socketserver.TCPServer` constructor. If *logRequests* - is true (the default), requests will be logged; setting this parameter to false - will turn off logging. The *allow_none* and *encoding* parameters are passed - on to :mod:`xmlrpclib` and control the XML-RPC responses that will be returned - 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. - - -.. class:: CGIXMLRPCRequestHandler([allow_none[, encoding]]) - - Create a new instance to handle XML-RPC requests in a CGI environment. The - *allow_none* and *encoding* parameters are passed on to :mod:`xmlrpclib` and - control the XML-RPC responses that will be returned from the server. - - -.. 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. - - -.. _simple-xmlrpc-servers: - -SimpleXMLRPCServer Objects --------------------------- - -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]) - - 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]) - - 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')``. - - -.. _simplexmlrpcserver-example: - -SimpleXMLRPCServer Example -^^^^^^^^^^^^^^^^^^^^^^^^^^ -Server code:: - - from SimpleXMLRPCServer import SimpleXMLRPCServer - from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler - - # Restrict to a particular path. - class RequestHandler(SimpleXMLRPCRequestHandler): - rpc_paths = ('/RPC2',) - - # Create server - server = SimpleXMLRPCServer(("localhost", 8000), - requestHandler=RequestHandler) - server.register_introspection_functions() - - # Register pow() function; this will use the value of - # pow.__name__ as the name, which is just 'pow'. - server.register_function(pow) - - # Register a function under a different name - def adder_function(x,y): - return x + y - server.register_function(adder_function, 'add') - - # Register an instance; all the methods of the instance are - # published as XML-RPC methods (in this case, just 'div'). - class MyFuncs: - def div(self, x, y): - return x // y - - server.register_instance(MyFuncs()) - - # Run the server's main loop - server.serve_forever() - -The following client code will call the methods made available by the preceding -server:: - - import xmlrpclib - - s = xmlrpclib.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 list of available methods - print(s.system.listMethods()) - - -CGIXMLRPCRequestHandler ------------------------ - -The :class:`CGIXMLRPCRequestHandler` class can be used to handle XML-RPC -requests sent to Python CGI scripts. - - -.. method:: CGIXMLRPCRequestHandler.register_function(function[, name]) - - 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 div(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() - diff --git a/Doc/library/xmlrpc.client.rst b/Doc/library/xmlrpc.client.rst new file mode 100644 index 0000000..7d59750 --- /dev/null +++ b/Doc/library/xmlrpc.client.rst @@ -0,0 +1,534 @@ +:mod:`xmlrpc.client` --- XML-RPC client access +============================================== + +.. module:: xmlrpc.client + :synopsis: XML-RPC client access. +.. moduleauthor:: Fredrik Lundh +.. sectionauthor:: Eric S. Raymond + + +.. XXX Not everything is documented yet. It might be good to describe + Marshaller, Unmarshaller, getparser, dumps, loads, and Transport. + +XML-RPC is a Remote Procedure Call method that uses XML passed via HTTP as a +transport. With it, a client can call methods with parameters on a remote +server (the server is named by a URI) and get back structured data. This module +supports writing XML-RPC client code; it handles all the details of translating +between conformable Python objects and XML on the wire. + + +.. class:: ServerProxy(uri[, transport[, encoding[, verbose[, allow_none[, use_datetime]]]]]) + + 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 + Indicator), and will normally be the URL of the server. The optional second + argument is a transport factory instance; by default it is an internal + :class:`SafeTransport` instance for https: URLs and an internal HTTP + :class:`Transport` instance otherwise. The optional third argument is an + encoding, by default UTF-8. The optional fourth argument is a debugging flag. + If *allow_none* is true, the Python constant ``None`` will be translated into + XML; the default behaviour is for ``None`` to raise a :exc:`TypeError`. This is + a commonly-used extension to the XML-RPC specification, but isn't supported by + all clients and servers; see http://ontosys.com/xml-rpc/extensions.php for a + description. The *use_datetime* flag can be used to cause date/time values to + be presented as :class:`datetime.datetime` objects; this is false by default. + :class:`datetime.datetime` objects may be passed to calls. + + Both the HTTP and HTTPS transports support the URL syntax extension for HTTP + Basic Authentication: ``http://user:pass@host:port/path``. The ``user:pass`` + portion will be base64-encoded as an HTTP 'Authorization' header, and sent to + the remote server as part of the connection process when invoking an XML-RPC + method. You only need to use this if the remote server requires a Basic + Authentication user and password. + + The returned instance is a proxy object with methods that can be used to invoke + corresponding RPC calls on the remote server. If the remote server supports the + introspection API, the proxy can also be used to query the remote server for the + methods it supports (service discovery) and fetch other server-associated + metadata. + + :class:`ServerProxy` instance methods take Python basic types and objects as + arguments and return Python basic types and classes. Types that are conformable + (e.g. that can be marshalled through XML), include the following (and except + where noted, they are unmarshalled as the same Python type): + + +---------------------------------+---------------------------------------------+ + | Name | Meaning | + +=================================+=============================================+ + | :const:`boolean` | The :const:`True` and :const:`False` | + | | constants | + +---------------------------------+---------------------------------------------+ + | :const:`integers` | Pass in directly | + +---------------------------------+---------------------------------------------+ + | :const:`floating-point numbers` | Pass in directly | + +---------------------------------+---------------------------------------------+ + | :const:`strings` | Pass in directly | + +---------------------------------+---------------------------------------------+ + | :const:`arrays` | Any Python sequence type containing | + | | conformable elements. Arrays are returned | + | | as lists | + +---------------------------------+---------------------------------------------+ + | :const:`structures` | A Python dictionary. Keys must be strings, | + | | values may be any conformable type. Objects | + | | of user-defined classes can be passed in; | + | | only their *__dict__* attribute is | + | | transmitted. | + +---------------------------------+---------------------------------------------+ + | :const:`dates` | in seconds since the epoch (pass in an | + | | instance of the :class:`DateTime` class) or | + | | a :class:`datetime.datetime` instance. | + +---------------------------------+---------------------------------------------+ + | :const:`binary data` | pass in an instance of the :class:`Binary` | + | | wrapper class | + +---------------------------------+---------------------------------------------+ + + This is the full set of data types supported by XML-RPC. Method calls may also + raise a special :exc:`Fault` instance, used to signal XML-RPC server errors, or + :exc:`ProtocolError` used to signal an error in the HTTP/HTTPS transport layer. + Both :exc:`Fault` and :exc:`ProtocolError` derive from a base class called + :exc:`Error`. Note that the xmlrpc client module currently does not marshal + instances of subclasses of builtin types. + + When passing strings, characters special to XML such as ``<``, ``>``, and ``&`` + will be automatically escaped. However, it's the caller's responsibility to + ensure that the string is free of characters that aren't allowed in XML, such as + the control characters with ASCII values between 0 and 31 (except, of course, + tab, newline and carriage return); failing to do this will result in an XML-RPC + request that isn't well-formed XML. If you have to pass arbitrary strings via + XML-RPC, use the :class:`Binary` wrapper class described below. + + :class:`Server` is retained as an alias for :class:`ServerProxy` for backwards + compatibility. New code should use :class:`ServerProxy`. + + +.. seealso:: + + `XML-RPC HOWTO `_ + A good description of XML-RPC operation and client software in several languages. + Contains pretty much everything an XML-RPC client developer needs to know. + + `XML-RPC Introspection `_ + Describes the XML-RPC protocol extension for introspection. + + `XML-RPC Specification `_ + The official specification. + + `Unofficial XML-RPC Errata `_ + Fredrik Lundh's "unofficial errata, intended to clarify certain + details in the XML-RPC specification, as well as hint at + 'best practices' to use when designing your own XML-RPC + implementations." + +.. _serverproxy-objects: + +ServerProxy Objects +------------------- + +A :class:`ServerProxy` instance has a method corresponding to each remote +procedure call accepted by the XML-RPC server. Calling the method performs an +RPC, dispatched by both name and argument signature (e.g. the same method name +can be overloaded with multiple argument signatures). The RPC finishes by +returning a value, which may be either returned data in a conformant type or a +:class:`Fault` or :class:`ProtocolError` object indicating an error. + +Servers that support the XML introspection API support some common methods +grouped under the reserved :attr:`system` member: + + +.. method:: ServerProxy.system.listMethods() + + This method returns a list of strings, one for each (non-system) method + supported by the XML-RPC server. + + +.. method:: ServerProxy.system.methodSignature(name) + + This method takes one parameter, the name of a method implemented by the XML-RPC + server.It returns an array of possible signatures for this method. A signature + is an array of types. The first of these types is the return type of the method, + the rest are parameters. + + Because multiple signatures (ie. overloading) is permitted, this method returns + a list of signatures rather than a singleton. + + Signatures themselves are restricted to the top level parameters expected by a + method. For instance if a method expects one array of structs as a parameter, + and it returns a string, its signature is simply "string, array". If it expects + three integers and returns a string, its signature is "string, int, int, int". + + If no signature is defined for the method, a non-array value is returned. In + Python this means that the type of the returned value will be something other + that list. + + +.. method:: ServerProxy.system.methodHelp(name) + + This method takes one parameter, the name of a method implemented by the XML-RPC + server. It returns a documentation string describing the use of that method. If + no such string is available, an empty string is returned. The documentation + string may contain HTML markup. + + +A working example follows. The server code:: + + from xmlrpc.server import SimpleXMLRPCServer + + def is_even(n): + return n%2 == 0 + + server = SimpleXMLRPCServer(("localhost", 8000)) + print("Listening on port 8000...") + server.register_function(is_even, "is_even") + server.serve_forever() + +The client code for the preceding server:: + + import xmlrpc.client + + proxy = xmlrpc.client.ServerProxy("http://localhost:8000/") + print("3 is even: %s" % str(proxy.is_even(3))) + print("100 is even: %s" % str(proxy.is_even(100))) + +.. _datetime-objects: + +DateTime Objects +---------------- + +This class may be initialized with seconds since the epoch, a time +tuple, an ISO 8601 time/date string, or a :class:`datetime.datetime` +instance. It has the following methods, supported mainly for internal +use by the marshalling/unmarshalling code: + + +.. method:: DateTime.decode(string) + + Accept a string as the instance's new time value. + + +.. method:: DateTime.encode(out) + + Write the XML-RPC encoding of this :class:`DateTime` item to the *out* stream + object. + +It also supports certain of Python's built-in operators through :meth:`__cmp__` +and :meth:`__repr__` methods. + +A working example follows. The server code:: + + import datetime + from xmlrpc.server import SimpleXMLRPCServer + import xmlrpc.client + + def today(): + today = datetime.datetime.today() + return xmlrpc.client.DateTime(today) + + server = SimpleXMLRPCServer(("localhost", 8000)) + print("Listening on port 8000...") + server.register_function(today, "today") + server.serve_forever() + +The client code for the preceding server:: + + import xmlrpc.client + import datetime + + proxy = xmlrpc.client.ServerProxy("http://localhost:8000/") + + today = proxy.today() + # convert the ISO8601 string to a datetime object + converted = datetime.datetime.strptime(today.value, "%Y%m%dT%H:%M:%S") + print("Today: %s" % converted.strftime("%d.%m.%Y, %H:%M")) + +.. _binary-objects: + +Binary Objects +-------------- + +This class may be initialized from string data (which may include NULs). The +primary access to the content of a :class:`Binary` object is provided by an +attribute: + + +.. attribute:: Binary.data + + The binary data encapsulated by the :class:`Binary` instance. The data is + provided as an 8-bit string. + +:class:`Binary` objects have the following methods, supported mainly for +internal use by the marshalling/unmarshalling code: + + +.. method:: Binary.decode(string) + + Accept a base64 string and decode it as the instance's new data. + + +.. method:: Binary.encode(out) + + Write the XML-RPC base 64 encoding of this binary item to the out stream object. + + The encoded data will have newlines every 76 characters as per + `RFC 2045 section 6.8 `_, + which was the de facto standard base64 specification when the + XML-RPC spec was written. + +It also supports certain of Python's built-in operators through a +:meth:`__cmp__` method. + +Example usage of the binary objects. We're going to transfer an image over +XMLRPC:: + + from xmlrpc.server import SimpleXMLRPCServer + import xmlrpc.client + + def python_logo(): + handle = open("python_logo.jpg") + return xmlrpc.client.Binary(handle.read()) + handle.close() + + server = SimpleXMLRPCServer(("localhost", 8000)) + print("Listening on port 8000...") + server.register_function(python_logo, 'python_logo') + + server.serve_forever() + +The client gets the image and saves it to a file:: + + import xmlrpc.client + + proxy = xmlrpc.client.ServerProxy("http://localhost:8000/") + handle = open("fetched_python_logo.jpg", "w") + handle.write(proxy.python_logo().data) + handle.close() + +.. _fault-objects: + +Fault Objects +------------- + +A :class:`Fault` object encapsulates the content of an XML-RPC fault tag. Fault +objects have the following members: + + +.. attribute:: Fault.faultCode + + A string indicating the fault type. + + +.. attribute:: Fault.faultString + + A string containing a diagnostic message associated with the fault. + +In the following example we're going to intentionally cause a :exc:`Fault` by +returning a complex type object. The server code:: + + from xmlrpc.server import SimpleXMLRPCServer + + # A marshalling error is going to occur because we're returning a + # complex number + def add(x,y): + return x+y+0j + + server = SimpleXMLRPCServer(("localhost", 8000)) + print("Listening on port 8000...") + server.register_function(add, 'add') + + server.serve_forever() + +The client code for the preceding server:: + + import xmlrpc.client + + proxy = xmlrpc.client.ServerProxy("http://localhost:8000/") + try: + proxy.add(2, 5) + except xmlrpc.client.Fault, err: + print("A fault occured") + print("Fault code: %d" % err.faultCode) + print("Fault string: %s" % err.faultString) + + + +.. _protocol-error-objects: + +ProtocolError Objects +--------------------- + +A :class:`ProtocolError` object describes a protocol error in the underlying +transport layer (such as a 404 'not found' error if the server named by the URI +does not exist). It has the following members: + + +.. attribute:: ProtocolError.url + + The URI or URL that triggered the error. + + +.. attribute:: ProtocolError.errcode + + The error code. + + +.. attribute:: ProtocolError.errmsg + + The error message or diagnostic string. + + +.. attribute:: ProtocolError.headers + + A dict containing the headers of the HTTP/HTTPS request that triggered the + error. + +In the following example we're going to intentionally cause a :exc:`ProtocolError` +by providing an invalid URI:: + + import xmlrpc.client + + # create a ServerProxy with an invalid URI + proxy = xmlrpc.client.ServerProxy("http://invalidaddress/") + + try: + proxy.some_method() + except xmlrpc.client.ProtocolError, err: + print("A protocol error occured") + print("URL: %s" % err.url) + print("HTTP/HTTPS headers: %s" % err.headers) + print("Error code: %d" % err.errcode) + print("Error message: %s" % err.errmsg) + +MultiCall Objects +----------------- + +In http://www.xmlrpc.com/discuss/msgReader%241208, an approach is presented to +encapsulate multiple calls to a remote server into a single request. + + +.. class:: MultiCall(server) + + Create an object used to boxcar method calls. *server* is the eventual target of + the call. Calls can be made to the result object, but they will immediately + return ``None``, and only store the call name and parameters in the + :class:`MultiCall` object. Calling the object itself causes all stored calls to + be transmitted as a single ``system.multicall`` request. The result of this call + is a :term:`generator`; iterating over this generator yields the individual + results. + +A usage example of this class follows. The server code :: + + from xmlrpc.server import SimpleXMLRPCServer + + def add(x,y): + return x+y + + def subtract(x, y): + return x-y + + def multiply(x, y): + return x*y + + def divide(x, y): + return x/y + + # A simple server with simple arithmetic functions + server = SimpleXMLRPCServer(("localhost", 8000)) + print("Listening on port 8000...") + server.register_multicall_functions() + server.register_function(add, 'add') + server.register_function(subtract, 'subtract') + server.register_function(multiply, 'multiply') + server.register_function(divide, 'divide') + server.serve_forever() + +The client code for the preceding server:: + + import xmlrpc.client + + proxy = xmlrpc.client.ServerProxy("http://localhost:8000/") + multicall = xmlrpc.client.MultiCall(proxy) + multicall.add(7,3) + multicall.subtract(7,3) + multicall.multiply(7,3) + multicall.divide(7,3) + result = multicall() + + print("7+3=%d, 7-3=%d, 7*3=%d, 7/3=%d" % tuple(result)) + + +Convenience Functions +--------------------- + +.. function:: dumps(params[, methodname[, methodresponse[, encoding[, allow_none]]]]) + + Convert *params* into an XML-RPC request. or into a response if *methodresponse* + is true. *params* can be either a tuple of arguments or an instance of the + :exc:`Fault` exception class. If *methodresponse* is true, only a single value + can be returned, meaning that *params* must be of length 1. *encoding*, if + supplied, is the encoding to use in the generated XML; the default is UTF-8. + Python's :const:`None` value cannot be used in standard XML-RPC; to allow using + it via an extension, provide a true value for *allow_none*. + + +.. function:: loads(data[, use_datetime]) + + Convert an XML-RPC request or response into Python objects, a ``(params, + methodname)``. *params* is a tuple of argument; *methodname* is a string, or + ``None`` if no method name is present in the packet. If the XML-RPC packet + represents a fault condition, this function will raise a :exc:`Fault` exception. + The *use_datetime* flag can be used to cause date/time values to be presented as + :class:`datetime.datetime` objects; this is false by default. + + +.. _xmlrpc-client-example: + +Example of Client Usage +----------------------- + +:: + + # simple test program (from the XML-RPC specification) + from xmlrpc.client import ServerProxy, Error + + # server = ServerProxy("http://localhost:8000") # local server + server = ServerProxy("http://betty.userland.com") + + print(server) + + try: + print(server.examples.getStateName(41)) + except Error as v: + print("ERROR", v) + +To access an XML-RPC server through a proxy, you need to define a custom +transport. The following example shows how: + +.. Example taken from http://lowlife.jp/nobonobo/wiki/xmlrpcwithproxy.html + +:: + + import xmlrpc.client, httplib + + class ProxiedTransport(xmlrpc.client.Transport): + def set_proxy(self, proxy): + self.proxy = proxy + def make_connection(self, host): + self.realhost = host + h = httplib.HTTP(self.proxy) + return h + def send_request(self, connection, handler, request_body): + connection.putrequest("POST", 'http://%s%s' % (self.realhost, handler)) + def send_host(self, connection, host): + connection.putheader('Host', self.realhost) + + p = ProxiedTransport() + p.set_proxy('proxy-server:8080') + server = xmlrpc.client.Server('http://time.xmlrpc.com/RPC2', transport=p) + print(server.currentTime.getCurrentTime()) + + +Example of Client and Server Usage +---------------------------------- + +See :ref:`simplexmlrpcserver-example`. + + diff --git a/Doc/library/xmlrpc.server.rst b/Doc/library/xmlrpc.server.rst new file mode 100644 index 0000000..1a9c757 --- /dev/null +++ b/Doc/library/xmlrpc.server.rst @@ -0,0 +1,307 @@ +:mod:`xmlrpc.server` --- Basic XML-RPC servers +============================================== + +.. module:: xmlrpc.server + :synopsis: Basic XML-RPC server implementations. +.. moduleauthor:: Brian Quinlan +.. sectionauthor:: Fred L. Drake, Jr. + + +The :mod:`xmlrpc.server` module provides a basic server framework for XML-RPC +servers written in Python. Servers can either be free standing, using +:class:`SimpleXMLRPCServer`, or embedded in a CGI environment, using +:class:`CGIXMLRPCRequestHandler`. + + +.. class:: SimpleXMLRPCServer(addr[, requestHandler[, logRequests[, allow_none[, encoding[, bind_and_activate]]]]]) + + Create a new server instance. This class provides methods for registration of + functions that can be called by the XML-RPC protocol. The *requestHandler* + parameter should be a factory for request handler instances; it defaults to + :class:`SimpleXMLRPCRequestHandler`. The *addr* and *requestHandler* parameters + are passed to the :class:`socketserver.TCPServer` constructor. If *logRequests* + is true (the default), requests will be logged; setting this parameter to false + will turn off logging. 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 *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. + + +.. class:: CGIXMLRPCRequestHandler([allow_none[, encoding]]) + + 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. + + +.. 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. + + +.. _simple-xmlrpc-servers: + +SimpleXMLRPCServer Objects +-------------------------- + +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]) + + 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]) + + 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')``. + + +.. _simplexmlrpcserver-example: + +SimpleXMLRPCServer Example +^^^^^^^^^^^^^^^^^^^^^^^^^^ +Server code:: + + from xmlrpc.server import SimpleXMLRPCServer + from xmlrpc.server import SimpleXMLRPCRequestHandler + + # Restrict to a particular path. + class RequestHandler(SimpleXMLRPCRequestHandler): + rpc_paths = ('/RPC2',) + + # Create server + server = SimpleXMLRPCServer(("localhost", 8000), + requestHandler=RequestHandler) + server.register_introspection_functions() + + # Register pow() function; this will use the value of + # pow.__name__ as the name, which is just 'pow'. + server.register_function(pow) + + # Register a function under a different name + def adder_function(x,y): + return x + y + server.register_function(adder_function, 'add') + + # Register an instance; all the methods of the instance are + # published as XML-RPC methods (in this case, just 'div'). + class MyFuncs: + def div(self, x, y): + return x // y + + server.register_instance(MyFuncs()) + + # Run the server's main loop + 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 list of available methods + print(s.system.listMethods()) + + +CGIXMLRPCRequestHandler +----------------------- + +The :class:`CGIXMLRPCRequestHandler` class can be used to handle XML-RPC +requests sent to Python CGI scripts. + + +.. method:: CGIXMLRPCRequestHandler.register_function(function[, name]) + + 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 div(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() + + +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`. + + +.. class:: DocXMLRPCServer(addr[, requestHandler[, logRequests[, allow_none[, encoding[, bind_and_activate]]]]]) + + Create a new server instance. All parameters have the same meaning as for + :class:`SimpleXMLRPCServer`; *requestHandler* defaults to + :class:`DocXMLRPCRequestHandler`. + + +.. 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. + + +.. _doc-xmlrpc-servers: + +DocXMLRPCServer Objects +----------------------- + +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. + + +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. diff --git a/Doc/library/xmlrpclib.rst b/Doc/library/xmlrpclib.rst deleted file mode 100644 index c1f13c3..0000000 --- a/Doc/library/xmlrpclib.rst +++ /dev/null @@ -1,560 +0,0 @@ -:mod:`xmlrpclib` --- XML-RPC client access -========================================== - -.. module:: xmlrpclib - :synopsis: XML-RPC client access. -.. moduleauthor:: Fredrik Lundh -.. sectionauthor:: Eric S. Raymond - - -.. XXX Not everything is documented yet. It might be good to describe - Marshaller, Unmarshaller, getparser, dumps, loads, and Transport. - -XML-RPC is a Remote Procedure Call method that uses XML passed via HTTP as a -transport. With it, a client can call methods with parameters on a remote -server (the server is named by a URI) and get back structured data. This module -supports writing XML-RPC client code; it handles all the details of translating -between conformable Python objects and XML on the wire. - - -.. class:: ServerProxy(uri[, transport[, encoding[, verbose[, allow_none[, use_datetime]]]]]) - - 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 - Indicator), and will normally be the URL of the server. The optional second - argument is a transport factory instance; by default it is an internal - :class:`SafeTransport` instance for https: URLs and an internal HTTP - :class:`Transport` instance otherwise. The optional third argument is an - encoding, by default UTF-8. The optional fourth argument is a debugging flag. - If *allow_none* is true, the Python constant ``None`` will be translated into - XML; the default behaviour is for ``None`` to raise a :exc:`TypeError`. This is - a commonly-used extension to the XML-RPC specification, but isn't supported by - all clients and servers; see http://ontosys.com/xml-rpc/extensions.php for a - description. The *use_datetime* flag can be used to cause date/time values to - be presented as :class:`datetime.datetime` objects; this is false by default. - :class:`datetime.datetime` objects may be passed to calls. - - Both the HTTP and HTTPS transports support the URL syntax extension for HTTP - Basic Authentication: ``http://user:pass@host:port/path``. The ``user:pass`` - portion will be base64-encoded as an HTTP 'Authorization' header, and sent to - the remote server as part of the connection process when invoking an XML-RPC - method. You only need to use this if the remote server requires a Basic - Authentication user and password. - - The returned instance is a proxy object with methods that can be used to invoke - corresponding RPC calls on the remote server. If the remote server supports the - introspection API, the proxy can also be used to query the remote server for the - methods it supports (service discovery) and fetch other server-associated - metadata. - - :class:`ServerProxy` instance methods take Python basic types and objects as - arguments and return Python basic types and classes. Types that are conformable - (e.g. that can be marshalled through XML), include the following (and except - where noted, they are unmarshalled as the same Python type): - - +---------------------------------+---------------------------------------------+ - | Name | Meaning | - +=================================+=============================================+ - | :const:`boolean` | The :const:`True` and :const:`False` | - | | constants | - +---------------------------------+---------------------------------------------+ - | :const:`integers` | Pass in directly | - +---------------------------------+---------------------------------------------+ - | :const:`floating-point numbers` | Pass in directly | - +---------------------------------+---------------------------------------------+ - | :const:`strings` | Pass in directly | - +---------------------------------+---------------------------------------------+ - | :const:`arrays` | Any Python sequence type containing | - | | conformable elements. Arrays are returned | - | | as lists | - +---------------------------------+---------------------------------------------+ - | :const:`structures` | A Python dictionary. Keys must be strings, | - | | values may be any conformable type. Objects | - | | of user-defined classes can be passed in; | - | | only their *__dict__* attribute is | - | | transmitted. | - +---------------------------------+---------------------------------------------+ - | :const:`dates` | in seconds since the epoch (pass in an | - | | instance of the :class:`DateTime` class) or | - | | a :class:`datetime.datetime` instance. | - +---------------------------------+---------------------------------------------+ - | :const:`binary data` | pass in an instance of the :class:`Binary` | - | | wrapper class | - +---------------------------------+---------------------------------------------+ - - This is the full set of data types supported by XML-RPC. Method calls may also - raise a special :exc:`Fault` instance, used to signal XML-RPC server errors, or - :exc:`ProtocolError` used to signal an error in the HTTP/HTTPS transport layer. - Both :exc:`Fault` and :exc:`ProtocolError` derive from a base class called - :exc:`Error`. Note that the xmlrpclib module currently does not marshal - instances of subclasses of builtin types. - - When passing strings, characters special to XML such as ``<``, ``>``, and ``&`` - will be automatically escaped. However, it's the caller's responsibility to - ensure that the string is free of characters that aren't allowed in XML, such as - the control characters with ASCII values between 0 and 31 (except, of course, - tab, newline and carriage return); failing to do this will result in an XML-RPC - request that isn't well-formed XML. If you have to pass arbitrary strings via - XML-RPC, use the :class:`Binary` wrapper class described below. - - :class:`Server` is retained as an alias for :class:`ServerProxy` for backwards - compatibility. New code should use :class:`ServerProxy`. - - -.. seealso:: - - `XML-RPC HOWTO `_ - A good description of XML-RPC operation and client software in several languages. - Contains pretty much everything an XML-RPC client developer needs to know. - - `XML-RPC Introspection `_ - Describes the XML-RPC protocol extension for introspection. - - `XML-RPC Specification `_ - The official specification. - - `Unofficial XML-RPC Errata `_ - Fredrik Lundh's "unofficial errata, intended to clarify certain - details in the XML-RPC specification, as well as hint at - 'best practices' to use when designing your own XML-RPC - implementations." - -.. _serverproxy-objects: - -ServerProxy Objects -------------------- - -A :class:`ServerProxy` instance has a method corresponding to each remote -procedure call accepted by the XML-RPC server. Calling the method performs an -RPC, dispatched by both name and argument signature (e.g. the same method name -can be overloaded with multiple argument signatures). The RPC finishes by -returning a value, which may be either returned data in a conformant type or a -:class:`Fault` or :class:`ProtocolError` object indicating an error. - -Servers that support the XML introspection API support some common methods -grouped under the reserved :attr:`system` member: - - -.. method:: ServerProxy.system.listMethods() - - This method returns a list of strings, one for each (non-system) method - supported by the XML-RPC server. - - -.. method:: ServerProxy.system.methodSignature(name) - - This method takes one parameter, the name of a method implemented by the XML-RPC - server.It returns an array of possible signatures for this method. A signature - is an array of types. The first of these types is the return type of the method, - the rest are parameters. - - Because multiple signatures (ie. overloading) is permitted, this method returns - a list of signatures rather than a singleton. - - Signatures themselves are restricted to the top level parameters expected by a - method. For instance if a method expects one array of structs as a parameter, - and it returns a string, its signature is simply "string, array". If it expects - three integers and returns a string, its signature is "string, int, int, int". - - If no signature is defined for the method, a non-array value is returned. In - Python this means that the type of the returned value will be something other - that list. - - -.. method:: ServerProxy.system.methodHelp(name) - - This method takes one parameter, the name of a method implemented by the XML-RPC - server. It returns a documentation string describing the use of that method. If - no such string is available, an empty string is returned. The documentation - string may contain HTML markup. - - -.. _boolean-objects: - -Boolean Objects ---------------- - -This class may be initialized from any Python value; the instance returned -depends only on its truth value. It supports various Python operators through -:meth:`__cmp__`, :meth:`__repr__`, :meth:`__int__`, and :meth:`__bool__` -methods, all implemented in the obvious ways. - -It also has the following method, supported mainly for internal use by the -unmarshalling code: - - -.. method:: Boolean.encode(out) - - Write the XML-RPC encoding of this Boolean item to the out stream object. - -A working example follows. The server code:: - - import xmlrpclib - from SimpleXMLRPCServer import SimpleXMLRPCServer - - def is_even(n): - return n%2 == 0 - - server = SimpleXMLRPCServer(("localhost", 8000)) - print("Listening on port 8000...") - server.register_function(is_even, "is_even") - server.serve_forever() - -The client code for the preceding server:: - - import xmlrpclib - - proxy = xmlrpclib.ServerProxy("http://localhost:8000/") - print("3 is even: %s" % str(proxy.is_even(3))) - print("100 is even: %s" % str(proxy.is_even(100))) - -.. _datetime-objects: - -DateTime Objects ----------------- - -This class may be initialized with seconds since the epoch, a time -tuple, an ISO 8601 time/date string, or a :class:`datetime.datetime` -instance. It has the following methods, supported mainly for internal -use by the marshalling/unmarshalling code: - - -.. method:: DateTime.decode(string) - - Accept a string as the instance's new time value. - - -.. method:: DateTime.encode(out) - - Write the XML-RPC encoding of this :class:`DateTime` item to the *out* stream - object. - -It also supports certain of Python's built-in operators through :meth:`__cmp__` -and :meth:`__repr__` methods. - -A working example follows. The server code:: - - import datetime - from SimpleXMLRPCServer import SimpleXMLRPCServer - import xmlrpclib - - def today(): - today = datetime.datetime.today() - return xmlrpclib.DateTime(today) - - server = SimpleXMLRPCServer(("localhost", 8000)) - print("Listening on port 8000...") - server.register_function(today, "today") - server.serve_forever() - -The client code for the preceding server:: - - import xmlrpclib - import datetime - - proxy = xmlrpclib.ServerProxy("http://localhost:8000/") - - today = proxy.today() - # convert the ISO8601 string to a datetime object - converted = datetime.datetime.strptime(today.value, "%Y%m%dT%H:%M:%S") - print("Today: %s" % converted.strftime("%d.%m.%Y, %H:%M")) - -.. _binary-objects: - -Binary Objects --------------- - -This class may be initialized from string data (which may include NULs). The -primary access to the content of a :class:`Binary` object is provided by an -attribute: - - -.. attribute:: Binary.data - - The binary data encapsulated by the :class:`Binary` instance. The data is - provided as an 8-bit string. - -:class:`Binary` objects have the following methods, supported mainly for -internal use by the marshalling/unmarshalling code: - - -.. method:: Binary.decode(string) - - Accept a base64 string and decode it as the instance's new data. - - -.. method:: Binary.encode(out) - - Write the XML-RPC base 64 encoding of this binary item to the out stream object. - - The encoded data will have newlines every 76 characters as per - `RFC 2045 section 6.8 `_, - which was the de facto standard base64 specification when the - XML-RPC spec was written. - -It also supports certain of Python's built-in operators through a -:meth:`__cmp__` method. - -Example usage of the binary objects. We're going to transfer an image over -XMLRPC:: - - from SimpleXMLRPCServer import SimpleXMLRPCServer - import xmlrpclib - - def python_logo(): - handle = open("python_logo.jpg") - return xmlrpclib.Binary(handle.read()) - handle.close() - - server = SimpleXMLRPCServer(("localhost", 8000)) - print("Listening on port 8000...") - server.register_function(python_logo, 'python_logo') - - server.serve_forever() - -The client gets the image and saves it to a file:: - - import xmlrpclib - - proxy = xmlrpclib.ServerProxy("http://localhost:8000/") - handle = open("fetched_python_logo.jpg", "w") - handle.write(proxy.python_logo().data) - handle.close() - -.. _fault-objects: - -Fault Objects -------------- - -A :class:`Fault` object encapsulates the content of an XML-RPC fault tag. Fault -objects have the following members: - - -.. attribute:: Fault.faultCode - - A string indicating the fault type. - - -.. attribute:: Fault.faultString - - A string containing a diagnostic message associated with the fault. - -In the following example we're going to intentionally cause a :exc:`Fault` by -returning a complex type object. The server code:: - - from SimpleXMLRPCServer import SimpleXMLRPCServer - - # A marshalling error is going to occur because we're returning a - # complex number - def add(x,y): - return x+y+0j - - server = SimpleXMLRPCServer(("localhost", 8000)) - print("Listening on port 8000...") - server.register_function(add, 'add') - - server.serve_forever() - -The client code for the preceding server:: - - import xmlrpclib - - proxy = xmlrpclib.ServerProxy("http://localhost:8000/") - try: - proxy.add(2, 5) - except xmlrpclib.Fault, err: - print("A fault occured") - print("Fault code: %d" % err.faultCode) - print("Fault string: %s" % err.faultString) - - - -.. _protocol-error-objects: - -ProtocolError Objects ---------------------- - -A :class:`ProtocolError` object describes a protocol error in the underlying -transport layer (such as a 404 'not found' error if the server named by the URI -does not exist). It has the following members: - - -.. attribute:: ProtocolError.url - - The URI or URL that triggered the error. - - -.. attribute:: ProtocolError.errcode - - The error code. - - -.. attribute:: ProtocolError.errmsg - - The error message or diagnostic string. - - -.. attribute:: ProtocolError.headers - - A dict containing the headers of the HTTP/HTTPS request that triggered the - error. - -In the following example we're going to intentionally cause a :exc:`ProtocolError` -by providing an invalid URI:: - - import xmlrpclib - - # create a ServerProxy with an invalid URI - proxy = xmlrpclib.ServerProxy("http://invalidaddress/") - - try: - proxy.some_method() - except xmlrpclib.ProtocolError, err: - print("A protocol error occured") - print("URL: %s" % err.url) - print("HTTP/HTTPS headers: %s" % err.headers) - print("Error code: %d" % err.errcode) - print("Error message: %s" % err.errmsg) - -MultiCall Objects ------------------ - -In http://www.xmlrpc.com/discuss/msgReader%241208, an approach is presented to -encapsulate multiple calls to a remote server into a single request. - - -.. class:: MultiCall(server) - - Create an object used to boxcar method calls. *server* is the eventual target of - the call. Calls can be made to the result object, but they will immediately - return ``None``, and only store the call name and parameters in the - :class:`MultiCall` object. Calling the object itself causes all stored calls to - be transmitted as a single ``system.multicall`` request. The result of this call - is a :term:`generator`; iterating over this generator yields the individual - results. - -A usage example of this class follows. The server code :: - - from SimpleXMLRPCServer import SimpleXMLRPCServer - - def add(x,y): - return x+y - - def subtract(x, y): - return x-y - - def multiply(x, y): - return x*y - - def divide(x, y): - return x/y - - # A simple server with simple arithmetic functions - server = SimpleXMLRPCServer(("localhost", 8000)) - print("Listening on port 8000...") - server.register_multicall_functions() - server.register_function(add, 'add') - server.register_function(subtract, 'subtract') - server.register_function(multiply, 'multiply') - server.register_function(divide, 'divide') - server.serve_forever() - -The client code for the preceding server:: - - import xmlrpclib - - proxy = xmlrpclib.ServerProxy("http://localhost:8000/") - multicall = xmlrpclib.MultiCall(proxy) - multicall.add(7,3) - multicall.subtract(7,3) - multicall.multiply(7,3) - multicall.divide(7,3) - result = multicall() - - print("7+3=%d, 7-3=%d, 7*3=%d, 7/3=%d" % tuple(result)) - - -Convenience Functions ---------------------- - - -.. function:: boolean(value) - - Convert any Python value to one of the XML-RPC Boolean constants, ``True`` or - ``False``. - - -.. function:: dumps(params[, methodname[, methodresponse[, encoding[, allow_none]]]]) - - Convert *params* into an XML-RPC request. or into a response if *methodresponse* - is true. *params* can be either a tuple of arguments or an instance of the - :exc:`Fault` exception class. If *methodresponse* is true, only a single value - can be returned, meaning that *params* must be of length 1. *encoding*, if - supplied, is the encoding to use in the generated XML; the default is UTF-8. - Python's :const:`None` value cannot be used in standard XML-RPC; to allow using - it via an extension, provide a true value for *allow_none*. - - -.. function:: loads(data[, use_datetime]) - - Convert an XML-RPC request or response into Python objects, a ``(params, - methodname)``. *params* is a tuple of argument; *methodname* is a string, or - ``None`` if no method name is present in the packet. If the XML-RPC packet - represents a fault condition, this function will raise a :exc:`Fault` exception. - The *use_datetime* flag can be used to cause date/time values to be presented as - :class:`datetime.datetime` objects; this is false by default. - - -.. _xmlrpc-client-example: - -Example of Client Usage ------------------------ - -:: - - # simple test program (from the XML-RPC specification) - from xmlrpclib import ServerProxy, Error - - # server = ServerProxy("http://localhost:8000") # local server - server = ServerProxy("http://betty.userland.com") - - print(server) - - try: - print(server.examples.getStateName(41)) - except Error as v: - print("ERROR", v) - -To access an XML-RPC server through a proxy, you need to define a custom -transport. The following example shows how: - -.. Example taken from http://lowlife.jp/nobonobo/wiki/xmlrpcwithproxy.html - -:: - - import xmlrpclib, httplib - - class ProxiedTransport(xmlrpclib.Transport): - def set_proxy(self, proxy): - self.proxy = proxy - def make_connection(self, host): - self.realhost = host - h = httplib.HTTP(self.proxy) - return h - def send_request(self, connection, handler, request_body): - connection.putrequest("POST", 'http://%s%s' % (self.realhost, handler)) - def send_host(self, connection, host): - connection.putheader('Host', self.realhost) - - p = ProxiedTransport() - p.set_proxy('proxy-server:8080') - server = xmlrpclib.Server('http://time.xmlrpc.com/RPC2', transport=p) - print(server.currentTime.getCurrentTime()) - - -Example of Client and Server Usage ----------------------------------- - -See :ref:`simplexmlrpcserver-example`. - - diff --git a/Doc/license.rst b/Doc/license.rst index 05962e9..ed399dc 100644 --- a/Doc/license.rst +++ b/Doc/license.rst @@ -570,7 +570,7 @@ The :mod:`uu` module contains the following notice:: XML Remote Procedure Calls -------------------------- -The :mod:`xmlrpclib` module contains the following notice:: +The :mod:`xmlrpc.client` module contains the following notice:: The XML-RPC client interface is diff --git a/Doc/tutorial/stdlib.rst b/Doc/tutorial/stdlib.rst index 725817c..66e73a9 100644 --- a/Doc/tutorial/stdlib.rst +++ b/Doc/tutorial/stdlib.rst @@ -294,7 +294,7 @@ Batteries Included Python has a "batteries included" philosophy. This is best seen through the sophisticated and robust capabilities of its larger packages. For example: -* The :mod:`xmlrpclib` and :mod:`SimpleXMLRPCServer` modules make implementing +* The :mod:`xmlrpc.client` and :mod:`xmlrpc.server` modules make implementing remote procedure calls into an almost trivial task. Despite the modules names, no direct knowledge or handling of XML is needed. diff --git a/Lib/DocXMLRPCServer.py b/Lib/DocXMLRPCServer.py deleted file mode 100644 index 246fd74..0000000 --- a/Lib/DocXMLRPCServer.py +++ /dev/null @@ -1,283 +0,0 @@ -"""Self documenting XML-RPC Server. - -This module can be used to create XML-RPC servers that -serve pydoc-style documentation in response to HTTP -GET requests. This documentation is dynamically generated -based on the functions and methods registered with the -server. - -This module is built upon the pydoc and SimpleXMLRPCServer -modules. -""" - -import pydoc -import inspect -import re -import sys - -from SimpleXMLRPCServer import (SimpleXMLRPCServer, - SimpleXMLRPCRequestHandler, - CGIXMLRPCRequestHandler, - resolve_dotted_attribute) - -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. - Each context dictionary maps object names to anchor names.""" - escape = escape or self.escape - results = [] - here = 0 - - # XXX Note that this regular expression does not allow for the - # hyperlinking of arbitrary strings being used as method - # names. Only methods with names consisting of word characters - # and '.'s are hyperlinked. - pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|' - r'RFC[- ]?(\d+)|' - r'PEP[- ]?(\d+)|' - r'(self\.)?((?:\w|\.)+))\b') - while 1: - match = pattern.search(text, here) - if not match: break - start, end = match.span() - results.append(escape(text[here:start])) - - all, scheme, rfc, pep, selfdot, name = match.groups() - if scheme: - url = escape(all).replace('"', '"') - results.append('%s' % (url, url)) - elif rfc: - url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc) - results.append('%s' % (url, escape(all))) - elif pep: - url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep) - results.append('%s' % (url, escape(all))) - elif text[end:end+1] == '(': - results.append(self.namelink(name, methods, funcs, classes)) - elif selfdot: - results.append('self.%s' % name) - else: - results.append(self.namelink(name, classes)) - here = end - results.append(escape(text[here:])) - return ''.join(results) - - def docroutine(self, object, name, mod=None, - funcs={}, classes={}, methods={}, cl=None): - """Produce HTML documentation for a function or method object.""" - - anchor = (cl and cl.__name__ or '') + '-' + name - note = '' - - title = '%s' % ( - self.escape(anchor), self.escape(name)) - - if inspect.ismethod(object): - args, varargs, varkw, defaults = inspect.getargspec(object) - # exclude the argument bound to the instance, it will be - # confusing to the non-Python user - argspec = inspect.formatargspec ( - args[1:], - varargs, - varkw, - defaults, - formatvalue=self.formatvalue - ) - elif inspect.isfunction(object): - args, varargs, varkw, defaults = inspect.getargspec(object) - argspec = inspect.formatargspec( - args, varargs, varkw, defaults, formatvalue=self.formatvalue) - else: - argspec = '(...)' - - if isinstance(object, tuple): - argspec = object[0] or argspec - docstring = object[1] or "" - else: - docstring = pydoc.getdoc(object) - - decl = title + argspec + (note and self.grey( - '%s' % note)) - - doc = self.markup( - docstring, self.preformat, funcs, classes, methods) - doc = doc and '
%s
' % doc - return '
%s
%s
\n' % (decl, doc) - - def docserver(self, server_name, package_documentation, methods): - """Produce HTML documentation for an XML-RPC server.""" - - fdict = {} - for key, value in methods.items(): - fdict[key] = '#-' + key - fdict[value] = fdict[key] - - server_name = self.escape(server_name) - head = '%s' % server_name - result = self.heading(head, '#ffffff', '#7799ee') - - doc = self.markup(package_documentation, self.preformat, fdict) - doc = doc and '%s' % doc - result = result + '

%s

\n' % doc - - contents = [] - method_items = sorted(methods.items()) - for key, value in method_items: - contents.append(self.docroutine(value, key, funcs=fdict)) - result = result + self.bigsection( - 'Methods', '#ffffff', '#eeaa77', ''.join(contents)) - - return result - -class XMLRPCDocGenerator: - """Generates documentation for an XML-RPC server. - - This class is designed as mix-in and should not - be constructed directly. - """ - - def __init__(self): - # setup variables used for HTML documentation - self.server_name = 'XML-RPC Server Documentation' - self.server_documentation = \ - "This server exports the following methods through the XML-RPC "\ - "protocol." - self.server_title = 'XML-RPC Server Documentation' - - def set_server_title(self, server_title): - """Set the HTML title of the generated server documentation""" - - self.server_title = server_title - - def set_server_name(self, server_name): - """Set the name of the generated HTML server documentation""" - - self.server_name = server_name - - def set_server_documentation(self, server_documentation): - """Set the documentation string for the entire server.""" - - self.server_documentation = server_documentation - - def generate_html_documentation(self): - """generate_html_documentation() => html documentation for the server - - Generates HTML documentation for the server using introspection for - installed functions and instances that do not implement the - _dispatch method. Alternatively, instances can choose to implement - the _get_method_argstring(method_name) method to provide the - argument string used in the documentation and the - _methodHelp(method_name) method to provide the help text used - in the documentation.""" - - methods = {} - - for method_name in self.system_listMethods(): - if method_name in self.funcs: - method = self.funcs[method_name] - elif self.instance is not None: - method_info = [None, None] # argspec, documentation - if hasattr(self.instance, '_get_method_argstring'): - method_info[0] = self.instance._get_method_argstring(method_name) - if hasattr(self.instance, '_methodHelp'): - method_info[1] = self.instance._methodHelp(method_name) - - method_info = tuple(method_info) - if method_info != (None, None): - method = method_info - elif not hasattr(self.instance, '_dispatch'): - try: - method = resolve_dotted_attribute( - self.instance, - method_name - ) - except AttributeError: - method = method_info - else: - method = method_info - else: - assert 0, "Could not find method in self.functions and no "\ - "instance installed" - - methods[method_name] = method - - documenter = ServerHTMLDoc() - documentation = documenter.docserver( - self.server_name, - self.server_documentation, - methods - ) - - return documenter.page(self.server_title, documentation) - -class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): - """XML-RPC and documentation request handler class. - - Handles all HTTP POST requests and attempts to decode them as - XML-RPC requests. - - Handles all HTTP GET requests and interprets them as requests - for documentation. - """ - - def do_GET(self): - """Handles the HTTP GET request. - - Interpret all HTTP GET requests as requests for server - documentation. - """ - # Check that the path is legal - if not self.is_rpc_path_valid(): - self.report_404() - return - - response = self.server.generate_html_documentation() - self.send_response(200) - self.send_header("Content-type", "text/html") - self.send_header("Content-length", str(len(response))) - self.end_headers() - self.wfile.write(response.encode()) - - # shut down the connection - self.wfile.flush() - self.connection.shutdown(1) - -class DocXMLRPCServer( SimpleXMLRPCServer, - XMLRPCDocGenerator): - """XML-RPC and HTML documentation server. - - Adds the ability to serve server documentation to the capabilities - of SimpleXMLRPCServer. - """ - - def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler, - logRequests=1, allow_none=False, encoding=None, - bind_and_activate=True): - SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests, - allow_none, encoding, bind_and_activate) - XMLRPCDocGenerator.__init__(self) - -class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler, - XMLRPCDocGenerator): - """Handler for XML-RPC data and documentation requests passed through - CGI""" - - def handle_get(self): - """Handles the HTTP GET request. - - Interpret all HTTP GET requests as requests for server - documentation. - """ - - response = self.generate_html_documentation() - - print('Content-Type: text/html') - print('Content-Length: %d' % len(response)) - print() - sys.stdout.write(response) - - def __init__(self): - CGIXMLRPCRequestHandler.__init__(self) - XMLRPCDocGenerator.__init__(self) diff --git a/Lib/SimpleXMLRPCServer.py b/Lib/SimpleXMLRPCServer.py deleted file mode 100644 index a985153..0000000 --- a/Lib/SimpleXMLRPCServer.py +++ /dev/null @@ -1,593 +0,0 @@ -"""Simple XML-RPC Server. - -This module can be used to create simple XML-RPC servers -by creating a server and either installing functions, a -class instance, or by extending the SimpleXMLRPCServer -class. - -It can also be used to handle XML-RPC requests in a CGI -environment using CGIXMLRPCRequestHandler. - -A list of possible usage patterns follows: - -1. Install functions: - -server = SimpleXMLRPCServer(("localhost", 8000)) -server.register_function(pow) -server.register_function(lambda x,y: x+y, 'add') -server.serve_forever() - -2. Install an instance: - -class MyFuncs: - def __init__(self): - # make all of the sys functions available through sys.func_name - import sys - self.sys = sys - def _listMethods(self): - # implement this method so that system.listMethods - # knows to advertise the sys methods - return list_public_methods(self) + \ - ['sys.' + method for method in list_public_methods(self.sys)] - def pow(self, x, y): return pow(x, y) - def add(self, x, y) : return x + y - -server = SimpleXMLRPCServer(("localhost", 8000)) -server.register_introspection_functions() -server.register_instance(MyFuncs()) -server.serve_forever() - -3. Install an instance with custom dispatch method: - -class Math: - def _listMethods(self): - # this method must be present for system.listMethods - # to work - return ['add', 'pow'] - def _methodHelp(self, method): - # this method must be present for system.methodHelp - # to work - if method == 'add': - return "add(2,3) => 5" - elif method == 'pow': - return "pow(x, y[, z]) => number" - else: - # By convention, return empty - # string if no help is available - return "" - def _dispatch(self, method, params): - if method == 'pow': - return pow(*params) - elif method == 'add': - return params[0] + params[1] - else: - raise 'bad method' - -server = SimpleXMLRPCServer(("localhost", 8000)) -server.register_introspection_functions() -server.register_instance(Math()) -server.serve_forever() - -4. Subclass SimpleXMLRPCServer: - -class MathServer(SimpleXMLRPCServer): - def _dispatch(self, method, params): - try: - # We are forcing the 'export_' prefix on methods that are - # callable through XML-RPC to prevent potential security - # problems - func = getattr(self, 'export_' + method) - except AttributeError: - raise Exception('method "%s" is not supported' % method) - else: - return func(*params) - - def export_add(self, x, y): - return x + y - -server = MathServer(("localhost", 8000)) -server.serve_forever() - -5. CGI script: - -server = CGIXMLRPCRequestHandler() -server.register_function(pow) -server.handle_request() -""" - -# Written by Brian Quinlan (brian@sweetapp.com). -# Based on code written by Fredrik Lundh. - -import xmlrpclib -from xmlrpclib import Fault -import socketserver -import BaseHTTPServer -import sys -import os -import traceback -try: - import fcntl -except ImportError: - fcntl = None - -def resolve_dotted_attribute(obj, attr, allow_dotted_names=True): - """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d - - Resolves a dotted attribute name to an object. Raises - an AttributeError if any attribute in the chain starts with a '_'. - - If the optional allow_dotted_names argument is false, dots are not - supported and this function operates similar to getattr(obj, attr). - """ - - if allow_dotted_names: - attrs = attr.split('.') - else: - attrs = [attr] - - for i in attrs: - if i.startswith('_'): - raise AttributeError( - 'attempt to access private attribute "%s"' % i - ) - else: - obj = getattr(obj,i) - return obj - -def list_public_methods(obj): - """Returns a list of attribute strings, found in the specified - object, which represent callable attributes""" - - return [member for member in dir(obj) - if not member.startswith('_') and - hasattr(getattr(obj, member), '__call__')] - -class SimpleXMLRPCDispatcher: - """Mix-in class that dispatches XML-RPC requests. - - This class is used to register XML-RPC method handlers - and then to dispatch them. There should never be any - reason to instantiate this class directly. - """ - - def __init__(self, allow_none, encoding): - self.funcs = {} - self.instance = None - self.allow_none = allow_none - self.encoding = encoding - - def register_instance(self, instance, allow_dotted_names=False): - """Registers an instance to respond to XML-RPC requests. - - Only one instance can be installed at a time. - - If the registered instance has a _dispatch method then that - method will be called with the name of the XML-RPC method and - its parameters as a tuple - e.g. instance._dispatch('add',(2,3)) - - If the registered instance does not have a _dispatch method - then the instance will be searched to find a matching method - and, if found, will be called. Methods beginning with an '_' - are considered private and will not be called by - SimpleXMLRPCServer. - - If a registered function matches a XML-RPC request, then it - will be called instead of the registered instance. - - If the optional allow_dotted_names argument is true and the - instance does not have a _dispatch method, method names - containing dots are supported and resolved, as long as none of - the name segments start with an '_'. - - *** SECURITY WARNING: *** - - Enabling the allow_dotted_names options 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. - - """ - - self.instance = instance - self.allow_dotted_names = allow_dotted_names - - def register_function(self, function, name = None): - """Registers a function to respond to XML-RPC requests. - - The optional name argument can be used to set a Unicode name - for the function. - """ - - if name is None: - name = function.__name__ - self.funcs[name] = function - - def register_introspection_functions(self): - """Registers the XML-RPC introspection methods in the system - namespace. - - see http://xmlrpc.usefulinc.com/doc/reserved.html - """ - - self.funcs.update({'system.listMethods' : self.system_listMethods, - 'system.methodSignature' : self.system_methodSignature, - 'system.methodHelp' : self.system_methodHelp}) - - def register_multicall_functions(self): - """Registers the XML-RPC multicall method in the system - namespace. - - see http://www.xmlrpc.com/discuss/msgReader$1208""" - - self.funcs.update({'system.multicall' : self.system_multicall}) - - def _marshaled_dispatch(self, data, dispatch_method = None): - """Dispatches an XML-RPC method from marshalled (XML) data. - - XML-RPC methods are dispatched from the marshalled (XML) data - using the _dispatch method and the result is returned as - marshalled data. For backwards compatibility, a dispatch - function can be provided as an argument (see comment in - SimpleXMLRPCRequestHandler.do_POST) but overriding the - existing method through subclassing is the prefered means - of changing method dispatch behavior. - """ - - try: - params, method = xmlrpclib.loads(data) - - # generate response - if dispatch_method is not None: - response = dispatch_method(method, params) - else: - response = self._dispatch(method, params) - # wrap response in a singleton tuple - response = (response,) - response = xmlrpclib.dumps(response, methodresponse=1, - allow_none=self.allow_none, encoding=self.encoding) - except Fault as fault: - response = xmlrpclib.dumps(fault, allow_none=self.allow_none, - encoding=self.encoding) - except: - # report exception back to server - exc_type, exc_value, exc_tb = sys.exc_info() - response = xmlrpclib.dumps( - xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)), - encoding=self.encoding, allow_none=self.allow_none, - ) - - return response - - def system_listMethods(self): - """system.listMethods() => ['add', 'subtract', 'multiple'] - - Returns a list of the methods supported by the server.""" - - methods = set(self.funcs.keys()) - if self.instance is not None: - # Instance can implement _listMethod to return a list of - # methods - if hasattr(self.instance, '_listMethods'): - methods |= set(self.instance._listMethods()) - # if the instance has a _dispatch method then we - # don't have enough information to provide a list - # of methods - elif not hasattr(self.instance, '_dispatch'): - methods |= set(list_public_methods(self.instance)) - return sorted(methods) - - def system_methodSignature(self, method_name): - """system.methodSignature('add') => [double, int, int] - - Returns a list describing the signature of the method. In the - above example, the add method takes two integers as arguments - and returns a double result. - - This server does NOT support system.methodSignature.""" - - # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html - - return 'signatures not supported' - - def system_methodHelp(self, method_name): - """system.methodHelp('add') => "Adds two integers together" - - Returns a string containing documentation for the specified method.""" - - method = None - if method_name in self.funcs: - method = self.funcs[method_name] - elif self.instance is not None: - # Instance can implement _methodHelp to return help for a method - if hasattr(self.instance, '_methodHelp'): - return self.instance._methodHelp(method_name) - # if the instance has a _dispatch method then we - # don't have enough information to provide help - elif not hasattr(self.instance, '_dispatch'): - try: - method = resolve_dotted_attribute( - self.instance, - method_name, - self.allow_dotted_names - ) - except AttributeError: - pass - - # Note that we aren't checking that the method actually - # be a callable object of some kind - if method is None: - return "" - else: - import pydoc - return pydoc.getdoc(method) - - def system_multicall(self, call_list): - """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \ -[[4], ...] - - Allows the caller to package multiple XML-RPC calls into a single - request. - - See http://www.xmlrpc.com/discuss/msgReader$1208 - """ - - results = [] - for call in call_list: - method_name = call['methodName'] - params = call['params'] - - try: - # XXX A marshalling error in any response will fail the entire - # multicall. If someone cares they should fix this. - results.append([self._dispatch(method_name, params)]) - except Fault as fault: - results.append( - {'faultCode' : fault.faultCode, - 'faultString' : fault.faultString} - ) - except: - exc_type, exc_value, exc_tb = sys.exc_info() - results.append( - {'faultCode' : 1, - 'faultString' : "%s:%s" % (exc_type, exc_value)} - ) - return results - - def _dispatch(self, method, params): - """Dispatches the XML-RPC method. - - XML-RPC calls are forwarded to a registered function that - matches the called XML-RPC method name. If no such function - exists then the call is forwarded to the registered instance, - if available. - - If the registered instance has a _dispatch method then that - method will be called with the name of the XML-RPC method and - its parameters as a tuple - e.g. instance._dispatch('add',(2,3)) - - If the registered instance does not have a _dispatch method - then the instance will be searched to find a matching method - and, if found, will be called. - - Methods beginning with an '_' are considered private and will - not be called. - """ - - func = None - try: - # check to see if a matching function has been registered - func = self.funcs[method] - except KeyError: - if self.instance is not None: - # check for a _dispatch method - if hasattr(self.instance, '_dispatch'): - return self.instance._dispatch(method, params) - else: - # call instance method directly - try: - func = resolve_dotted_attribute( - self.instance, - method, - self.allow_dotted_names - ) - except AttributeError: - pass - - if func is not None: - return func(*params) - else: - raise Exception('method "%s" is not supported' % method) - -class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): - """Simple XML-RPC request handler class. - - Handles all HTTP POST requests and attempts to decode them as - XML-RPC requests. - """ - - # Class attribute listing the accessible path components; - # paths not on this list will result in a 404 error. - rpc_paths = ('/', '/RPC2') - - def is_rpc_path_valid(self): - if self.rpc_paths: - return self.path in self.rpc_paths - else: - # If .rpc_paths is empty, just assume all paths are legal - return True - - def do_POST(self): - """Handles the HTTP POST request. - - Attempts to interpret all HTTP POST requests as XML-RPC calls, - which are forwarded to the server's _dispatch method for handling. - """ - - # Check that the path is legal - if not self.is_rpc_path_valid(): - self.report_404() - return - - try: - # Get arguments by reading body of request. - # We read this in chunks to avoid straining - # socket.read(); around the 10 or 15Mb mark, some platforms - # begin to have problems (bug #792570). - max_chunk_size = 10*1024*1024 - size_remaining = int(self.headers["content-length"]) - L = [] - while size_remaining: - chunk_size = min(size_remaining, max_chunk_size) - L.append(self.rfile.read(chunk_size)) - size_remaining -= len(L[-1]) - data = b''.join(L) - - # In previous versions of SimpleXMLRPCServer, _dispatch - # could be overridden in this class, instead of in - # SimpleXMLRPCDispatcher. To maintain backwards compatibility, - # check to see if a subclass implements _dispatch and dispatch - # using that method if present. - response = self.server._marshaled_dispatch( - data, getattr(self, '_dispatch', None) - ) - except Exception as e: # This should only happen if the module is buggy - # internal error, report as HTTP server error - self.send_response(500) - - # Send information about the exception if requested - if hasattr(self.server, '_send_traceback_header') and \ - self.server._send_traceback_header: - self.send_header("X-exception", str(e)) - self.send_header("X-traceback", traceback.format_exc()) - - self.end_headers() - else: - # Got a valid XML RPC response; convert to bytes first - response = response.encode("utf-8") - self.send_response(200) - self.send_header("Content-type", "text/xml") - self.send_header("Content-length", str(len(response))) - self.end_headers() - self.wfile.write(response) - - # shut down the connection - self.wfile.flush() - self.connection.shutdown(1) - - def report_404 (self): - # Report a 404 error - self.send_response(404) - response = b'No such page' - self.send_header("Content-type", "text/plain") - self.send_header("Content-length", str(len(response))) - self.end_headers() - self.wfile.write(response) - # shut down the connection - self.wfile.flush() - self.connection.shutdown(1) - - def log_request(self, code='-', size='-'): - """Selectively log an accepted request.""" - - if self.server.logRequests: - BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size) - -class SimpleXMLRPCServer(socketserver.TCPServer, - SimpleXMLRPCDispatcher): - """Simple XML-RPC server. - - Simple XML-RPC server that allows functions and a single instance - to be installed to handle requests. The default implementation - attempts to dispatch XML-RPC calls to the functions or instance - installed in the server. Override the _dispatch method inhereted - from SimpleXMLRPCDispatcher to change this behavior. - """ - - allow_reuse_address = True - - # Warning: this is for debugging purposes only! Never set this to True in - # production code, as will be sending out sensitive information (exception - # and stack trace details) when exceptions are raised inside - # SimpleXMLRPCRequestHandler.do_POST - _send_traceback_header = False - - def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler, - logRequests=True, allow_none=False, encoding=None, bind_and_activate=True): - self.logRequests = logRequests - - SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding) - socketserver.TCPServer.__init__(self, addr, requestHandler, bind_and_activate) - - # [Bug #1222790] If possible, set close-on-exec flag; if a - # method spawns a subprocess, the subprocess shouldn't have - # the listening socket open. - if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'): - flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD) - flags |= fcntl.FD_CLOEXEC - fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags) - -class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher): - """Simple handler for XML-RPC data passed through CGI.""" - - def __init__(self, allow_none=False, encoding=None): - SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding) - - def handle_xmlrpc(self, request_text): - """Handle a single XML-RPC request""" - - response = self._marshaled_dispatch(request_text) - - print('Content-Type: text/xml') - print('Content-Length: %d' % len(response)) - print() - sys.stdout.write(response) - - def handle_get(self): - """Handle a single HTTP GET request. - - Default implementation indicates an error because - XML-RPC uses the POST method. - """ - - code = 400 - message, explain = \ - BaseHTTPServer.BaseHTTPRequestHandler.responses[code] - - response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \ - { - 'code' : code, - 'message' : message, - 'explain' : explain - } - print('Status: %d %s' % (code, message)) - print('Content-Type: text/html') - print('Content-Length: %d' % len(response)) - print() - sys.stdout.write(response) - - def handle_request(self, request_text = None): - """Handle a single XML-RPC request passed through a CGI post method. - - If no XML data is given then it is read from stdin. The resulting - XML-RPC response is printed to stdout along with the correct HTTP - headers. - """ - - if request_text is None and \ - os.environ.get('REQUEST_METHOD', None) == 'GET': - self.handle_get() - else: - # POST data is normally available through stdin - if request_text is None: - request_text = sys.stdin.read() - - self.handle_xmlrpc(request_text) - -if __name__ == '__main__': - print('Running XML-RPC server on port 8000') - server = SimpleXMLRPCServer(("localhost", 8000)) - server.register_function(pow) - server.register_function(lambda x,y: x+y, 'add') - server.serve_forever() diff --git a/Lib/test/test_anydbm.py b/Lib/test/test_anydbm.py deleted file mode 100644 index aab1388..0000000 --- a/Lib/test/test_anydbm.py +++ /dev/null @@ -1,147 +0,0 @@ -#! /usr/bin/env python -"""Test script for the dbm.open function based on testdumbdbm.py""" - -import os -import unittest -import dbm -import glob -import test.support - -_fname = test.support.TESTFN - -# -# Iterates over every database module supported by dbm currently available, -# setting dbm to use each in turn, and yielding that module -# -def dbm_iterator(): - old_default = dbm._defaultmod - for module in dbm._modules.values(): - dbm._defaultmod = module - yield module - dbm._defaultmod = old_default - -# -# Clean up all scratch databases we might have created during testing -# -def delete_files(): - # we don't know the precise name the underlying database uses - # so we use glob to locate all names - for f in glob.glob(_fname + "*"): - test.support.unlink(f) - - -class AnyDBMTestCase(unittest.TestCase): - _dict = {'0': b'', - 'a': b'Python:', - 'b': b'Programming', - 'c': b'the', - 'd': b'way', - 'f': b'Guido', - 'g': b'intended', - } - - def __init__(self, *args): - unittest.TestCase.__init__(self, *args) - - def test_anydbm_creation(self): - f = dbm.open(_fname, 'c') - self.assertEqual(list(f.keys()), []) - for key in self._dict: - f[key.encode("ascii")] = self._dict[key] - self.read_helper(f) - f.close() - - def test_anydbm_modification(self): - self.init_db() - f = dbm.open(_fname, 'c') - self._dict['g'] = f[b'g'] = b"indented" - self.read_helper(f) - f.close() - - def test_anydbm_read(self): - self.init_db() - f = dbm.open(_fname, 'r') - self.read_helper(f) - f.close() - - def test_anydbm_keys(self): - self.init_db() - f = dbm.open(_fname, 'r') - keys = self.keys_helper(f) - f.close() - - def test_anydbm_access(self): - self.init_db() - f = dbm.open(_fname, 'r') - key = "a".encode("ascii") - assert(key in f) - assert(f[key] == b"Python:") - f.close() - - def read_helper(self, f): - keys = self.keys_helper(f) - for key in self._dict: - self.assertEqual(self._dict[key], f[key.encode("ascii")]) - - def init_db(self): - f = dbm.open(_fname, 'n') - for k in self._dict: - f[k.encode("ascii")] = self._dict[k] - f.close() - - def keys_helper(self, f): - keys = sorted(k.decode("ascii") for k in f.keys()) - dkeys = sorted(self._dict.keys()) - self.assertEqual(keys, dkeys) - return keys - - def tearDown(self): - delete_files() - - def setUp(self): - delete_files() - - -class WhichDBTestCase(unittest.TestCase): - # Actual test methods are added to namespace after class definition. - def __init__(self, *args): - unittest.TestCase.__init__(self, *args) - - def test_whichdb(self): - for module in dbm_iterator(): - # Check whether whichdb correctly guesses module name - # for databases opened with "module" module. - # Try with empty files first - name = module.__name__ - if name == 'dbm.dumb': - continue # whichdb can't support dbm.dumb - test.support.unlink(_fname) - f = module.open(_fname, 'c') - f.close() - self.assertEqual(name, dbm.whichdb(_fname)) - # Now add a key - f = module.open(_fname, 'w') - f[b"1"] = b"1" - # and test that we can find it - self.assertTrue(b"1" in f) - # and read it - self.assertTrue(f[b"1"] == b"1") - f.close() - self.assertEqual(name, dbm.whichdb(_fname)) - - def tearDown(self): - delete_files() - - def setUp(self): - delete_files() - - -def test_main(): - try: - for module in dbm_iterator(): - test.support.run_unittest(AnyDBMTestCase, WhichDBTestCase) - finally: - delete_files() - -if __name__ == "__main__": - test_main() diff --git a/Lib/test/test_docxmlrpc.py b/Lib/test/test_docxmlrpc.py index 2af2071..9cb9ffb 100644 --- a/Lib/test/test_docxmlrpc.py +++ b/Lib/test/test_docxmlrpc.py @@ -1,10 +1,9 @@ -from DocXMLRPCServer import DocXMLRPCServer +from xmlrpc.server import DocXMLRPCServer import httplib from test import support import threading import time import unittest -import xmlrpclib PORT = None diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py index cad2b9d..25a9c9d 100644 --- a/Lib/test/test_xmlrpc.py +++ b/Lib/test/test_xmlrpc.py @@ -3,8 +3,8 @@ import datetime import sys import time import unittest -import xmlrpclib -import SimpleXMLRPCServer +import xmlrpc.client as xmlrpclib +import xmlrpc.server import threading import mimetools import httplib @@ -160,9 +160,9 @@ class FaultTestCase(unittest.TestCase): # this will raise AttirebuteError because code don't want us to use # private methods self.assertRaises(AttributeError, - SimpleXMLRPCServer.resolve_dotted_attribute, str, '__add') + xmlrpc.server.resolve_dotted_attribute, str, '__add') - self.assert_(SimpleXMLRPCServer.resolve_dotted_attribute(str, 'title')) + self.assert_(xmlrpc.server.resolve_dotted_attribute(str, 'title')) class DateTimeTestCase(unittest.TestCase): def test_default(self): @@ -249,7 +249,7 @@ def http_server(evt, numrequests): '''This is my function''' return True - class MyXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer): + class MyXMLRPCServer(xmlrpc.server.SimpleXMLRPCServer): def get_request(self): # Ensure the socket is always non-blocking. On Linux, socket # attributes are not inherited like they are on *BSD and Windows. @@ -306,7 +306,7 @@ def is_unavailable_exception(e): class SimpleServerTestCase(unittest.TestCase): def setUp(self): # enable traceback reporting - SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = True + xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = True self.evt = threading.Event() # start server thread to handle requests @@ -326,7 +326,7 @@ class SimpleServerTestCase(unittest.TestCase): raise RuntimeError("timeout reached, test has failed") # disable traceback reporting - SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = False + xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = False def test_simple1(self): try: @@ -443,9 +443,9 @@ class SimpleServerTestCase(unittest.TestCase): def test_dotted_attribute(self): # Raises an AttributeError because private methods are not allowed. self.assertRaises(AttributeError, - SimpleXMLRPCServer.resolve_dotted_attribute, str, '__add') + xmlrpc.server.resolve_dotted_attribute, str, '__add') - self.assert_(SimpleXMLRPCServer.resolve_dotted_attribute(str, 'title')) + self.assert_(xmlrpc.server.resolve_dotted_attribute(str, 'title')) # Get the test to run faster by sending a request with test_simple1. # This avoids waiting for the socket timeout. self.test_simple1() @@ -475,17 +475,17 @@ class FailingServerTestCase(unittest.TestCase): # wait on the server thread to terminate self.evt.wait() # reset flag - SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = False + xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = False # reset message class - SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.MessageClass = mimetools.Message + xmlrpc.server.SimpleXMLRPCRequestHandler.MessageClass = mimetools.Message def test_basic(self): # check that flag is false by default - flagval = SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header + flagval = xmlrpc.server.SimpleXMLRPCServer._send_traceback_header self.assertEqual(flagval, False) # enable traceback reporting - SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = True + xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = True # test a call that shouldn't fail just as a smoke test try: @@ -499,7 +499,7 @@ class FailingServerTestCase(unittest.TestCase): def test_fail_no_info(self): # use the broken message class - SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.MessageClass = FailingMessageClass + xmlrpc.server.SimpleXMLRPCRequestHandler.MessageClass = FailingMessageClass try: p = xmlrpclib.ServerProxy('http://localhost:%d' % PORT) @@ -515,11 +515,11 @@ class FailingServerTestCase(unittest.TestCase): def test_fail_with_info(self): # use the broken message class - SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.MessageClass = FailingMessageClass + xmlrpc.server.SimpleXMLRPCRequestHandler.MessageClass = FailingMessageClass # Check that errors in the server send back exception/traceback # info when flag is set - SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = True + xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = True try: p = xmlrpclib.ServerProxy('http://localhost:%d' % PORT) @@ -536,7 +536,7 @@ class FailingServerTestCase(unittest.TestCase): class CGIHandlerTestCase(unittest.TestCase): def setUp(self): - self.cgi = SimpleXMLRPCServer.CGIXMLRPCRequestHandler() + self.cgi = xmlrpc.server.CGIXMLRPCRequestHandler() def tearDown(self): self.cgi = None diff --git a/Lib/test/test_xmlrpc_net.py b/Lib/test/test_xmlrpc_net.py index 1f8dd5d..2525254 100644 --- a/Lib/test/test_xmlrpc_net.py +++ b/Lib/test/test_xmlrpc_net.py @@ -6,7 +6,7 @@ import sys import unittest from test import support -import xmlrpclib +import xmlrpclib.client as xmlrpclib class CurrentTimeTest(unittest.TestCase): diff --git a/Lib/xmlrpc/__init__.py b/Lib/xmlrpc/__init__.py new file mode 100644 index 0000000..196d378 --- /dev/null +++ b/Lib/xmlrpc/__init__.py @@ -0,0 +1 @@ +# This directory is a Python package. diff --git a/Lib/xmlrpc/client.py b/Lib/xmlrpc/client.py new file mode 100644 index 0000000..138d86d --- /dev/null +++ b/Lib/xmlrpc/client.py @@ -0,0 +1,1402 @@ +# +# XML-RPC CLIENT LIBRARY +# $Id$ +# +# an XML-RPC client interface for Python. +# +# the marshalling and response parser code can also be used to +# implement XML-RPC servers. +# +# Notes: +# this version is designed to work with Python 2.1 or newer. +# +# History: +# 1999-01-14 fl Created +# 1999-01-15 fl Changed dateTime to use localtime +# 1999-01-16 fl Added Binary/base64 element, default to RPC2 service +# 1999-01-19 fl Fixed array data element (from Skip Montanaro) +# 1999-01-21 fl Fixed dateTime constructor, etc. +# 1999-02-02 fl Added fault handling, handle empty sequences, etc. +# 1999-02-10 fl Fixed problem with empty responses (from Skip Montanaro) +# 1999-06-20 fl Speed improvements, pluggable parsers/transports (0.9.8) +# 2000-11-28 fl Changed boolean to check the truth value of its argument +# 2001-02-24 fl Added encoding/Unicode/SafeTransport patches +# 2001-02-26 fl Added compare support to wrappers (0.9.9/1.0b1) +# 2001-03-28 fl Make sure response tuple is a singleton +# 2001-03-29 fl Don't require empty params element (from Nicholas Riley) +# 2001-06-10 fl Folded in _xmlrpclib accelerator support (1.0b2) +# 2001-08-20 fl Base xmlrpclib.Error on built-in Exception (from Paul Prescod) +# 2001-09-03 fl Allow Transport subclass to override getparser +# 2001-09-10 fl Lazy import of urllib, cgi, xmllib (20x import speedup) +# 2001-10-01 fl Remove containers from memo cache when done with them +# 2001-10-01 fl Use faster escape method (80% dumps speedup) +# 2001-10-02 fl More dumps microtuning +# 2001-10-04 fl Make sure import expat gets a parser (from Guido van Rossum) +# 2001-10-10 sm Allow long ints to be passed as ints if they don't overflow +# 2001-10-17 sm Test for int and long overflow (allows use on 64-bit systems) +# 2001-11-12 fl Use repr() to marshal doubles (from Paul Felix) +# 2002-03-17 fl Avoid buffered read when possible (from James Rucker) +# 2002-04-07 fl Added pythondoc comments +# 2002-04-16 fl Added __str__ methods to datetime/binary wrappers +# 2002-05-15 fl Added error constants (from Andrew Kuchling) +# 2002-06-27 fl Merged with Python CVS version +# 2002-10-22 fl Added basic authentication (based on code from Phillip Eby) +# 2003-01-22 sm Add support for the bool type +# 2003-02-27 gvr Remove apply calls +# 2003-04-24 sm Use cStringIO if available +# 2003-04-25 ak Add support for nil +# 2003-06-15 gn Add support for time.struct_time +# 2003-07-12 gp Correct marshalling of Faults +# 2003-10-31 mvl Add multicall support +# 2004-08-20 mvl Bump minimum supported Python version to 2.1 +# +# Copyright (c) 1999-2002 by Secret Labs AB. +# Copyright (c) 1999-2002 by Fredrik Lundh. +# +# info@pythonware.com +# http://www.pythonware.com +# +# -------------------------------------------------------------------- +# The XML-RPC client interface is +# +# Copyright (c) 1999-2002 by Secret Labs AB +# Copyright (c) 1999-2002 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- + +# +# things to look into some day: + +# TODO: sort out True/False/boolean issues for Python 2.3 + +""" +An XML-RPC client interface for Python. + +The marshalling and response parser code can also be used to +implement XML-RPC servers. + +Exported exceptions: + + Error Base class for client errors + ProtocolError Indicates an HTTP protocol error + ResponseError Indicates a broken response package + Fault Indicates an XML-RPC fault package + +Exported classes: + + ServerProxy Represents a logical connection to an XML-RPC server + + MultiCall Executor of boxcared xmlrpc requests + DateTime dateTime wrapper for an ISO 8601 string or time tuple or + localtime integer value to generate a "dateTime.iso8601" + XML-RPC value + Binary binary data wrapper + + SlowParser Slow but safe standard parser (based on xmllib) + Marshaller Generate an XML-RPC params chunk from a Python data structure + Unmarshaller Unmarshal an XML-RPC response from incoming XML event message + Transport Handles an HTTP transaction to an XML-RPC server + SafeTransport Handles an HTTPS transaction to an XML-RPC server + +Exported constants: + + True + False + +Exported functions: + + getparser Create instance of the fastest available parser & attach + to an unmarshalling object + dumps Convert an argument tuple or a Fault instance to an XML-RPC + request (or response, if the methodresponse option is used). + loads Convert an XML-RPC packet to unmarshalled data plus a method + name (None if not present). +""" + +import re, time, operator +import httplib + +# -------------------------------------------------------------------- +# Internal stuff + +try: + import datetime +except ImportError: + datetime = None + +def _decode(data, encoding, is8bit=re.compile("[\x80-\xff]").search): + # decode non-ascii string (if possible) + if encoding and is8bit(data): + data = str(data, encoding) + return data + +def escape(s): + s = s.replace("&", "&") + s = s.replace("<", "<") + return s.replace(">", ">",) + +def _stringify(string): + # convert to 7-bit ascii if possible + try: + return string.decode("ascii") + except (UnicodeError, TypeError, AttributeError): + return string + +__version__ = "1.0.1" + +# xmlrpc integer limits +MAXINT = 2**31-1 +MININT = -2**31 + +# -------------------------------------------------------------------- +# Error constants (from Dan Libby's specification at +# http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php) + +# Ranges of errors +PARSE_ERROR = -32700 +SERVER_ERROR = -32600 +APPLICATION_ERROR = -32500 +SYSTEM_ERROR = -32400 +TRANSPORT_ERROR = -32300 + +# Specific errors +NOT_WELLFORMED_ERROR = -32700 +UNSUPPORTED_ENCODING = -32701 +INVALID_ENCODING_CHAR = -32702 +INVALID_XMLRPC = -32600 +METHOD_NOT_FOUND = -32601 +INVALID_METHOD_PARAMS = -32602 +INTERNAL_ERROR = -32603 + +# -------------------------------------------------------------------- +# Exceptions + +## +# Base class for all kinds of client-side errors. + +class Error(Exception): + """Base class for client errors.""" + def __str__(self): + return repr(self) + +## +# Indicates an HTTP-level protocol error. This is raised by the HTTP +# transport layer, if the server returns an error code other than 200 +# (OK). +# +# @param url The target URL. +# @param errcode The HTTP error code. +# @param errmsg The HTTP error message. +# @param headers The HTTP header dictionary. + +class ProtocolError(Error): + """Indicates an HTTP protocol error.""" + def __init__(self, url, errcode, errmsg, headers): + Error.__init__(self) + self.url = url + self.errcode = errcode + self.errmsg = errmsg + self.headers = headers + def __repr__(self): + return ( + "" % + (self.url, self.errcode, self.errmsg) + ) + +## +# Indicates a broken XML-RPC response package. This exception is +# raised by the unmarshalling layer, if the XML-RPC response is +# malformed. + +class ResponseError(Error): + """Indicates a broken response package.""" + pass + +## +# Indicates an XML-RPC fault response package. This exception is +# raised by the unmarshalling layer, if the XML-RPC response contains +# a fault string. This exception can also used as a class, to +# generate a fault XML-RPC message. +# +# @param faultCode The XML-RPC fault code. +# @param faultString The XML-RPC fault string. + +class Fault(Error): + """Indicates an XML-RPC fault package.""" + def __init__(self, faultCode, faultString, **extra): + Error.__init__(self) + self.faultCode = faultCode + self.faultString = faultString + def __repr__(self): + return ( + "" % + (self.faultCode, repr(self.faultString)) + ) + +# -------------------------------------------------------------------- +# Special values + +## +# Backwards compatibility + +boolean = Boolean = bool + +## +# Wrapper for XML-RPC DateTime values. This converts a time value to +# the format used by XML-RPC. +#

+# The value can be given as a string in the format +# "yyyymmddThh:mm:ss", as a 9-item time tuple (as returned by +# time.localtime()), or an integer value (as returned by time.time()). +# The wrapper uses time.localtime() to convert an integer to a time +# tuple. +# +# @param value The time, given as an ISO 8601 string, a time +# tuple, or a integer time value. + +def _strftime(value): + if datetime: + if isinstance(value, datetime.datetime): + return "%04d%02d%02dT%02d:%02d:%02d" % ( + value.year, value.month, value.day, + value.hour, value.minute, value.second) + + if not isinstance(value, (tuple, time.struct_time)): + if value == 0: + value = time.time() + value = time.localtime(value) + + return "%04d%02d%02dT%02d:%02d:%02d" % value[:6] + +class DateTime: + """DateTime wrapper for an ISO 8601 string or time tuple or + localtime integer value to generate 'dateTime.iso8601' XML-RPC + value. + """ + + def __init__(self, value=0): + if isinstance(value, str): + self.value = value + else: + self.value = _strftime(value) + + def make_comparable(self, other): + if isinstance(other, DateTime): + s = self.value + o = other.value + elif datetime and isinstance(other, datetime.datetime): + s = self.value + o = other.strftime("%Y%m%dT%H:%M:%S") + elif isinstance(other, (str, unicode)): + s = self.value + o = other + elif hasattr(other, "timetuple"): + s = self.timetuple() + o = other.timetuple() + else: + otype = (hasattr(other, "__class__") + and other.__class__.__name__ + or type(other)) + raise TypeError("Can't compare %s and %s" % + (self.__class__.__name__, otype)) + return s, o + + def __lt__(self, other): + s, o = self.make_comparable(other) + return s < o + + def __le__(self, other): + s, o = self.make_comparable(other) + return s <= o + + def __gt__(self, other): + s, o = self.make_comparable(other) + return s > o + + def __ge__(self, other): + s, o = self.make_comparable(other) + return s >= o + + def __eq__(self, other): + s, o = self.make_comparable(other) + return s == o + + def __ne__(self, other): + s, o = self.make_comparable(other) + return s != o + + def timetuple(self): + return time.strptime(self.value, "%Y%m%dT%H:%M:%S") + + def __cmp__(self, other): + s, o = self.make_comparable(other) + return cmp(s, o) + + ## + # Get date/time value. + # + # @return Date/time value, as an ISO 8601 string. + + def __str__(self): + return self.value + + def __repr__(self): + return "" % (repr(self.value), id(self)) + + def decode(self, data): + self.value = str(data).strip() + + def encode(self, out): + out.write("") + out.write(self.value) + out.write("\n") + +def _datetime(data): + # decode xml element contents into a DateTime structure. + value = DateTime() + value.decode(data) + return value + +def _datetime_type(data): + t = time.strptime(data, "%Y%m%dT%H:%M:%S") + return datetime.datetime(*tuple(t)[:6]) + +## +# Wrapper for binary data. This can be used to transport any kind +# of binary data over XML-RPC, using BASE64 encoding. +# +# @param data An 8-bit string containing arbitrary data. + +import base64 +import io + +class Binary: + """Wrapper for binary data.""" + + def __init__(self, data=None): + if data is None: + data = b"" + else: + if not isinstance(data, bytes): + raise TypeError("expected bytes, not %s" % + data.__class__.__name__) + data = bytes(data) # Make a copy of the bytes! + self.data = data + + ## + # Get buffer contents. + # + # @return Buffer contents, as an 8-bit string. + + def __str__(self): + return str(self.data, "latin-1") # XXX encoding?! + + def __eq__(self, other): + if isinstance(other, Binary): + other = other.data + return self.data == other + + def __ne__(self, other): + if isinstance(other, Binary): + other = other.data + return self.data != other + + def decode(self, data): + self.data = base64.decodestring(data) + + def encode(self, out): + out.write("\n") + encoded = base64.encodestring(self.data) + out.write(encoded.decode('ascii')) + out.write('\n') + out.write("\n") + +def _binary(data): + # decode xml element contents into a Binary structure + value = Binary() + value.decode(data) + return value + +WRAPPERS = (DateTime, Binary) + +# -------------------------------------------------------------------- +# XML parsers + +# +# the SGMLOP parser is about 15x faster than Python's builtin +# XML parser. SGMLOP sources can be downloaded from: +# +# http://www.pythonware.com/products/xml/sgmlop.htm +# + +try: + import sgmlop + if not hasattr(sgmlop, "XMLParser"): + raise ImportError +except ImportError: + SgmlopParser = None # sgmlop accelerator not available +else: + class SgmlopParser: + def __init__(self, target): + + # setup callbacks + self.finish_starttag = target.start + self.finish_endtag = target.end + self.handle_data = target.data + self.handle_xml = target.xml + + # activate parser + self.parser = sgmlop.XMLParser() + self.parser.register(self) + self.feed = self.parser.feed + self.entity = { + "amp": "&", "gt": ">", "lt": "<", + "apos": "'", "quot": '"' + } + + def close(self): + try: + self.parser.close() + finally: + self.parser = self.feed = None # nuke circular reference + + def handle_proc(self, tag, attr): + m = re.search("encoding\s*=\s*['\"]([^\"']+)[\"']", attr) + if m: + self.handle_xml(m.group(1), 1) + + def handle_entityref(self, entity): + # entity + try: + self.handle_data(self.entity[entity]) + except KeyError: + self.handle_data("&%s;" % entity) + +try: + from xml.parsers import expat + if not hasattr(expat, "ParserCreate"): + raise ImportError +except ImportError: + ExpatParser = None # expat not available +else: + class ExpatParser: + # fast expat parser for Python 2.0 and later. this is about + # 50% slower than sgmlop, on roundtrip testing + def __init__(self, target): + self._parser = parser = expat.ParserCreate(None, None) + self._target = target + parser.StartElementHandler = target.start + parser.EndElementHandler = target.end + parser.CharacterDataHandler = target.data + encoding = None + target.xml(encoding, None) + + def feed(self, data): + self._parser.Parse(data, 0) + + def close(self): + self._parser.Parse("", 1) # end of data + del self._target, self._parser # get rid of circular references + +# -------------------------------------------------------------------- +# XML-RPC marshalling and unmarshalling code + +## +# XML-RPC marshaller. +# +# @param encoding Default encoding for 8-bit strings. The default +# value is None (interpreted as UTF-8). +# @see dumps + +class Marshaller: + """Generate an XML-RPC params chunk from a Python data structure. + + Create a Marshaller instance for each set of parameters, and use + the "dumps" method to convert your data (represented as a tuple) + to an XML-RPC params chunk. To write a fault response, pass a + Fault instance instead. You may prefer to use the "dumps" module + function for this purpose. + """ + + # by the way, if you don't understand what's going on in here, + # that's perfectly ok. + + def __init__(self, encoding=None, allow_none=0): + self.memo = {} + self.data = None + self.encoding = encoding + self.allow_none = allow_none + + dispatch = {} + + def dumps(self, values): + out = [] + write = out.append + dump = self.__dump + if isinstance(values, Fault): + # fault instance + write("\n") + dump({'faultCode': values.faultCode, + 'faultString': values.faultString}, + write) + write("\n") + else: + # parameter block + # FIXME: the xml-rpc specification allows us to leave out + # the entire block if there are no parameters. + # however, changing this may break older code (including + # old versions of xmlrpclib.py), so this is better left as + # is for now. See @XMLRPC3 for more information. /F + write("\n") + for v in values: + write("\n") + dump(v, write) + write("\n") + write("\n") + result = "".join(out) + return result + + def __dump(self, value, write): + try: + f = self.dispatch[type(value)] + except KeyError: + # check if this object can be marshalled as a structure + try: + value.__dict__ + except: + raise TypeError("cannot marshal %s objects" % type(value)) + # check if this class is a sub-class of a basic type, + # because we don't know how to marshal these types + # (e.g. a string sub-class) + for type_ in type(value).__mro__: + if type_ in self.dispatch.keys(): + raise TypeError("cannot marshal %s objects" % type(value)) + # XXX(twouters): using "_arbitrary_instance" as key as a quick-fix + # for the p3yk merge, this should probably be fixed more neatly. + f = self.dispatch["_arbitrary_instance"] + f(self, value, write) + + def dump_nil (self, value, write): + if not self.allow_none: + raise TypeError("cannot marshal None unless allow_none is enabled") + write("") + dispatch[type(None)] = dump_nil + + def dump_int(self, value, write): + # in case ints are > 32 bits + if value > MAXINT or value < MININT: + raise OverflowError("int exceeds XML-RPC limits") + write("") + write(str(value)) + write("\n") + #dispatch[int] = dump_int + + def dump_bool(self, value, write): + write("") + write(value and "1" or "0") + write("\n") + dispatch[bool] = dump_bool + + def dump_long(self, value, write): + if value > MAXINT or value < MININT: + raise OverflowError("long int exceeds XML-RPC limits") + write("") + write(str(int(value))) + write("\n") + dispatch[int] = dump_long + + def dump_double(self, value, write): + write("") + write(repr(value)) + write("\n") + dispatch[float] = dump_double + + def dump_string(self, value, write, escape=escape): + write("") + write(escape(value)) + write("\n") + dispatch[bytes] = dump_string + + def dump_unicode(self, value, write, escape=escape): + write("") + write(escape(value)) + write("\n") + dispatch[str] = dump_unicode + + def dump_array(self, value, write): + i = id(value) + if i in self.memo: + raise TypeError("cannot marshal recursive sequences") + self.memo[i] = None + dump = self.__dump + write("\n") + for v in value: + dump(v, write) + write("\n") + del self.memo[i] + dispatch[tuple] = dump_array + dispatch[list] = dump_array + + def dump_struct(self, value, write, escape=escape): + i = id(value) + if i in self.memo: + raise TypeError("cannot marshal recursive dictionaries") + self.memo[i] = None + dump = self.__dump + write("\n") + for k, v in value.items(): + write("\n") + if not isinstance(k, str): + raise TypeError("dictionary key must be string") + write("%s\n" % escape(k)) + dump(v, write) + write("\n") + write("\n") + del self.memo[i] + dispatch[dict] = dump_struct + + if datetime: + def dump_datetime(self, value, write): + write("") + write(_strftime(value)) + write("\n") + dispatch[datetime.datetime] = dump_datetime + + def dump_instance(self, value, write): + # check for special wrappers + if value.__class__ in WRAPPERS: + self.write = write + value.encode(self) + del self.write + else: + # store instance attributes as a struct (really?) + self.dump_struct(value.__dict__, write) + dispatch[DateTime] = dump_instance + dispatch[Binary] = dump_instance + # XXX(twouters): using "_arbitrary_instance" as key as a quick-fix + # for the p3yk merge, this should probably be fixed more neatly. + dispatch["_arbitrary_instance"] = dump_instance + +## +# XML-RPC unmarshaller. +# +# @see loads + +class Unmarshaller: + """Unmarshal an XML-RPC response, based on incoming XML event + messages (start, data, end). Call close() to get the resulting + data structure. + + Note that this reader is fairly tolerant, and gladly accepts bogus + XML-RPC data without complaining (but not bogus XML). + """ + + # and again, if you don't understand what's going on in here, + # that's perfectly ok. + + def __init__(self, use_datetime=0): + self._type = None + self._stack = [] + self._marks = [] + self._data = [] + self._methodname = None + self._encoding = "utf-8" + self.append = self._stack.append + self._use_datetime = use_datetime + if use_datetime and not datetime: + raise ValueError("the datetime module is not available") + + def close(self): + # return response tuple and target method + if self._type is None or self._marks: + raise ResponseError() + if self._type == "fault": + raise Fault(**self._stack[0]) + return tuple(self._stack) + + def getmethodname(self): + return self._methodname + + # + # event handlers + + def xml(self, encoding, standalone): + self._encoding = encoding + # FIXME: assert standalone == 1 ??? + + def start(self, tag, attrs): + # prepare to handle this element + if tag == "array" or tag == "struct": + self._marks.append(len(self._stack)) + self._data = [] + self._value = (tag == "value") + + def data(self, text): + self._data.append(text) + + def end(self, tag): + # call the appropriate end tag handler + try: + f = self.dispatch[tag] + except KeyError: + pass # unknown tag ? + else: + return f(self, "".join(self._data)) + + # + # accelerator support + + def end_dispatch(self, tag, data): + # dispatch data + try: + f = self.dispatch[tag] + except KeyError: + pass # unknown tag ? + else: + return f(self, data) + + # + # element decoders + + dispatch = {} + + def end_nil (self, data): + self.append(None) + self._value = 0 + dispatch["nil"] = end_nil + + def end_boolean(self, data): + if data == "0": + self.append(False) + elif data == "1": + self.append(True) + else: + raise TypeError("bad boolean value") + self._value = 0 + dispatch["boolean"] = end_boolean + + def end_int(self, data): + self.append(int(data)) + self._value = 0 + dispatch["i4"] = end_int + dispatch["int"] = end_int + + def end_double(self, data): + self.append(float(data)) + self._value = 0 + dispatch["double"] = end_double + + def end_string(self, data): + if self._encoding: + data = _decode(data, self._encoding) + self.append(_stringify(data)) + self._value = 0 + dispatch["string"] = end_string + dispatch["name"] = end_string # struct keys are always strings + + def end_array(self, data): + mark = self._marks.pop() + # map arrays to Python lists + self._stack[mark:] = [self._stack[mark:]] + self._value = 0 + dispatch["array"] = end_array + + def end_struct(self, data): + mark = self._marks.pop() + # map structs to Python dictionaries + dict = {} + items = self._stack[mark:] + for i in range(0, len(items), 2): + dict[_stringify(items[i])] = items[i+1] + self._stack[mark:] = [dict] + self._value = 0 + dispatch["struct"] = end_struct + + def end_base64(self, data): + value = Binary() + value.decode(data.encode("ascii")) + self.append(value) + self._value = 0 + dispatch["base64"] = end_base64 + + def end_dateTime(self, data): + value = DateTime() + value.decode(data) + if self._use_datetime: + value = _datetime_type(data) + self.append(value) + dispatch["dateTime.iso8601"] = end_dateTime + + def end_value(self, data): + # if we stumble upon a value element with no internal + # elements, treat it as a string element + if self._value: + self.end_string(data) + dispatch["value"] = end_value + + def end_params(self, data): + self._type = "params" + dispatch["params"] = end_params + + def end_fault(self, data): + self._type = "fault" + dispatch["fault"] = end_fault + + def end_methodName(self, data): + if self._encoding: + data = _decode(data, self._encoding) + self._methodname = data + self._type = "methodName" # no params + dispatch["methodName"] = end_methodName + +## Multicall support +# + +class _MultiCallMethod: + # some lesser magic to store calls made to a MultiCall object + # for batch execution + def __init__(self, call_list, name): + self.__call_list = call_list + self.__name = name + def __getattr__(self, name): + return _MultiCallMethod(self.__call_list, "%s.%s" % (self.__name, name)) + def __call__(self, *args): + self.__call_list.append((self.__name, args)) + +class MultiCallIterator: + """Iterates over the results of a multicall. Exceptions are + thrown in response to xmlrpc faults.""" + + def __init__(self, results): + self.results = results + + def __getitem__(self, i): + item = self.results[i] + if type(item) == type({}): + raise Fault(item['faultCode'], item['faultString']) + elif type(item) == type([]): + return item[0] + else: + raise ValueError("unexpected type in multicall result") + +class MultiCall: + """server -> a object used to boxcar method calls + + server should be a ServerProxy object. + + Methods can be added to the MultiCall using normal + method call syntax e.g.: + + multicall = MultiCall(server_proxy) + multicall.add(2,3) + multicall.get_address("Guido") + + To execute the multicall, call the MultiCall object e.g.: + + add_result, address = multicall() + """ + + def __init__(self, server): + self.__server = server + self.__call_list = [] + + def __repr__(self): + return "" % id(self) + + __str__ = __repr__ + + def __getattr__(self, name): + return _MultiCallMethod(self.__call_list, name) + + def __call__(self): + marshalled_list = [] + for name, args in self.__call_list: + marshalled_list.append({'methodName' : name, 'params' : args}) + + return MultiCallIterator(self.__server.system.multicall(marshalled_list)) + +# -------------------------------------------------------------------- +# convenience functions + +FastMarshaller = FastParser = FastUnmarshaller = None + +## +# Create a parser object, and connect it to an unmarshalling instance. +# This function picks the fastest available XML parser. +# +# return A (parser, unmarshaller) tuple. + +def getparser(use_datetime=0): + """getparser() -> parser, unmarshaller + + Create an instance of the fastest available parser, and attach it + to an unmarshalling object. Return both objects. + """ + if use_datetime and not datetime: + raise ValueError("the datetime module is not available") + if FastParser and FastUnmarshaller: + if use_datetime: + mkdatetime = _datetime_type + else: + mkdatetime = _datetime + target = FastUnmarshaller(True, False, _binary, mkdatetime, Fault) + parser = FastParser(target) + else: + target = Unmarshaller(use_datetime=use_datetime) + if FastParser: + parser = FastParser(target) + elif SgmlopParser: + parser = SgmlopParser(target) + elif ExpatParser: + parser = ExpatParser(target) + else: + parser = SlowParser(target) + return parser, target + +## +# Convert a Python tuple or a Fault instance to an XML-RPC packet. +# +# @def dumps(params, **options) +# @param params A tuple or Fault instance. +# @keyparam methodname If given, create a methodCall request for +# this method name. +# @keyparam methodresponse If given, create a methodResponse packet. +# If used with a tuple, the tuple must be a singleton (that is, +# it must contain exactly one element). +# @keyparam encoding The packet encoding. +# @return A string containing marshalled data. + +def dumps(params, methodname=None, methodresponse=None, encoding=None, + allow_none=0): + """data [,options] -> marshalled data + + Convert an argument tuple or a Fault instance to an XML-RPC + request (or response, if the methodresponse option is used). + + In addition to the data object, the following options can be given + as keyword arguments: + + methodname: the method name for a methodCall packet + + methodresponse: true to create a methodResponse packet. + If this option is used with a tuple, the tuple must be + a singleton (i.e. it can contain only one element). + + encoding: the packet encoding (default is UTF-8) + + All 8-bit strings in the data structure are assumed to use the + packet encoding. Unicode strings are automatically converted, + where necessary. + """ + + assert isinstance(params, (tuple, Fault)), "argument must be tuple or Fault instance" + if isinstance(params, Fault): + methodresponse = 1 + elif methodresponse and isinstance(params, tuple): + assert len(params) == 1, "response tuple must be a singleton" + + if not encoding: + encoding = "utf-8" + + if FastMarshaller: + m = FastMarshaller(encoding) + else: + m = Marshaller(encoding, allow_none) + + data = m.dumps(params) + + if encoding != "utf-8": + xmlheader = "\n" % str(encoding) + else: + xmlheader = "\n" # utf-8 is default + + # standard XML-RPC wrappings + if methodname: + # a method call + if not isinstance(methodname, str): + methodname = methodname.encode(encoding) + data = ( + xmlheader, + "\n" + "", methodname, "\n", + data, + "\n" + ) + elif methodresponse: + # a method response, or a fault structure + data = ( + xmlheader, + "\n", + data, + "\n" + ) + else: + return data # return as is + return "".join(data) + +## +# Convert an XML-RPC packet to a Python object. If the XML-RPC packet +# represents a fault condition, this function raises a Fault exception. +# +# @param data An XML-RPC packet, given as an 8-bit string. +# @return A tuple containing the unpacked data, and the method name +# (None if not present). +# @see Fault + +def loads(data, use_datetime=0): + """data -> unmarshalled data, method name + + Convert an XML-RPC packet to unmarshalled data plus a method + name (None if not present). + + If the XML-RPC packet represents a fault condition, this function + raises a Fault exception. + """ + p, u = getparser(use_datetime=use_datetime) + p.feed(data) + p.close() + return u.close(), u.getmethodname() + + +# -------------------------------------------------------------------- +# request dispatcher + +class _Method: + # some magic to bind an XML-RPC method to an RPC server. + # supports "nested" methods (e.g. examples.getStateName) + def __init__(self, send, name): + self.__send = send + self.__name = name + def __getattr__(self, name): + return _Method(self.__send, "%s.%s" % (self.__name, name)) + def __call__(self, *args): + return self.__send(self.__name, args) + +## +# Standard transport class for XML-RPC over HTTP. +#

+# You can create custom transports by subclassing this method, and +# overriding selected methods. + +class Transport: + """Handles an HTTP transaction to an XML-RPC server.""" + + # client identifier (may be overridden) + user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__ + + def __init__(self, use_datetime=0): + self._use_datetime = use_datetime + + ## + # Send a complete request, and parse the response. + # + # @param host Target host. + # @param handler Target PRC handler. + # @param request_body XML-RPC request body. + # @param verbose Debugging flag. + # @return Parsed response. + + def request(self, host, handler, request_body, verbose=0): + # issue XML-RPC request + + http_conn = self.send_request(host, handler, request_body, verbose) + resp = http_conn.getresponse() + + if resp.status != 200: + raise ProtocolError( + host + handler, + resp.status, resp.reason, + dict(resp.getheaders()) + ) + + self.verbose = verbose + + return self._parse_response(resp, None) + + ## + # Create parser. + # + # @return A 2-tuple containing a parser and a unmarshaller. + + def getparser(self): + # get parser and unmarshaller + return getparser(use_datetime=self._use_datetime) + + ## + # Get authorization info from host parameter + # Host may be a string, or a (host, x509-dict) tuple; if a string, + # it is checked for a "user:pw@host" format, and a "Basic + # Authentication" header is added if appropriate. + # + # @param host Host descriptor (URL or (URL, x509 info) tuple). + # @return A 3-tuple containing (actual host, extra headers, + # x509 info). The header and x509 fields may be None. + + def get_host_info(self, host): + + x509 = {} + if isinstance(host, tuple): + host, x509 = host + + import urllib + auth, host = urllib.splituser(host) + + if auth: + import base64 + auth = base64.encodestring(urllib.unquote(auth)) + auth = "".join(auth.split()) # get rid of whitespace + extra_headers = [ + ("Authorization", "Basic " + auth) + ] + else: + extra_headers = None + + return host, extra_headers, x509 + + ## + # Connect to server. + # + # @param host Target host. + # @return An HTTPConnection object + + def make_connection(self, host): + # create a HTTP connection object from a host descriptor + host, extra_headers, x509 = self.get_host_info(host) + + + ## + # Send HTTP request. + # + # @param host Host descriptor (URL or (URL, x509 info) tuple). + # @param handler Targer RPC handler (a path relative to host) + # @param request_body The XML-RPC request body + # @param debug Enable debugging if debug is true. + # @return An HTTPConnection. + + def send_request(self, host, handler, request_body, debug): + host, extra_headers, x509 = self.get_host_info(host) + connection = httplib.HTTPConnection(host) + if debug: + connection.set_debuglevel(1) + headers = {} + if extra_headers: + for key, val in extra_headers: + header[key] = val + headers["Content-Type"] = "text/xml" + headers["User-Agent"] = self.user_agent + connection.request("POST", handler, request_body, headers) + return connection + + ## + # Parse response. + # + # @param file Stream. + # @return Response tuple and target method. + + def parse_response(self, file): + # compatibility interface + return self._parse_response(file, None) + + ## + # Parse response (alternate interface). This is similar to the + # parse_response method, but also provides direct access to the + # underlying socket object (where available). + # + # @param file Stream. + # @param sock Socket handle (or None, if the socket object + # could not be accessed). + # @return Response tuple and target method. + + def _parse_response(self, file, sock): + # read response from input file/socket, and parse it + + p, u = self.getparser() + + while 1: + if sock: + response = sock.recv(1024) + else: + response = file.read(1024) + if not response: + break + if self.verbose: + print("body:", repr(response)) + p.feed(response) + + file.close() + p.close() + + return u.close() + +## +# Standard transport class for XML-RPC over HTTPS. + +class SafeTransport(Transport): + """Handles an HTTPS transaction to an XML-RPC server.""" + + # FIXME: mostly untested + + def send_request(self, host, handler, request_body, debug): + import socket + if not hasattr(socket, "ssl"): + raise NotImplementedError( + "your version of httplib doesn't support HTTPS") + + host, extra_headers, x509 = self.get_host_info(host) + connection = httplib.HTTPSConnection(host, None, **(x509 or {})) + if debug: + connection.set_debuglevel(1) + headers = {} + if extra_headers: + for key, val in extra_headers: + header[key] = val + headers["Content-Type"] = "text/xml" + headers["User-Agent"] = self.user_agent + connection.request("POST", handler, request_body, headers) + return connection + +## +# Standard server proxy. This class establishes a virtual connection +# to an XML-RPC server. +#

+# This class is available as ServerProxy and Server. New code should +# use ServerProxy, to avoid confusion. +# +# @def ServerProxy(uri, **options) +# @param uri The connection point on the server. +# @keyparam transport A transport factory, compatible with the +# standard transport class. +# @keyparam encoding The default encoding used for 8-bit strings +# (default is UTF-8). +# @keyparam verbose Use a true value to enable debugging output. +# (printed to standard output). +# @see Transport + +class ServerProxy: + """uri [,options] -> a logical connection to an XML-RPC server + + uri is the connection point on the server, given as + scheme://host/target. + + The standard implementation always supports the "http" scheme. If + SSL socket support is available (Python 2.0), it also supports + "https". + + If the target part and the slash preceding it are both omitted, + "/RPC2" is assumed. + + The following options can be given as keyword arguments: + + transport: a transport factory + encoding: the request encoding (default is UTF-8) + + All 8-bit strings passed to the server proxy are assumed to use + the given encoding. + """ + + def __init__(self, uri, transport=None, encoding=None, verbose=0, + allow_none=0, use_datetime=0): + # establish a "logical" server connection + + # get the url + import urllib + type, uri = urllib.splittype(uri) + if type not in ("http", "https"): + raise IOError("unsupported XML-RPC protocol") + self.__host, self.__handler = urllib.splithost(uri) + if not self.__handler: + self.__handler = "/RPC2" + + if transport is None: + if type == "https": + transport = SafeTransport(use_datetime=use_datetime) + else: + transport = Transport(use_datetime=use_datetime) + self.__transport = transport + + self.__encoding = encoding + self.__verbose = verbose + self.__allow_none = allow_none + + def __request(self, methodname, params): + # call a method on the remote server + + request = dumps(params, methodname, encoding=self.__encoding, + allow_none=self.__allow_none) + + response = self.__transport.request( + self.__host, + self.__handler, + request, + verbose=self.__verbose + ) + + if len(response) == 1: + response = response[0] + + return response + + def __repr__(self): + return ( + "" % + (self.__host, self.__handler) + ) + + __str__ = __repr__ + + def __getattr__(self, name): + # magic method dispatcher + return _Method(self.__request, name) + + # note: to call a remote object with an non-standard name, use + # result getattr(server, "strange-python-name")(args) + +# compatibility + +Server = ServerProxy + +# -------------------------------------------------------------------- +# test code + +if __name__ == "__main__": + + # simple test program (from the XML-RPC specification) + + # server = ServerProxy("http://localhost:8000") # local server + server = ServerProxy("http://time.xmlrpc.com/RPC2") + + try: + print(server.currentTime.getCurrentTime()) + except Error as v: + print("ERROR", v) + + # The server at xmlrpc.com doesn't seem to support multicall anymore. + multi = MultiCall(server) + multi.currentTime.getCurrentTime() + multi.currentTime.getCurrentTime() + try: + for response in multi(): + print(response) + except Error as v: + print("ERROR", v) diff --git a/Lib/xmlrpc/server.py b/Lib/xmlrpc/server.py new file mode 100644 index 0000000..9668c8c --- /dev/null +++ b/Lib/xmlrpc/server.py @@ -0,0 +1,868 @@ +"""XML-RPC Servers. + +This module can be used to create simple XML-RPC servers +by creating a server and either installing functions, a +class instance, or by extending the SimpleXMLRPCServer +class. + +It can also be used to handle XML-RPC requests in a CGI +environment using CGIXMLRPCRequestHandler. + +The Doc* classes can be used to create XML-RPC servers that +serve pydoc-style documentation in response to HTTP +GET requests. This documentation is dynamically generated +based on the functions and methods registered with the +server. + +A list of possible usage patterns follows: + +1. Install functions: + +server = SimpleXMLRPCServer(("localhost", 8000)) +server.register_function(pow) +server.register_function(lambda x,y: x+y, 'add') +server.serve_forever() + +2. Install an instance: + +class MyFuncs: + def __init__(self): + # make all of the sys functions available through sys.func_name + import sys + self.sys = sys + def _listMethods(self): + # implement this method so that system.listMethods + # knows to advertise the sys methods + return list_public_methods(self) + \ + ['sys.' + method for method in list_public_methods(self.sys)] + def pow(self, x, y): return pow(x, y) + def add(self, x, y) : return x + y + +server = SimpleXMLRPCServer(("localhost", 8000)) +server.register_introspection_functions() +server.register_instance(MyFuncs()) +server.serve_forever() + +3. Install an instance with custom dispatch method: + +class Math: + def _listMethods(self): + # this method must be present for system.listMethods + # to work + return ['add', 'pow'] + def _methodHelp(self, method): + # this method must be present for system.methodHelp + # to work + if method == 'add': + return "add(2,3) => 5" + elif method == 'pow': + return "pow(x, y[, z]) => number" + else: + # By convention, return empty + # string if no help is available + return "" + def _dispatch(self, method, params): + if method == 'pow': + return pow(*params) + elif method == 'add': + return params[0] + params[1] + else: + raise 'bad method' + +server = SimpleXMLRPCServer(("localhost", 8000)) +server.register_introspection_functions() +server.register_instance(Math()) +server.serve_forever() + +4. Subclass SimpleXMLRPCServer: + +class MathServer(SimpleXMLRPCServer): + def _dispatch(self, method, params): + try: + # We are forcing the 'export_' prefix on methods that are + # callable through XML-RPC to prevent potential security + # problems + func = getattr(self, 'export_' + method) + except AttributeError: + raise Exception('method "%s" is not supported' % method) + else: + return func(*params) + + def export_add(self, x, y): + return x + y + +server = MathServer(("localhost", 8000)) +server.serve_forever() + +5. CGI script: + +server = CGIXMLRPCRequestHandler() +server.register_function(pow) +server.handle_request() +""" + +# Written by Brian Quinlan (brian@sweetapp.com). +# Based on code written by Fredrik Lundh. + +from xmlrpc.client import Fault, dumps, loads +import socketserver +import BaseHTTPServer +import sys +import os +import re +import pydoc +import inspect +import traceback +try: + import fcntl +except ImportError: + fcntl = None + +def resolve_dotted_attribute(obj, attr, allow_dotted_names=True): + """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d + + Resolves a dotted attribute name to an object. Raises + an AttributeError if any attribute in the chain starts with a '_'. + + If the optional allow_dotted_names argument is false, dots are not + supported and this function operates similar to getattr(obj, attr). + """ + + if allow_dotted_names: + attrs = attr.split('.') + else: + attrs = [attr] + + for i in attrs: + if i.startswith('_'): + raise AttributeError( + 'attempt to access private attribute "%s"' % i + ) + else: + obj = getattr(obj,i) + return obj + +def list_public_methods(obj): + """Returns a list of attribute strings, found in the specified + object, which represent callable attributes""" + + return [member for member in dir(obj) + if not member.startswith('_') and + hasattr(getattr(obj, member), '__call__')] + +class SimpleXMLRPCDispatcher: + """Mix-in class that dispatches XML-RPC requests. + + This class is used to register XML-RPC method handlers + and then to dispatch them. There should never be any + reason to instantiate this class directly. + """ + + def __init__(self, allow_none, encoding): + self.funcs = {} + self.instance = None + self.allow_none = allow_none + self.encoding = encoding + + def register_instance(self, instance, allow_dotted_names=False): + """Registers an instance to respond to XML-RPC requests. + + Only one instance can be installed at a time. + + If the registered instance has a _dispatch method then that + method will be called with the name of the XML-RPC method and + its parameters as a tuple + e.g. instance._dispatch('add',(2,3)) + + If the registered instance does not have a _dispatch method + then the instance will be searched to find a matching method + and, if found, will be called. Methods beginning with an '_' + are considered private and will not be called by + SimpleXMLRPCServer. + + If a registered function matches a XML-RPC request, then it + will be called instead of the registered instance. + + If the optional allow_dotted_names argument is true and the + instance does not have a _dispatch method, method names + containing dots are supported and resolved, as long as none of + the name segments start with an '_'. + + *** SECURITY WARNING: *** + + Enabling the allow_dotted_names options 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. + + """ + + self.instance = instance + self.allow_dotted_names = allow_dotted_names + + def register_function(self, function, name = None): + """Registers a function to respond to XML-RPC requests. + + The optional name argument can be used to set a Unicode name + for the function. + """ + + if name is None: + name = function.__name__ + self.funcs[name] = function + + def register_introspection_functions(self): + """Registers the XML-RPC introspection methods in the system + namespace. + + see http://xmlrpc.usefulinc.com/doc/reserved.html + """ + + self.funcs.update({'system.listMethods' : self.system_listMethods, + 'system.methodSignature' : self.system_methodSignature, + 'system.methodHelp' : self.system_methodHelp}) + + def register_multicall_functions(self): + """Registers the XML-RPC multicall method in the system + namespace. + + see http://www.xmlrpc.com/discuss/msgReader$1208""" + + self.funcs.update({'system.multicall' : self.system_multicall}) + + def _marshaled_dispatch(self, data, dispatch_method = None): + """Dispatches an XML-RPC method from marshalled (XML) data. + + XML-RPC methods are dispatched from the marshalled (XML) data + using the _dispatch method and the result is returned as + marshalled data. For backwards compatibility, a dispatch + function can be provided as an argument (see comment in + SimpleXMLRPCRequestHandler.do_POST) but overriding the + existing method through subclassing is the prefered means + of changing method dispatch behavior. + """ + + try: + params, method = loads(data) + + # generate response + if dispatch_method is not None: + response = dispatch_method(method, params) + else: + response = self._dispatch(method, params) + # wrap response in a singleton tuple + response = (response,) + response = dumps(response, methodresponse=1, + allow_none=self.allow_none, encoding=self.encoding) + except Fault as fault: + response = dumps(fault, allow_none=self.allow_none, + encoding=self.encoding) + except: + # report exception back to server + exc_type, exc_value, exc_tb = sys.exc_info() + response = dumps( + Fault(1, "%s:%s" % (exc_type, exc_value)), + encoding=self.encoding, allow_none=self.allow_none, + ) + + return response + + def system_listMethods(self): + """system.listMethods() => ['add', 'subtract', 'multiple'] + + Returns a list of the methods supported by the server.""" + + methods = set(self.funcs.keys()) + if self.instance is not None: + # Instance can implement _listMethod to return a list of + # methods + if hasattr(self.instance, '_listMethods'): + methods |= set(self.instance._listMethods()) + # if the instance has a _dispatch method then we + # don't have enough information to provide a list + # of methods + elif not hasattr(self.instance, '_dispatch'): + methods |= set(list_public_methods(self.instance)) + return sorted(methods) + + def system_methodSignature(self, method_name): + """system.methodSignature('add') => [double, int, int] + + Returns a list describing the signature of the method. In the + above example, the add method takes two integers as arguments + and returns a double result. + + This server does NOT support system.methodSignature.""" + + # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html + + return 'signatures not supported' + + def system_methodHelp(self, method_name): + """system.methodHelp('add') => "Adds two integers together" + + Returns a string containing documentation for the specified method.""" + + method = None + if method_name in self.funcs: + method = self.funcs[method_name] + elif self.instance is not None: + # Instance can implement _methodHelp to return help for a method + if hasattr(self.instance, '_methodHelp'): + return self.instance._methodHelp(method_name) + # if the instance has a _dispatch method then we + # don't have enough information to provide help + elif not hasattr(self.instance, '_dispatch'): + try: + method = resolve_dotted_attribute( + self.instance, + method_name, + self.allow_dotted_names + ) + except AttributeError: + pass + + # Note that we aren't checking that the method actually + # be a callable object of some kind + if method is None: + return "" + else: + import pydoc + return pydoc.getdoc(method) + + def system_multicall(self, call_list): + """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \ +[[4], ...] + + Allows the caller to package multiple XML-RPC calls into a single + request. + + See http://www.xmlrpc.com/discuss/msgReader$1208 + """ + + results = [] + for call in call_list: + method_name = call['methodName'] + params = call['params'] + + try: + # XXX A marshalling error in any response will fail the entire + # multicall. If someone cares they should fix this. + results.append([self._dispatch(method_name, params)]) + except Fault as fault: + results.append( + {'faultCode' : fault.faultCode, + 'faultString' : fault.faultString} + ) + except: + exc_type, exc_value, exc_tb = sys.exc_info() + results.append( + {'faultCode' : 1, + 'faultString' : "%s:%s" % (exc_type, exc_value)} + ) + return results + + def _dispatch(self, method, params): + """Dispatches the XML-RPC method. + + XML-RPC calls are forwarded to a registered function that + matches the called XML-RPC method name. If no such function + exists then the call is forwarded to the registered instance, + if available. + + If the registered instance has a _dispatch method then that + method will be called with the name of the XML-RPC method and + its parameters as a tuple + e.g. instance._dispatch('add',(2,3)) + + If the registered instance does not have a _dispatch method + then the instance will be searched to find a matching method + and, if found, will be called. + + Methods beginning with an '_' are considered private and will + not be called. + """ + + func = None + try: + # check to see if a matching function has been registered + func = self.funcs[method] + except KeyError: + if self.instance is not None: + # check for a _dispatch method + if hasattr(self.instance, '_dispatch'): + return self.instance._dispatch(method, params) + else: + # call instance method directly + try: + func = resolve_dotted_attribute( + self.instance, + method, + self.allow_dotted_names + ) + except AttributeError: + pass + + if func is not None: + return func(*params) + else: + raise Exception('method "%s" is not supported' % method) + +class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + """Simple XML-RPC request handler class. + + Handles all HTTP POST requests and attempts to decode them as + XML-RPC requests. + """ + + # Class attribute listing the accessible path components; + # paths not on this list will result in a 404 error. + rpc_paths = ('/', '/RPC2') + + def is_rpc_path_valid(self): + if self.rpc_paths: + return self.path in self.rpc_paths + else: + # If .rpc_paths is empty, just assume all paths are legal + return True + + def do_POST(self): + """Handles the HTTP POST request. + + Attempts to interpret all HTTP POST requests as XML-RPC calls, + which are forwarded to the server's _dispatch method for handling. + """ + + # Check that the path is legal + if not self.is_rpc_path_valid(): + self.report_404() + return + + try: + # Get arguments by reading body of request. + # We read this in chunks to avoid straining + # socket.read(); around the 10 or 15Mb mark, some platforms + # begin to have problems (bug #792570). + max_chunk_size = 10*1024*1024 + size_remaining = int(self.headers["content-length"]) + L = [] + while size_remaining: + chunk_size = min(size_remaining, max_chunk_size) + L.append(self.rfile.read(chunk_size)) + size_remaining -= len(L[-1]) + data = b''.join(L) + + # In previous versions of SimpleXMLRPCServer, _dispatch + # could be overridden in this class, instead of in + # SimpleXMLRPCDispatcher. To maintain backwards compatibility, + # check to see if a subclass implements _dispatch and dispatch + # using that method if present. + response = self.server._marshaled_dispatch( + data, getattr(self, '_dispatch', None) + ) + except Exception as e: # This should only happen if the module is buggy + # internal error, report as HTTP server error + self.send_response(500) + + # Send information about the exception if requested + if hasattr(self.server, '_send_traceback_header') and \ + self.server._send_traceback_header: + self.send_header("X-exception", str(e)) + self.send_header("X-traceback", traceback.format_exc()) + + self.end_headers() + else: + # Got a valid XML RPC response; convert to bytes first + response = response.encode("utf-8") + self.send_response(200) + self.send_header("Content-type", "text/xml") + self.send_header("Content-length", str(len(response))) + self.end_headers() + self.wfile.write(response) + + # shut down the connection + self.wfile.flush() + self.connection.shutdown(1) + + def report_404 (self): + # Report a 404 error + self.send_response(404) + response = b'No such page' + self.send_header("Content-type", "text/plain") + self.send_header("Content-length", str(len(response))) + self.end_headers() + self.wfile.write(response) + # shut down the connection + self.wfile.flush() + self.connection.shutdown(1) + + def log_request(self, code='-', size='-'): + """Selectively log an accepted request.""" + + if self.server.logRequests: + BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size) + +class SimpleXMLRPCServer(socketserver.TCPServer, + SimpleXMLRPCDispatcher): + """Simple XML-RPC server. + + Simple XML-RPC server that allows functions and a single instance + to be installed to handle requests. The default implementation + attempts to dispatch XML-RPC calls to the functions or instance + installed in the server. Override the _dispatch method inhereted + from SimpleXMLRPCDispatcher to change this behavior. + """ + + allow_reuse_address = True + + # Warning: this is for debugging purposes only! Never set this to True in + # production code, as will be sending out sensitive information (exception + # and stack trace details) when exceptions are raised inside + # SimpleXMLRPCRequestHandler.do_POST + _send_traceback_header = False + + def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler, + logRequests=True, allow_none=False, encoding=None, bind_and_activate=True): + self.logRequests = logRequests + + SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding) + socketserver.TCPServer.__init__(self, addr, requestHandler, bind_and_activate) + + # [Bug #1222790] If possible, set close-on-exec flag; if a + # method spawns a subprocess, the subprocess shouldn't have + # the listening socket open. + if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'): + flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD) + flags |= fcntl.FD_CLOEXEC + fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags) + +class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher): + """Simple handler for XML-RPC data passed through CGI.""" + + def __init__(self, allow_none=False, encoding=None): + SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding) + + def handle_xmlrpc(self, request_text): + """Handle a single XML-RPC request""" + + response = self._marshaled_dispatch(request_text) + + print('Content-Type: text/xml') + print('Content-Length: %d' % len(response)) + print() + sys.stdout.write(response) + + def handle_get(self): + """Handle a single HTTP GET request. + + Default implementation indicates an error because + XML-RPC uses the POST method. + """ + + code = 400 + message, explain = \ + BaseHTTPServer.BaseHTTPRequestHandler.responses[code] + + response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \ + { + 'code' : code, + 'message' : message, + 'explain' : explain + } + print('Status: %d %s' % (code, message)) + print('Content-Type: text/html') + print('Content-Length: %d' % len(response)) + print() + sys.stdout.write(response) + + def handle_request(self, request_text = None): + """Handle a single XML-RPC request passed through a CGI post method. + + If no XML data is given then it is read from stdin. The resulting + XML-RPC response is printed to stdout along with the correct HTTP + headers. + """ + + if request_text is None and \ + os.environ.get('REQUEST_METHOD', None) == 'GET': + self.handle_get() + else: + # POST data is normally available through stdin + if request_text is None: + request_text = sys.stdin.read() + + self.handle_xmlrpc(request_text) + + +# ----------------------------------------------------------------------------- +# 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. + Each context dictionary maps object names to anchor names.""" + escape = escape or self.escape + results = [] + here = 0 + + # XXX Note that this regular expression does not allow for the + # hyperlinking of arbitrary strings being used as method + # names. Only methods with names consisting of word characters + # and '.'s are hyperlinked. + pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|' + r'RFC[- ]?(\d+)|' + r'PEP[- ]?(\d+)|' + r'(self\.)?((?:\w|\.)+))\b') + while 1: + match = pattern.search(text, here) + if not match: break + start, end = match.span() + results.append(escape(text[here:start])) + + all, scheme, rfc, pep, selfdot, name = match.groups() + if scheme: + url = escape(all).replace('"', '"') + results.append('%s' % (url, url)) + elif rfc: + url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc) + results.append('%s' % (url, escape(all))) + elif pep: + url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep) + results.append('%s' % (url, escape(all))) + elif text[end:end+1] == '(': + results.append(self.namelink(name, methods, funcs, classes)) + elif selfdot: + results.append('self.%s' % name) + else: + results.append(self.namelink(name, classes)) + here = end + results.append(escape(text[here:])) + return ''.join(results) + + def docroutine(self, object, name, mod=None, + funcs={}, classes={}, methods={}, cl=None): + """Produce HTML documentation for a function or method object.""" + + anchor = (cl and cl.__name__ or '') + '-' + name + note = '' + + title = '%s' % ( + self.escape(anchor), self.escape(name)) + + if inspect.ismethod(object): + args, varargs, varkw, defaults = inspect.getargspec(object) + # exclude the argument bound to the instance, it will be + # confusing to the non-Python user + argspec = inspect.formatargspec ( + args[1:], + varargs, + varkw, + defaults, + formatvalue=self.formatvalue + ) + elif inspect.isfunction(object): + args, varargs, varkw, defaults = inspect.getargspec(object) + argspec = inspect.formatargspec( + args, varargs, varkw, defaults, formatvalue=self.formatvalue) + else: + argspec = '(...)' + + if isinstance(object, tuple): + argspec = object[0] or argspec + docstring = object[1] or "" + else: + docstring = pydoc.getdoc(object) + + decl = title + argspec + (note and self.grey( + '%s' % note)) + + doc = self.markup( + docstring, self.preformat, funcs, classes, methods) + doc = doc and '

%s
' % doc + return '
%s
%s
\n' % (decl, doc) + + def docserver(self, server_name, package_documentation, methods): + """Produce HTML documentation for an XML-RPC server.""" + + fdict = {} + for key, value in methods.items(): + fdict[key] = '#-' + key + fdict[value] = fdict[key] + + server_name = self.escape(server_name) + head = '%s' % server_name + result = self.heading(head, '#ffffff', '#7799ee') + + doc = self.markup(package_documentation, self.preformat, fdict) + doc = doc and '%s' % doc + result = result + '

%s

\n' % doc + + contents = [] + method_items = sorted(methods.items()) + for key, value in method_items: + contents.append(self.docroutine(value, key, funcs=fdict)) + result = result + self.bigsection( + 'Methods', '#ffffff', '#eeaa77', ''.join(contents)) + + return result + +class XMLRPCDocGenerator: + """Generates documentation for an XML-RPC server. + + This class is designed as mix-in and should not + be constructed directly. + """ + + def __init__(self): + # setup variables used for HTML documentation + self.server_name = 'XML-RPC Server Documentation' + self.server_documentation = \ + "This server exports the following methods through the XML-RPC "\ + "protocol." + self.server_title = 'XML-RPC Server Documentation' + + def set_server_title(self, server_title): + """Set the HTML title of the generated server documentation""" + + self.server_title = server_title + + def set_server_name(self, server_name): + """Set the name of the generated HTML server documentation""" + + self.server_name = server_name + + def set_server_documentation(self, server_documentation): + """Set the documentation string for the entire server.""" + + self.server_documentation = server_documentation + + def generate_html_documentation(self): + """generate_html_documentation() => html documentation for the server + + Generates HTML documentation for the server using introspection for + installed functions and instances that do not implement the + _dispatch method. Alternatively, instances can choose to implement + the _get_method_argstring(method_name) method to provide the + argument string used in the documentation and the + _methodHelp(method_name) method to provide the help text used + in the documentation.""" + + methods = {} + + for method_name in self.system_listMethods(): + if method_name in self.funcs: + method = self.funcs[method_name] + elif self.instance is not None: + method_info = [None, None] # argspec, documentation + if hasattr(self.instance, '_get_method_argstring'): + method_info[0] = self.instance._get_method_argstring(method_name) + if hasattr(self.instance, '_methodHelp'): + method_info[1] = self.instance._methodHelp(method_name) + + method_info = tuple(method_info) + if method_info != (None, None): + method = method_info + elif not hasattr(self.instance, '_dispatch'): + try: + method = resolve_dotted_attribute( + self.instance, + method_name + ) + except AttributeError: + method = method_info + else: + method = method_info + else: + assert 0, "Could not find method in self.functions and no "\ + "instance installed" + + methods[method_name] = method + + documenter = ServerHTMLDoc() + documentation = documenter.docserver( + self.server_name, + self.server_documentation, + methods + ) + + return documenter.page(self.server_title, documentation) + +class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): + """XML-RPC and documentation request handler class. + + Handles all HTTP POST requests and attempts to decode them as + XML-RPC requests. + + Handles all HTTP GET requests and interprets them as requests + for documentation. + """ + + def do_GET(self): + """Handles the HTTP GET request. + + Interpret all HTTP GET requests as requests for server + documentation. + """ + # Check that the path is legal + if not self.is_rpc_path_valid(): + self.report_404() + return + + response = self.server.generate_html_documentation() + self.send_response(200) + self.send_header("Content-type", "text/html") + self.send_header("Content-length", str(len(response))) + self.end_headers() + self.wfile.write(response.encode()) + + # shut down the connection + self.wfile.flush() + self.connection.shutdown(1) + +class DocXMLRPCServer( SimpleXMLRPCServer, + XMLRPCDocGenerator): + """XML-RPC and HTML documentation server. + + Adds the ability to serve server documentation to the capabilities + of SimpleXMLRPCServer. + """ + + def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler, + logRequests=1, allow_none=False, encoding=None, + bind_and_activate=True): + SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests, + allow_none, encoding, bind_and_activate) + XMLRPCDocGenerator.__init__(self) + +class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler, + XMLRPCDocGenerator): + """Handler for XML-RPC data and documentation requests passed through + CGI""" + + def handle_get(self): + """Handles the HTTP GET request. + + Interpret all HTTP GET requests as requests for server + documentation. + """ + + response = self.generate_html_documentation() + + print('Content-Type: text/html') + print('Content-Length: %d' % len(response)) + print() + sys.stdout.write(response) + + def __init__(self): + CGIXMLRPCRequestHandler.__init__(self) + XMLRPCDocGenerator.__init__(self) + + +if __name__ == '__main__': + print('Running XML-RPC server on port 8000') + server = SimpleXMLRPCServer(("localhost", 8000)) + server.register_function(pow) + server.register_function(lambda x,y: x+y, 'add') + server.serve_forever() diff --git a/Lib/xmlrpclib.py b/Lib/xmlrpclib.py deleted file mode 100644 index 522df4d..0000000 --- a/Lib/xmlrpclib.py +++ /dev/null @@ -1,1429 +0,0 @@ -# -# XML-RPC CLIENT LIBRARY -# $Id$ -# -# an XML-RPC client interface for Python. -# -# the marshalling and response parser code can also be used to -# implement XML-RPC servers. -# -# Notes: -# this version is designed to work with Python 2.1 or newer. -# -# History: -# 1999-01-14 fl Created -# 1999-01-15 fl Changed dateTime to use localtime -# 1999-01-16 fl Added Binary/base64 element, default to RPC2 service -# 1999-01-19 fl Fixed array data element (from Skip Montanaro) -# 1999-01-21 fl Fixed dateTime constructor, etc. -# 1999-02-02 fl Added fault handling, handle empty sequences, etc. -# 1999-02-10 fl Fixed problem with empty responses (from Skip Montanaro) -# 1999-06-20 fl Speed improvements, pluggable parsers/transports (0.9.8) -# 2000-11-28 fl Changed boolean to check the truth value of its argument -# 2001-02-24 fl Added encoding/Unicode/SafeTransport patches -# 2001-02-26 fl Added compare support to wrappers (0.9.9/1.0b1) -# 2001-03-28 fl Make sure response tuple is a singleton -# 2001-03-29 fl Don't require empty params element (from Nicholas Riley) -# 2001-06-10 fl Folded in _xmlrpclib accelerator support (1.0b2) -# 2001-08-20 fl Base xmlrpclib.Error on built-in Exception (from Paul Prescod) -# 2001-09-03 fl Allow Transport subclass to override getparser -# 2001-09-10 fl Lazy import of urllib, cgi, xmllib (20x import speedup) -# 2001-10-01 fl Remove containers from memo cache when done with them -# 2001-10-01 fl Use faster escape method (80% dumps speedup) -# 2001-10-02 fl More dumps microtuning -# 2001-10-04 fl Make sure import expat gets a parser (from Guido van Rossum) -# 2001-10-10 sm Allow long ints to be passed as ints if they don't overflow -# 2001-10-17 sm Test for int and long overflow (allows use on 64-bit systems) -# 2001-11-12 fl Use repr() to marshal doubles (from Paul Felix) -# 2002-03-17 fl Avoid buffered read when possible (from James Rucker) -# 2002-04-07 fl Added pythondoc comments -# 2002-04-16 fl Added __str__ methods to datetime/binary wrappers -# 2002-05-15 fl Added error constants (from Andrew Kuchling) -# 2002-06-27 fl Merged with Python CVS version -# 2002-10-22 fl Added basic authentication (based on code from Phillip Eby) -# 2003-01-22 sm Add support for the bool type -# 2003-02-27 gvr Remove apply calls -# 2003-04-24 sm Use cStringIO if available -# 2003-04-25 ak Add support for nil -# 2003-06-15 gn Add support for time.struct_time -# 2003-07-12 gp Correct marshalling of Faults -# 2003-10-31 mvl Add multicall support -# 2004-08-20 mvl Bump minimum supported Python version to 2.1 -# -# Copyright (c) 1999-2002 by Secret Labs AB. -# Copyright (c) 1999-2002 by Fredrik Lundh. -# -# info@pythonware.com -# http://www.pythonware.com -# -# -------------------------------------------------------------------- -# The XML-RPC client interface is -# -# Copyright (c) 1999-2002 by Secret Labs AB -# Copyright (c) 1999-2002 by Fredrik Lundh -# -# By obtaining, using, and/or copying this software and/or its -# associated documentation, you agree that you have read, understood, -# and will comply with the following terms and conditions: -# -# Permission to use, copy, modify, and distribute this software and -# its associated documentation for any purpose and without fee is -# hereby granted, provided that the above copyright notice appears in -# all copies, and that both that copyright notice and this permission -# notice appear in supporting documentation, and that the name of -# Secret Labs AB or the author not be used in advertising or publicity -# pertaining to distribution of the software without specific, written -# prior permission. -# -# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD -# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- -# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR -# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY -# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS -# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE -# OF THIS SOFTWARE. -# -------------------------------------------------------------------- - -# -# things to look into some day: - -# TODO: sort out True/False/boolean issues for Python 2.3 - -""" -An XML-RPC client interface for Python. - -The marshalling and response parser code can also be used to -implement XML-RPC servers. - -Exported exceptions: - - Error Base class for client errors - ProtocolError Indicates an HTTP protocol error - ResponseError Indicates a broken response package - Fault Indicates an XML-RPC fault package - -Exported classes: - - ServerProxy Represents a logical connection to an XML-RPC server - - MultiCall Executor of boxcared xmlrpc requests - Boolean boolean wrapper to generate a "boolean" XML-RPC value - DateTime dateTime wrapper for an ISO 8601 string or time tuple or - localtime integer value to generate a "dateTime.iso8601" - XML-RPC value - Binary binary data wrapper - - SlowParser Slow but safe standard parser (based on xmllib) - Marshaller Generate an XML-RPC params chunk from a Python data structure - Unmarshaller Unmarshal an XML-RPC response from incoming XML event message - Transport Handles an HTTP transaction to an XML-RPC server - SafeTransport Handles an HTTPS transaction to an XML-RPC server - -Exported constants: - - True - False - -Exported functions: - - boolean Convert any Python value to an XML-RPC boolean - getparser Create instance of the fastest available parser & attach - to an unmarshalling object - dumps Convert an argument tuple or a Fault instance to an XML-RPC - request (or response, if the methodresponse option is used). - loads Convert an XML-RPC packet to unmarshalled data plus a method - name (None if not present). -""" - -import re, time, operator -import httplib - -# -------------------------------------------------------------------- -# Internal stuff - -try: - import datetime -except ImportError: - datetime = None - -try: - _bool_is_builtin = False.__class__.__name__ == "bool" -except NameError: - _bool_is_builtin = 0 - -def _decode(data, encoding, is8bit=re.compile("[\x80-\xff]").search): - # decode non-ascii string (if possible) - if encoding and is8bit(data): - data = str(data, encoding) - return data - -def escape(s): - s = s.replace("&", "&") - s = s.replace("<", "<") - return s.replace(">", ">",) - -def _stringify(string): - # convert to 7-bit ascii if possible - try: - return string.decode("ascii") - except (UnicodeError, TypeError, AttributeError): - return string - -__version__ = "1.0.1" - -# xmlrpc integer limits -MAXINT = 2**31-1 -MININT = -2**31 - -# -------------------------------------------------------------------- -# Error constants (from Dan Libby's specification at -# http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php) - -# Ranges of errors -PARSE_ERROR = -32700 -SERVER_ERROR = -32600 -APPLICATION_ERROR = -32500 -SYSTEM_ERROR = -32400 -TRANSPORT_ERROR = -32300 - -# Specific errors -NOT_WELLFORMED_ERROR = -32700 -UNSUPPORTED_ENCODING = -32701 -INVALID_ENCODING_CHAR = -32702 -INVALID_XMLRPC = -32600 -METHOD_NOT_FOUND = -32601 -INVALID_METHOD_PARAMS = -32602 -INTERNAL_ERROR = -32603 - -# -------------------------------------------------------------------- -# Exceptions - -## -# Base class for all kinds of client-side errors. - -class Error(Exception): - """Base class for client errors.""" - def __str__(self): - return repr(self) - -## -# Indicates an HTTP-level protocol error. This is raised by the HTTP -# transport layer, if the server returns an error code other than 200 -# (OK). -# -# @param url The target URL. -# @param errcode The HTTP error code. -# @param errmsg The HTTP error message. -# @param headers The HTTP header dictionary. - -class ProtocolError(Error): - """Indicates an HTTP protocol error.""" - def __init__(self, url, errcode, errmsg, headers): - Error.__init__(self) - self.url = url - self.errcode = errcode - self.errmsg = errmsg - self.headers = headers - def __repr__(self): - return ( - "" % - (self.url, self.errcode, self.errmsg) - ) - -## -# Indicates a broken XML-RPC response package. This exception is -# raised by the unmarshalling layer, if the XML-RPC response is -# malformed. - -class ResponseError(Error): - """Indicates a broken response package.""" - pass - -## -# Indicates an XML-RPC fault response package. This exception is -# raised by the unmarshalling layer, if the XML-RPC response contains -# a fault string. This exception can also used as a class, to -# generate a fault XML-RPC message. -# -# @param faultCode The XML-RPC fault code. -# @param faultString The XML-RPC fault string. - -class Fault(Error): - """Indicates an XML-RPC fault package.""" - def __init__(self, faultCode, faultString, **extra): - Error.__init__(self) - self.faultCode = faultCode - self.faultString = faultString - def __repr__(self): - return ( - "" % - (self.faultCode, repr(self.faultString)) - ) - -# -------------------------------------------------------------------- -# Special values - -## -# Wrapper for XML-RPC boolean values. Use the xmlrpclib.True and -# xmlrpclib.False constants, or the xmlrpclib.boolean() function, to -# generate boolean XML-RPC values. -# -# @param value A boolean value. Any true value is interpreted as True, -# all other values are interpreted as False. - -boolean = Boolean = bool - -## -# Wrapper for XML-RPC DateTime values. This converts a time value to -# the format used by XML-RPC. -#

-# The value can be given as a string in the format -# "yyyymmddThh:mm:ss", as a 9-item time tuple (as returned by -# time.localtime()), or an integer value (as returned by time.time()). -# The wrapper uses time.localtime() to convert an integer to a time -# tuple. -# -# @param value The time, given as an ISO 8601 string, a time -# tuple, or a integer time value. - -def _strftime(value): - if datetime: - if isinstance(value, datetime.datetime): - return "%04d%02d%02dT%02d:%02d:%02d" % ( - value.year, value.month, value.day, - value.hour, value.minute, value.second) - - if not isinstance(value, (tuple, time.struct_time)): - if value == 0: - value = time.time() - value = time.localtime(value) - - return "%04d%02d%02dT%02d:%02d:%02d" % value[:6] - -class DateTime: - """DateTime wrapper for an ISO 8601 string or time tuple or - localtime integer value to generate 'dateTime.iso8601' XML-RPC - value. - """ - - def __init__(self, value=0): - if isinstance(value, str): - self.value = value - else: - self.value = _strftime(value) - - def make_comparable(self, other): - if isinstance(other, DateTime): - s = self.value - o = other.value - elif datetime and isinstance(other, datetime.datetime): - s = self.value - o = other.strftime("%Y%m%dT%H:%M:%S") - elif isinstance(other, (str, unicode)): - s = self.value - o = other - elif hasattr(other, "timetuple"): - s = self.timetuple() - o = other.timetuple() - else: - otype = (hasattr(other, "__class__") - and other.__class__.__name__ - or type(other)) - raise TypeError("Can't compare %s and %s" % - (self.__class__.__name__, otype)) - return s, o - - def __lt__(self, other): - s, o = self.make_comparable(other) - return s < o - - def __le__(self, other): - s, o = self.make_comparable(other) - return s <= o - - def __gt__(self, other): - s, o = self.make_comparable(other) - return s > o - - def __ge__(self, other): - s, o = self.make_comparable(other) - return s >= o - - def __eq__(self, other): - s, o = self.make_comparable(other) - return s == o - - def __ne__(self, other): - s, o = self.make_comparable(other) - return s != o - - def timetuple(self): - return time.strptime(self.value, "%Y%m%dT%H:%M:%S") - - def __cmp__(self, other): - s, o = self.make_comparable(other) - return cmp(s, o) - - ## - # Get date/time value. - # - # @return Date/time value, as an ISO 8601 string. - - def __str__(self): - return self.value - - def __repr__(self): - return "" % (repr(self.value), id(self)) - - def decode(self, data): - self.value = str(data).strip() - - def encode(self, out): - out.write("") - out.write(self.value) - out.write("\n") - -def _datetime(data): - # decode xml element contents into a DateTime structure. - value = DateTime() - value.decode(data) - return value - -def _datetime_type(data): - t = time.strptime(data, "%Y%m%dT%H:%M:%S") - return datetime.datetime(*tuple(t)[:6]) - -## -# Wrapper for binary data. This can be used to transport any kind -# of binary data over XML-RPC, using BASE64 encoding. -# -# @param data An 8-bit string containing arbitrary data. - -import base64 -import io - -class Binary: - """Wrapper for binary data.""" - - def __init__(self, data=None): - if data is None: - data = b"" - else: - if not isinstance(data, bytes): - raise TypeError("expected bytes, not %s" % - data.__class__.__name__) - data = bytes(data) # Make a copy of the bytes! - self.data = data - - ## - # Get buffer contents. - # - # @return Buffer contents, as an 8-bit string. - - def __str__(self): - return str(self.data, "latin-1") # XXX encoding?! - - def __eq__(self, other): - if isinstance(other, Binary): - other = other.data - return self.data == other - - def __ne__(self, other): - if isinstance(other, Binary): - other = other.data - return self.data != other - - def decode(self, data): - self.data = base64.decodestring(data) - - def encode(self, out): - out.write("\n") - encoded = base64.encodestring(self.data) - out.write(encoded.decode('ascii')) - out.write('\n') - out.write("\n") - -def _binary(data): - # decode xml element contents into a Binary structure - value = Binary() - value.decode(data) - return value - -WRAPPERS = (DateTime, Binary) -if not _bool_is_builtin: - WRAPPERS = WRAPPERS + (Boolean,) - -# -------------------------------------------------------------------- -# XML parsers - -try: - # optional xmlrpclib accelerator - import _xmlrpclib - FastParser = _xmlrpclib.Parser - FastUnmarshaller = _xmlrpclib.Unmarshaller -except (AttributeError, ImportError): - FastParser = FastUnmarshaller = None - -try: - import _xmlrpclib - FastMarshaller = _xmlrpclib.Marshaller -except (AttributeError, ImportError): - FastMarshaller = None - -# -# the SGMLOP parser is about 15x faster than Python's builtin -# XML parser. SGMLOP sources can be downloaded from: -# -# http://www.pythonware.com/products/xml/sgmlop.htm -# - -try: - import sgmlop - if not hasattr(sgmlop, "XMLParser"): - raise ImportError -except ImportError: - SgmlopParser = None # sgmlop accelerator not available -else: - class SgmlopParser: - def __init__(self, target): - - # setup callbacks - self.finish_starttag = target.start - self.finish_endtag = target.end - self.handle_data = target.data - self.handle_xml = target.xml - - # activate parser - self.parser = sgmlop.XMLParser() - self.parser.register(self) - self.feed = self.parser.feed - self.entity = { - "amp": "&", "gt": ">", "lt": "<", - "apos": "'", "quot": '"' - } - - def close(self): - try: - self.parser.close() - finally: - self.parser = self.feed = None # nuke circular reference - - def handle_proc(self, tag, attr): - m = re.search("encoding\s*=\s*['\"]([^\"']+)[\"']", attr) - if m: - self.handle_xml(m.group(1), 1) - - def handle_entityref(self, entity): - # entity - try: - self.handle_data(self.entity[entity]) - except KeyError: - self.handle_data("&%s;" % entity) - -try: - from xml.parsers import expat - if not hasattr(expat, "ParserCreate"): - raise ImportError -except ImportError: - ExpatParser = None # expat not available -else: - class ExpatParser: - # fast expat parser for Python 2.0 and later. this is about - # 50% slower than sgmlop, on roundtrip testing - def __init__(self, target): - self._parser = parser = expat.ParserCreate(None, None) - self._target = target - parser.StartElementHandler = target.start - parser.EndElementHandler = target.end - parser.CharacterDataHandler = target.data - encoding = None - target.xml(encoding, None) - - def feed(self, data): - self._parser.Parse(data, 0) - - def close(self): - self._parser.Parse("", 1) # end of data - del self._target, self._parser # get rid of circular references - -# -------------------------------------------------------------------- -# XML-RPC marshalling and unmarshalling code - -## -# XML-RPC marshaller. -# -# @param encoding Default encoding for 8-bit strings. The default -# value is None (interpreted as UTF-8). -# @see dumps - -class Marshaller: - """Generate an XML-RPC params chunk from a Python data structure. - - Create a Marshaller instance for each set of parameters, and use - the "dumps" method to convert your data (represented as a tuple) - to an XML-RPC params chunk. To write a fault response, pass a - Fault instance instead. You may prefer to use the "dumps" module - function for this purpose. - """ - - # by the way, if you don't understand what's going on in here, - # that's perfectly ok. - - def __init__(self, encoding=None, allow_none=0): - self.memo = {} - self.data = None - self.encoding = encoding - self.allow_none = allow_none - - dispatch = {} - - def dumps(self, values): - out = [] - write = out.append - dump = self.__dump - if isinstance(values, Fault): - # fault instance - write("\n") - dump({'faultCode': values.faultCode, - 'faultString': values.faultString}, - write) - write("\n") - else: - # parameter block - # FIXME: the xml-rpc specification allows us to leave out - # the entire block if there are no parameters. - # however, changing this may break older code (including - # old versions of xmlrpclib.py), so this is better left as - # is for now. See @XMLRPC3 for more information. /F - write("\n") - for v in values: - write("\n") - dump(v, write) - write("\n") - write("\n") - result = "".join(out) - return result - - def __dump(self, value, write): - try: - f = self.dispatch[type(value)] - except KeyError: - # check if this object can be marshalled as a structure - try: - value.__dict__ - except: - raise TypeError("cannot marshal %s objects" % type(value)) - # check if this class is a sub-class of a basic type, - # because we don't know how to marshal these types - # (e.g. a string sub-class) - for type_ in type(value).__mro__: - if type_ in self.dispatch.keys(): - raise TypeError("cannot marshal %s objects" % type(value)) - # XXX(twouters): using "_arbitrary_instance" as key as a quick-fix - # for the p3yk merge, this should probably be fixed more neatly. - f = self.dispatch["_arbitrary_instance"] - f(self, value, write) - - def dump_nil (self, value, write): - if not self.allow_none: - raise TypeError("cannot marshal None unless allow_none is enabled") - write("") - dispatch[type(None)] = dump_nil - - def dump_int(self, value, write): - # in case ints are > 32 bits - if value > MAXINT or value < MININT: - raise OverflowError("int exceeds XML-RPC limits") - write("") - write(str(value)) - write("\n") - #dispatch[int] = dump_int - - if _bool_is_builtin: - def dump_bool(self, value, write): - write("") - write(value and "1" or "0") - write("\n") - dispatch[bool] = dump_bool - - def dump_long(self, value, write): - if value > MAXINT or value < MININT: - raise OverflowError("long int exceeds XML-RPC limits") - write("") - write(str(int(value))) - write("\n") - dispatch[int] = dump_long - - def dump_double(self, value, write): - write("") - write(repr(value)) - write("\n") - dispatch[float] = dump_double - - def dump_string(self, value, write, escape=escape): - write("") - write(escape(value)) - write("\n") - dispatch[bytes] = dump_string - - def dump_unicode(self, value, write, escape=escape): - write("") - write(escape(value)) - write("\n") - dispatch[str] = dump_unicode - - def dump_array(self, value, write): - i = id(value) - if i in self.memo: - raise TypeError("cannot marshal recursive sequences") - self.memo[i] = None - dump = self.__dump - write("\n") - for v in value: - dump(v, write) - write("\n") - del self.memo[i] - dispatch[tuple] = dump_array - dispatch[list] = dump_array - - def dump_struct(self, value, write, escape=escape): - i = id(value) - if i in self.memo: - raise TypeError("cannot marshal recursive dictionaries") - self.memo[i] = None - dump = self.__dump - write("\n") - for k, v in value.items(): - write("\n") - if not isinstance(k, str): - raise TypeError("dictionary key must be string") - write("%s\n" % escape(k)) - dump(v, write) - write("\n") - write("\n") - del self.memo[i] - dispatch[dict] = dump_struct - - if datetime: - def dump_datetime(self, value, write): - write("") - write(_strftime(value)) - write("\n") - dispatch[datetime.datetime] = dump_datetime - - def dump_instance(self, value, write): - # check for special wrappers - if value.__class__ in WRAPPERS: - self.write = write - value.encode(self) - del self.write - else: - # store instance attributes as a struct (really?) - self.dump_struct(value.__dict__, write) - dispatch[DateTime] = dump_instance - dispatch[Binary] = dump_instance - # XXX(twouters): using "_arbitrary_instance" as key as a quick-fix - # for the p3yk merge, this should probably be fixed more neatly. - dispatch["_arbitrary_instance"] = dump_instance - -## -# XML-RPC unmarshaller. -# -# @see loads - -class Unmarshaller: - """Unmarshal an XML-RPC response, based on incoming XML event - messages (start, data, end). Call close() to get the resulting - data structure. - - Note that this reader is fairly tolerant, and gladly accepts bogus - XML-RPC data without complaining (but not bogus XML). - """ - - # and again, if you don't understand what's going on in here, - # that's perfectly ok. - - def __init__(self, use_datetime=0): - self._type = None - self._stack = [] - self._marks = [] - self._data = [] - self._methodname = None - self._encoding = "utf-8" - self.append = self._stack.append - self._use_datetime = use_datetime - if use_datetime and not datetime: - raise ValueError("the datetime module is not available") - - def close(self): - # return response tuple and target method - if self._type is None or self._marks: - raise ResponseError() - if self._type == "fault": - raise Fault(**self._stack[0]) - return tuple(self._stack) - - def getmethodname(self): - return self._methodname - - # - # event handlers - - def xml(self, encoding, standalone): - self._encoding = encoding - # FIXME: assert standalone == 1 ??? - - def start(self, tag, attrs): - # prepare to handle this element - if tag == "array" or tag == "struct": - self._marks.append(len(self._stack)) - self._data = [] - self._value = (tag == "value") - - def data(self, text): - self._data.append(text) - - def end(self, tag): - # call the appropriate end tag handler - try: - f = self.dispatch[tag] - except KeyError: - pass # unknown tag ? - else: - return f(self, "".join(self._data)) - - # - # accelerator support - - def end_dispatch(self, tag, data): - # dispatch data - try: - f = self.dispatch[tag] - except KeyError: - pass # unknown tag ? - else: - return f(self, data) - - # - # element decoders - - dispatch = {} - - def end_nil (self, data): - self.append(None) - self._value = 0 - dispatch["nil"] = end_nil - - def end_boolean(self, data): - if data == "0": - self.append(False) - elif data == "1": - self.append(True) - else: - raise TypeError("bad boolean value") - self._value = 0 - dispatch["boolean"] = end_boolean - - def end_int(self, data): - self.append(int(data)) - self._value = 0 - dispatch["i4"] = end_int - dispatch["int"] = end_int - - def end_double(self, data): - self.append(float(data)) - self._value = 0 - dispatch["double"] = end_double - - def end_string(self, data): - if self._encoding: - data = _decode(data, self._encoding) - self.append(_stringify(data)) - self._value = 0 - dispatch["string"] = end_string - dispatch["name"] = end_string # struct keys are always strings - - def end_array(self, data): - mark = self._marks.pop() - # map arrays to Python lists - self._stack[mark:] = [self._stack[mark:]] - self._value = 0 - dispatch["array"] = end_array - - def end_struct(self, data): - mark = self._marks.pop() - # map structs to Python dictionaries - dict = {} - items = self._stack[mark:] - for i in range(0, len(items), 2): - dict[_stringify(items[i])] = items[i+1] - self._stack[mark:] = [dict] - self._value = 0 - dispatch["struct"] = end_struct - - def end_base64(self, data): - value = Binary() - value.decode(data.encode("ascii")) - self.append(value) - self._value = 0 - dispatch["base64"] = end_base64 - - def end_dateTime(self, data): - value = DateTime() - value.decode(data) - if self._use_datetime: - value = _datetime_type(data) - self.append(value) - dispatch["dateTime.iso8601"] = end_dateTime - - def end_value(self, data): - # if we stumble upon a value element with no internal - # elements, treat it as a string element - if self._value: - self.end_string(data) - dispatch["value"] = end_value - - def end_params(self, data): - self._type = "params" - dispatch["params"] = end_params - - def end_fault(self, data): - self._type = "fault" - dispatch["fault"] = end_fault - - def end_methodName(self, data): - if self._encoding: - data = _decode(data, self._encoding) - self._methodname = data - self._type = "methodName" # no params - dispatch["methodName"] = end_methodName - -## Multicall support -# - -class _MultiCallMethod: - # some lesser magic to store calls made to a MultiCall object - # for batch execution - def __init__(self, call_list, name): - self.__call_list = call_list - self.__name = name - def __getattr__(self, name): - return _MultiCallMethod(self.__call_list, "%s.%s" % (self.__name, name)) - def __call__(self, *args): - self.__call_list.append((self.__name, args)) - -class MultiCallIterator: - """Iterates over the results of a multicall. Exceptions are - thrown in response to xmlrpc faults.""" - - def __init__(self, results): - self.results = results - - def __getitem__(self, i): - item = self.results[i] - if type(item) == type({}): - raise Fault(item['faultCode'], item['faultString']) - elif type(item) == type([]): - return item[0] - else: - raise ValueError("unexpected type in multicall result") - -class MultiCall: - """server -> a object used to boxcar method calls - - server should be a ServerProxy object. - - Methods can be added to the MultiCall using normal - method call syntax e.g.: - - multicall = MultiCall(server_proxy) - multicall.add(2,3) - multicall.get_address("Guido") - - To execute the multicall, call the MultiCall object e.g.: - - add_result, address = multicall() - """ - - def __init__(self, server): - self.__server = server - self.__call_list = [] - - def __repr__(self): - return "" % id(self) - - __str__ = __repr__ - - def __getattr__(self, name): - return _MultiCallMethod(self.__call_list, name) - - def __call__(self): - marshalled_list = [] - for name, args in self.__call_list: - marshalled_list.append({'methodName' : name, 'params' : args}) - - return MultiCallIterator(self.__server.system.multicall(marshalled_list)) - -# -------------------------------------------------------------------- -# convenience functions - -## -# Create a parser object, and connect it to an unmarshalling instance. -# This function picks the fastest available XML parser. -# -# return A (parser, unmarshaller) tuple. - -def getparser(use_datetime=0): - """getparser() -> parser, unmarshaller - - Create an instance of the fastest available parser, and attach it - to an unmarshalling object. Return both objects. - """ - if use_datetime and not datetime: - raise ValueError("the datetime module is not available") - if FastParser and FastUnmarshaller: - if use_datetime: - mkdatetime = _datetime_type - else: - mkdatetime = _datetime - target = FastUnmarshaller(True, False, _binary, mkdatetime, Fault) - parser = FastParser(target) - else: - target = Unmarshaller(use_datetime=use_datetime) - if FastParser: - parser = FastParser(target) - elif SgmlopParser: - parser = SgmlopParser(target) - elif ExpatParser: - parser = ExpatParser(target) - else: - parser = SlowParser(target) - return parser, target - -## -# Convert a Python tuple or a Fault instance to an XML-RPC packet. -# -# @def dumps(params, **options) -# @param params A tuple or Fault instance. -# @keyparam methodname If given, create a methodCall request for -# this method name. -# @keyparam methodresponse If given, create a methodResponse packet. -# If used with a tuple, the tuple must be a singleton (that is, -# it must contain exactly one element). -# @keyparam encoding The packet encoding. -# @return A string containing marshalled data. - -def dumps(params, methodname=None, methodresponse=None, encoding=None, - allow_none=0): - """data [,options] -> marshalled data - - Convert an argument tuple or a Fault instance to an XML-RPC - request (or response, if the methodresponse option is used). - - In addition to the data object, the following options can be given - as keyword arguments: - - methodname: the method name for a methodCall packet - - methodresponse: true to create a methodResponse packet. - If this option is used with a tuple, the tuple must be - a singleton (i.e. it can contain only one element). - - encoding: the packet encoding (default is UTF-8) - - All 8-bit strings in the data structure are assumed to use the - packet encoding. Unicode strings are automatically converted, - where necessary. - """ - - assert isinstance(params, (tuple, Fault)), "argument must be tuple or Fault instance" - if isinstance(params, Fault): - methodresponse = 1 - elif methodresponse and isinstance(params, tuple): - assert len(params) == 1, "response tuple must be a singleton" - - if not encoding: - encoding = "utf-8" - - if FastMarshaller: - m = FastMarshaller(encoding) - else: - m = Marshaller(encoding, allow_none) - - data = m.dumps(params) - - if encoding != "utf-8": - xmlheader = "\n" % str(encoding) - else: - xmlheader = "\n" # utf-8 is default - - # standard XML-RPC wrappings - if methodname: - # a method call - if not isinstance(methodname, str): - methodname = methodname.encode(encoding) - data = ( - xmlheader, - "\n" - "", methodname, "\n", - data, - "\n" - ) - elif methodresponse: - # a method response, or a fault structure - data = ( - xmlheader, - "\n", - data, - "\n" - ) - else: - return data # return as is - return "".join(data) - -## -# Convert an XML-RPC packet to a Python object. If the XML-RPC packet -# represents a fault condition, this function raises a Fault exception. -# -# @param data An XML-RPC packet, given as an 8-bit string. -# @return A tuple containing the unpacked data, and the method name -# (None if not present). -# @see Fault - -def loads(data, use_datetime=0): - """data -> unmarshalled data, method name - - Convert an XML-RPC packet to unmarshalled data plus a method - name (None if not present). - - If the XML-RPC packet represents a fault condition, this function - raises a Fault exception. - """ - p, u = getparser(use_datetime=use_datetime) - p.feed(data) - p.close() - return u.close(), u.getmethodname() - - -# -------------------------------------------------------------------- -# request dispatcher - -class _Method: - # some magic to bind an XML-RPC method to an RPC server. - # supports "nested" methods (e.g. examples.getStateName) - def __init__(self, send, name): - self.__send = send - self.__name = name - def __getattr__(self, name): - return _Method(self.__send, "%s.%s" % (self.__name, name)) - def __call__(self, *args): - return self.__send(self.__name, args) - -## -# Standard transport class for XML-RPC over HTTP. -#

-# You can create custom transports by subclassing this method, and -# overriding selected methods. - -class Transport: - """Handles an HTTP transaction to an XML-RPC server.""" - - # client identifier (may be overridden) - user_agent = "xmlrpclib.py/%s (by www.pythonware.com)" % __version__ - - def __init__(self, use_datetime=0): - self._use_datetime = use_datetime - - ## - # Send a complete request, and parse the response. - # - # @param host Target host. - # @param handler Target PRC handler. - # @param request_body XML-RPC request body. - # @param verbose Debugging flag. - # @return Parsed response. - - def request(self, host, handler, request_body, verbose=0): - # issue XML-RPC request - - http_conn = self.send_request(host, handler, request_body, verbose) - resp = http_conn.getresponse() - - if resp.status != 200: - raise ProtocolError( - host + handler, - resp.status, resp.reason, - dict(resp.getheaders()) - ) - - self.verbose = verbose - - return self._parse_response(resp, None) - - ## - # Create parser. - # - # @return A 2-tuple containing a parser and a unmarshaller. - - def getparser(self): - # get parser and unmarshaller - return getparser(use_datetime=self._use_datetime) - - ## - # Get authorization info from host parameter - # Host may be a string, or a (host, x509-dict) tuple; if a string, - # it is checked for a "user:pw@host" format, and a "Basic - # Authentication" header is added if appropriate. - # - # @param host Host descriptor (URL or (URL, x509 info) tuple). - # @return A 3-tuple containing (actual host, extra headers, - # x509 info). The header and x509 fields may be None. - - def get_host_info(self, host): - - x509 = {} - if isinstance(host, tuple): - host, x509 = host - - import urllib - auth, host = urllib.splituser(host) - - if auth: - import base64 - auth = base64.encodestring(urllib.unquote(auth)) - auth = "".join(auth.split()) # get rid of whitespace - extra_headers = [ - ("Authorization", "Basic " + auth) - ] - else: - extra_headers = None - - return host, extra_headers, x509 - - ## - # Connect to server. - # - # @param host Target host. - # @return An HTTPConnection object - - def make_connection(self, host): - # create a HTTP connection object from a host descriptor - host, extra_headers, x509 = self.get_host_info(host) - - - ## - # Send HTTP request. - # - # @param host Host descriptor (URL or (URL, x509 info) tuple). - # @param handler Targer RPC handler (a path relative to host) - # @param request_body The XML-RPC request body - # @param debug Enable debugging if debug is true. - # @return An HTTPConnection. - - def send_request(self, host, handler, request_body, debug): - host, extra_headers, x509 = self.get_host_info(host) - connection = httplib.HTTPConnection(host) - if debug: - connection.set_debuglevel(1) - headers = {} - if extra_headers: - for key, val in extra_headers: - header[key] = val - headers["Content-Type"] = "text/xml" - headers["User-Agent"] = self.user_agent - connection.request("POST", handler, request_body, headers) - return connection - - ## - # Parse response. - # - # @param file Stream. - # @return Response tuple and target method. - - def parse_response(self, file): - # compatibility interface - return self._parse_response(file, None) - - ## - # Parse response (alternate interface). This is similar to the - # parse_response method, but also provides direct access to the - # underlying socket object (where available). - # - # @param file Stream. - # @param sock Socket handle (or None, if the socket object - # could not be accessed). - # @return Response tuple and target method. - - def _parse_response(self, file, sock): - # read response from input file/socket, and parse it - - p, u = self.getparser() - - while 1: - if sock: - response = sock.recv(1024) - else: - response = file.read(1024) - if not response: - break - if self.verbose: - print("body:", repr(response)) - p.feed(response) - - file.close() - p.close() - - return u.close() - -## -# Standard transport class for XML-RPC over HTTPS. - -class SafeTransport(Transport): - """Handles an HTTPS transaction to an XML-RPC server.""" - - # FIXME: mostly untested - - def send_request(self, host, handler, request_body, debug): - import socket - if not hasattr(socket, "ssl"): - raise NotImplementedError( - "your version of httplib doesn't support HTTPS") - - host, extra_headers, x509 = self.get_host_info(host) - connection = httplib.HTTPSConnection(host, None, **(x509 or {})) - if debug: - connection.set_debuglevel(1) - headers = {} - if extra_headers: - for key, val in extra_headers: - header[key] = val - headers["Content-Type"] = "text/xml" - headers["User-Agent"] = self.user_agent - connection.request("POST", handler, request_body, headers) - return connection - -## -# Standard server proxy. This class establishes a virtual connection -# to an XML-RPC server. -#

-# This class is available as ServerProxy and Server. New code should -# use ServerProxy, to avoid confusion. -# -# @def ServerProxy(uri, **options) -# @param uri The connection point on the server. -# @keyparam transport A transport factory, compatible with the -# standard transport class. -# @keyparam encoding The default encoding used for 8-bit strings -# (default is UTF-8). -# @keyparam verbose Use a true value to enable debugging output. -# (printed to standard output). -# @see Transport - -class ServerProxy: - """uri [,options] -> a logical connection to an XML-RPC server - - uri is the connection point on the server, given as - scheme://host/target. - - The standard implementation always supports the "http" scheme. If - SSL socket support is available (Python 2.0), it also supports - "https". - - If the target part and the slash preceding it are both omitted, - "/RPC2" is assumed. - - The following options can be given as keyword arguments: - - transport: a transport factory - encoding: the request encoding (default is UTF-8) - - All 8-bit strings passed to the server proxy are assumed to use - the given encoding. - """ - - def __init__(self, uri, transport=None, encoding=None, verbose=0, - allow_none=0, use_datetime=0): - # establish a "logical" server connection - - # get the url - import urllib - type, uri = urllib.splittype(uri) - if type not in ("http", "https"): - raise IOError("unsupported XML-RPC protocol") - self.__host, self.__handler = urllib.splithost(uri) - if not self.__handler: - self.__handler = "/RPC2" - - if transport is None: - if type == "https": - transport = SafeTransport(use_datetime=use_datetime) - else: - transport = Transport(use_datetime=use_datetime) - self.__transport = transport - - self.__encoding = encoding - self.__verbose = verbose - self.__allow_none = allow_none - - def __request(self, methodname, params): - # call a method on the remote server - - request = dumps(params, methodname, encoding=self.__encoding, - allow_none=self.__allow_none) - - response = self.__transport.request( - self.__host, - self.__handler, - request, - verbose=self.__verbose - ) - - if len(response) == 1: - response = response[0] - - return response - - def __repr__(self): - return ( - "" % - (self.__host, self.__handler) - ) - - __str__ = __repr__ - - def __getattr__(self, name): - # magic method dispatcher - return _Method(self.__request, name) - - # note: to call a remote object with an non-standard name, use - # result getattr(server, "strange-python-name")(args) - -# compatibility - -Server = ServerProxy - -# -------------------------------------------------------------------- -# test code - -if __name__ == "__main__": - - # simple test program (from the XML-RPC specification) - - # server = ServerProxy("http://localhost:8000") # local server - server = ServerProxy("http://time.xmlrpc.com/RPC2") - - try: - print(server.currentTime.getCurrentTime()) - except Error as v: - print("ERROR", v) - - # The server at xmlrpc.com doesn't seem to support multicall anymore. - multi = MultiCall(server) - multi.currentTime.getCurrentTime() - multi.currentTime.getCurrentTime() - try: - for response in multi(): - print(response) - except Error as v: - print("ERROR", v) diff --git a/Misc/NEWS b/Misc/NEWS index f8b7ee0..88bd889 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -56,6 +56,17 @@ Extension Modules Library ------- +- The ``xmlrpc`` package was created; it contains the old + ``xmlrpclib`` module as ``xmlrpc.client`` and the content of + the old ``SimpleXMLRPCServer`` and ``DocXMLRPCServer`` modules + as ``xmlrpc.server``. + +- The ``dbm`` package was created, containing the old modules + ``anydbm`` and ``whichdb`` in its ``__init__.py``, and having + ``dbm.gnu`` (was ``gdbm``), ``dbm.bsd`` (was ``dbhash``), + ``dbm.ndbm`` (was ``dbm``) and ``dbm.dumb`` (was ``dumbdbm``) + as submodules. + - The ``repr`` module has been renamed to ``reprlib``. - The ``statvfs`` module has been removed. diff --git a/Misc/cheatsheet b/Misc/cheatsheet index c959de5..ed7c98a 100644 --- a/Misc/cheatsheet +++ b/Misc/cheatsheet @@ -1952,7 +1952,8 @@ xdrlib Implements (a subset of) Sun XDR (eXternal Data xmllib A parser for XML, using the derived class as static DTD. xml.dom Classes for processing XML using the Document Object Model. xml.sax Classes for processing XML using the SAX API. -xmlrpclib Support for remote procedure calls using XML. +xmlrpc.client Support for remote procedure calls using XML. +xmlrpc.server Create XMLRPC servers. zipfile Read & write PK zipped files. -- cgit v0.12