"""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
It can also be used to handle XML-RPC requests in a CGI
environment using CGIXMLRPCRequestHandler.
A list of possible usage patterns follows:
server = SimpleXMLRPCServer(("localhost", 8000))
server.register_function(pow)
server.register_function(lambda x,y: x+y, 'add')
# make all of the string functions available through
# implement this method so that system.listMethods
# knows to advertise the strings methods
return list_public_methods(self) + \
['string.' + method for method in list_public_methods(self.string)]
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())
3. Install an instance with custom dispatch method:
# this method must be present for system.listMethods
def _methodHelp(self, method):
# this method must be present for system.methodHelp
return "pow(x, y[, z]) => number"
# By convention, return empty
# string if no help is available
def _dispatch(self, method, params):
return params[0] + params[1]
server = SimpleXMLRPCServer(("localhost", 8000))
server.register_introspection_functions()
server.register_instance(Math())
4. Subclass SimpleXMLRPCServer:
class MathServer(SimpleXMLRPCServer):
def _dispatch(self, method, params):
# We are forcing the 'export_' prefix on methods that are
# callable through XML-RPC to prevent potential security
func = getattr(self, 'export_' + method)
raise Exception('method "%s" is not supported' % method)
def export_add(self, x, y):
server = MathServer(("localhost", 8000))
server = CGIXMLRPCRequestHandler()
server.register_function(pow)
# Written by Brian Quinlan (brian@sweetapp.com).
# Based on code written by Fredrik Lundh.
from xmlrpclib
import Fault
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).
'attempt to access private attribute "%s"' % i
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
callable(getattr(obj
, member
))]
def remove_duplicates(lst
):
"""remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
Returns a copy of a list without duplicates. Every list
item must be hashable and the order of the items in the
resulting list is not defined.
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 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
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
.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
self
.funcs
[name
] = function
def register_introspection_functions(self
):
"""Registers the XML-RPC introspection methods in the system
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
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.
params
, method
= xmlrpclib
.loads(data
)
if dispatch_method
is not None:
response
= dispatch_method(method
, params
)
response
= self
._dispatch
(method
, params
)
# wrap response in a singleton tuple
response
= xmlrpclib
.dumps(response
, methodresponse
=1)
response
= xmlrpclib
.dumps(fault
)
# report exception back to server
response
= xmlrpclib
.dumps(
xmlrpclib
.Fault(1, "%s:%s" % (sys
.exc_type
, sys
.exc_value
))
def system_listMethods(self
):
"""system.listMethods() => ['add', 'subtract', 'multiple']
Returns a list of the methods supported by the server."""
methods
= self
.funcs
.keys()
if self
.instance
is not None:
# Instance can implement _listMethod to return a list of
if hasattr(self
.instance
, '_listMethods'):
methods
= remove_duplicates(
methods
+ self
.instance
._listMethods
()
# if the instance has a _dispatch method then we
# don't have enough information to provide a list
elif not hasattr(self
.instance
, '_dispatch'):
methods
= remove_duplicates(
methods
+ list_public_methods(self
.instance
)
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."""
if self
.funcs
.has_key(method_name
):
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'):
method
= resolve_dotted_attribute(
# Note that we aren't checking that the method actually
# be a callable object of some kind
return pydoc
.getdoc(method
)
def system_multicall(self
, call_list
):
"""system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
Allows the caller to package multiple XML-RPC calls into a single
See http://www.xmlrpc.com/discuss/msgReader$1208
method_name
= call
['methodName']
# 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
)])
{'faultCode' : fault
.faultCode
,
'faultString' : fault
.faultString
}
'faultString' : "%s:%s" % (sys
.exc_type
, sys
.exc_value
)}
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 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
# check to see if a matching function has been registered
func
= self
.funcs
[method
]
if self
.instance
is not None:
# check for a _dispatch method
if hasattr(self
.instance
, '_dispatch'):
return self
.instance
._dispatch
(method
, params
)
# call instance method directly
func
= resolve_dotted_attribute(
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
"""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.
data
= self
.rfile
.read(int(self
.headers
["content-length"]))
# 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: # This should only happen if the module is buggy
# internal error, report as HTTP server error
# got a valid XML RPC response
self
.send_header("Content-type", "text/xml")
self
.send_header("Content-length", str(len(response
)))
self
.wfile
.write(response
)
# shut down the connection
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
,
"""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.
def __init__(self
, addr
, requestHandler
=SimpleXMLRPCRequestHandler
,
self
.logRequests
= logRequests
SimpleXMLRPCDispatcher
.__init
__(self
)
SocketServer
.TCPServer
.__init
__(self
, addr
, requestHandler
)
class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher
):
"""Simple handler for XML-RPC data passed through CGI."""
SimpleXMLRPCDispatcher
.__init
__(self
)
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
)
sys
.stdout
.write(response
)
"""Handle a single HTTP GET request.
Default implementation indicates an error because
XML-RPC uses the POST method.
BaseHTTPServer
.BaseHTTPRequestHandler
.responses
[code
]
response
= BaseHTTPServer
.DEFAULT_ERROR_MESSAGE
% \
print 'Status: %d %s' % (code
, message
)
print 'Content-Type: text/html'
print 'Content-Length: %d' % len(response
)
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
if request_text
is None and \
os
.environ
.get('REQUEST_METHOD', None) == 'GET':
# POST data is normally available through stdin
request_text
= sys
.stdin
.read()
self
.handle_xmlrpc(request_text
)
if __name__
== '__main__':
server
= SimpleXMLRPCServer(("localhost", 8000))
server
.register_function(pow)
server
.register_function(lambda x
,y
: x
+y
, 'add')