| 1 | """Simple XML-RPC Server.
|
|---|
| 2 |
|
|---|
| 3 | This module can be used to create simple XML-RPC servers
|
|---|
| 4 | by creating a server and either installing functions, a
|
|---|
| 5 | class instance, or by extending the SimpleXMLRPCServer
|
|---|
| 6 | class.
|
|---|
| 7 |
|
|---|
| 8 | It can also be used to handle XML-RPC requests in a CGI
|
|---|
| 9 | environment using CGIXMLRPCRequestHandler.
|
|---|
| 10 |
|
|---|
| 11 | A list of possible usage patterns follows:
|
|---|
| 12 |
|
|---|
| 13 | 1. Install functions:
|
|---|
| 14 |
|
|---|
| 15 | server = SimpleXMLRPCServer(("localhost", 8000))
|
|---|
| 16 | server.register_function(pow)
|
|---|
| 17 | server.register_function(lambda x,y: x+y, 'add')
|
|---|
| 18 | server.serve_forever()
|
|---|
| 19 |
|
|---|
| 20 | 2. Install an instance:
|
|---|
| 21 |
|
|---|
| 22 | class MyFuncs:
|
|---|
| 23 | def __init__(self):
|
|---|
| 24 | # make all of the string functions available through
|
|---|
| 25 | # string.func_name
|
|---|
| 26 | import string
|
|---|
| 27 | self.string = string
|
|---|
| 28 | def _listMethods(self):
|
|---|
| 29 | # implement this method so that system.listMethods
|
|---|
| 30 | # knows to advertise the strings methods
|
|---|
| 31 | return list_public_methods(self) + \
|
|---|
| 32 | ['string.' + method for method in list_public_methods(self.string)]
|
|---|
| 33 | def pow(self, x, y): return pow(x, y)
|
|---|
| 34 | def add(self, x, y) : return x + y
|
|---|
| 35 |
|
|---|
| 36 | server = SimpleXMLRPCServer(("localhost", 8000))
|
|---|
| 37 | server.register_introspection_functions()
|
|---|
| 38 | server.register_instance(MyFuncs())
|
|---|
| 39 | server.serve_forever()
|
|---|
| 40 |
|
|---|
| 41 | 3. Install an instance with custom dispatch method:
|
|---|
| 42 |
|
|---|
| 43 | class Math:
|
|---|
| 44 | def _listMethods(self):
|
|---|
| 45 | # this method must be present for system.listMethods
|
|---|
| 46 | # to work
|
|---|
| 47 | return ['add', 'pow']
|
|---|
| 48 | def _methodHelp(self, method):
|
|---|
| 49 | # this method must be present for system.methodHelp
|
|---|
| 50 | # to work
|
|---|
| 51 | if method == 'add':
|
|---|
| 52 | return "add(2,3) => 5"
|
|---|
| 53 | elif method == 'pow':
|
|---|
| 54 | return "pow(x, y[, z]) => number"
|
|---|
| 55 | else:
|
|---|
| 56 | # By convention, return empty
|
|---|
| 57 | # string if no help is available
|
|---|
| 58 | return ""
|
|---|
| 59 | def _dispatch(self, method, params):
|
|---|
| 60 | if method == 'pow':
|
|---|
| 61 | return pow(*params)
|
|---|
| 62 | elif method == 'add':
|
|---|
| 63 | return params[0] + params[1]
|
|---|
| 64 | else:
|
|---|
| 65 | raise 'bad method'
|
|---|
| 66 |
|
|---|
| 67 | server = SimpleXMLRPCServer(("localhost", 8000))
|
|---|
| 68 | server.register_introspection_functions()
|
|---|
| 69 | server.register_instance(Math())
|
|---|
| 70 | server.serve_forever()
|
|---|
| 71 |
|
|---|
| 72 | 4. Subclass SimpleXMLRPCServer:
|
|---|
| 73 |
|
|---|
| 74 | class MathServer(SimpleXMLRPCServer):
|
|---|
| 75 | def _dispatch(self, method, params):
|
|---|
| 76 | try:
|
|---|
| 77 | # We are forcing the 'export_' prefix on methods that are
|
|---|
| 78 | # callable through XML-RPC to prevent potential security
|
|---|
| 79 | # problems
|
|---|
| 80 | func = getattr(self, 'export_' + method)
|
|---|
| 81 | except AttributeError:
|
|---|
| 82 | raise Exception('method "%s" is not supported' % method)
|
|---|
| 83 | else:
|
|---|
| 84 | return func(*params)
|
|---|
| 85 |
|
|---|
| 86 | def export_add(self, x, y):
|
|---|
| 87 | return x + y
|
|---|
| 88 |
|
|---|
| 89 | server = MathServer(("localhost", 8000))
|
|---|
| 90 | server.serve_forever()
|
|---|
| 91 |
|
|---|
| 92 | 5. CGI script:
|
|---|
| 93 |
|
|---|
| 94 | server = CGIXMLRPCRequestHandler()
|
|---|
| 95 | server.register_function(pow)
|
|---|
| 96 | server.handle_request()
|
|---|
| 97 | """
|
|---|
| 98 |
|
|---|
| 99 | # Written by Brian Quinlan ([email protected]).
|
|---|
| 100 | # Based on code written by Fredrik Lundh.
|
|---|
| 101 |
|
|---|
| 102 | import xmlrpclib
|
|---|
| 103 | from xmlrpclib import Fault
|
|---|
| 104 | import SocketServer
|
|---|
| 105 | import BaseHTTPServer
|
|---|
| 106 | import sys
|
|---|
| 107 | import os
|
|---|
| 108 | try:
|
|---|
| 109 | import fcntl
|
|---|
| 110 | except ImportError:
|
|---|
| 111 | fcntl = None
|
|---|
| 112 |
|
|---|
| 113 | def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
|
|---|
| 114 | """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
|
|---|
| 115 |
|
|---|
| 116 | Resolves a dotted attribute name to an object. Raises
|
|---|
| 117 | an AttributeError if any attribute in the chain starts with a '_'.
|
|---|
| 118 |
|
|---|
| 119 | If the optional allow_dotted_names argument is false, dots are not
|
|---|
| 120 | supported and this function operates similar to getattr(obj, attr).
|
|---|
| 121 | """
|
|---|
| 122 |
|
|---|
| 123 | if allow_dotted_names:
|
|---|
| 124 | attrs = attr.split('.')
|
|---|
| 125 | else:
|
|---|
| 126 | attrs = [attr]
|
|---|
| 127 |
|
|---|
| 128 | for i in attrs:
|
|---|
| 129 | if i.startswith('_'):
|
|---|
| 130 | raise AttributeError(
|
|---|
| 131 | 'attempt to access private attribute "%s"' % i
|
|---|
| 132 | )
|
|---|
| 133 | else:
|
|---|
| 134 | obj = getattr(obj,i)
|
|---|
| 135 | return obj
|
|---|
| 136 |
|
|---|
| 137 | def list_public_methods(obj):
|
|---|
| 138 | """Returns a list of attribute strings, found in the specified
|
|---|
| 139 | object, which represent callable attributes"""
|
|---|
| 140 |
|
|---|
| 141 | return [member for member in dir(obj)
|
|---|
| 142 | if not member.startswith('_') and
|
|---|
| 143 | callable(getattr(obj, member))]
|
|---|
| 144 |
|
|---|
| 145 | def remove_duplicates(lst):
|
|---|
| 146 | """remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
|
|---|
| 147 |
|
|---|
| 148 | Returns a copy of a list without duplicates. Every list
|
|---|
| 149 | item must be hashable and the order of the items in the
|
|---|
| 150 | resulting list is not defined.
|
|---|
| 151 | """
|
|---|
| 152 | u = {}
|
|---|
| 153 | for x in lst:
|
|---|
| 154 | u[x] = 1
|
|---|
| 155 |
|
|---|
| 156 | return u.keys()
|
|---|
| 157 |
|
|---|
| 158 | class SimpleXMLRPCDispatcher:
|
|---|
| 159 | """Mix-in class that dispatches XML-RPC requests.
|
|---|
| 160 |
|
|---|
| 161 | This class is used to register XML-RPC method handlers
|
|---|
| 162 | and then to dispatch them. There should never be any
|
|---|
| 163 | reason to instantiate this class directly.
|
|---|
| 164 | """
|
|---|
| 165 |
|
|---|
| 166 | def __init__(self, allow_none, encoding):
|
|---|
| 167 | self.funcs = {}
|
|---|
| 168 | self.instance = None
|
|---|
| 169 | self.allow_none = allow_none
|
|---|
| 170 | self.encoding = encoding
|
|---|
| 171 |
|
|---|
| 172 | def register_instance(self, instance, allow_dotted_names=False):
|
|---|
| 173 | """Registers an instance to respond to XML-RPC requests.
|
|---|
| 174 |
|
|---|
| 175 | Only one instance can be installed at a time.
|
|---|
| 176 |
|
|---|
| 177 | If the registered instance has a _dispatch method then that
|
|---|
| 178 | method will be called with the name of the XML-RPC method and
|
|---|
| 179 | its parameters as a tuple
|
|---|
| 180 | e.g. instance._dispatch('add',(2,3))
|
|---|
| 181 |
|
|---|
| 182 | If the registered instance does not have a _dispatch method
|
|---|
| 183 | then the instance will be searched to find a matching method
|
|---|
| 184 | and, if found, will be called. Methods beginning with an '_'
|
|---|
| 185 | are considered private and will not be called by
|
|---|
| 186 | SimpleXMLRPCServer.
|
|---|
| 187 |
|
|---|
| 188 | If a registered function matches a XML-RPC request, then it
|
|---|
| 189 | will be called instead of the registered instance.
|
|---|
| 190 |
|
|---|
| 191 | If the optional allow_dotted_names argument is true and the
|
|---|
| 192 | instance does not have a _dispatch method, method names
|
|---|
| 193 | containing dots are supported and resolved, as long as none of
|
|---|
| 194 | the name segments start with an '_'.
|
|---|
| 195 |
|
|---|
| 196 | *** SECURITY WARNING: ***
|
|---|
| 197 |
|
|---|
| 198 | Enabling the allow_dotted_names options allows intruders
|
|---|
| 199 | to access your module's global variables and may allow
|
|---|
| 200 | intruders to execute arbitrary code on your machine. Only
|
|---|
| 201 | use this option on a secure, closed network.
|
|---|
| 202 |
|
|---|
| 203 | """
|
|---|
| 204 |
|
|---|
| 205 | self.instance = instance
|
|---|
| 206 | self.allow_dotted_names = allow_dotted_names
|
|---|
| 207 |
|
|---|
| 208 | def register_function(self, function, name = None):
|
|---|
| 209 | """Registers a function to respond to XML-RPC requests.
|
|---|
| 210 |
|
|---|
| 211 | The optional name argument can be used to set a Unicode name
|
|---|
| 212 | for the function.
|
|---|
| 213 | """
|
|---|
| 214 |
|
|---|
| 215 | if name is None:
|
|---|
| 216 | name = function.__name__
|
|---|
| 217 | self.funcs[name] = function
|
|---|
| 218 |
|
|---|
| 219 | def register_introspection_functions(self):
|
|---|
| 220 | """Registers the XML-RPC introspection methods in the system
|
|---|
| 221 | namespace.
|
|---|
| 222 |
|
|---|
| 223 | see http://xmlrpc.usefulinc.com/doc/reserved.html
|
|---|
| 224 | """
|
|---|
| 225 |
|
|---|
| 226 | self.funcs.update({'system.listMethods' : self.system_listMethods,
|
|---|
| 227 | 'system.methodSignature' : self.system_methodSignature,
|
|---|
| 228 | 'system.methodHelp' : self.system_methodHelp})
|
|---|
| 229 |
|
|---|
| 230 | def register_multicall_functions(self):
|
|---|
| 231 | """Registers the XML-RPC multicall method in the system
|
|---|
| 232 | namespace.
|
|---|
| 233 |
|
|---|
| 234 | see http://www.xmlrpc.com/discuss/msgReader$1208"""
|
|---|
| 235 |
|
|---|
| 236 | self.funcs.update({'system.multicall' : self.system_multicall})
|
|---|
| 237 |
|
|---|
| 238 | def _marshaled_dispatch(self, data, dispatch_method = None):
|
|---|
| 239 | """Dispatches an XML-RPC method from marshalled (XML) data.
|
|---|
| 240 |
|
|---|
| 241 | XML-RPC methods are dispatched from the marshalled (XML) data
|
|---|
| 242 | using the _dispatch method and the result is returned as
|
|---|
| 243 | marshalled data. For backwards compatibility, a dispatch
|
|---|
| 244 | function can be provided as an argument (see comment in
|
|---|
| 245 | SimpleXMLRPCRequestHandler.do_POST) but overriding the
|
|---|
| 246 | existing method through subclassing is the prefered means
|
|---|
| 247 | of changing method dispatch behavior.
|
|---|
| 248 | """
|
|---|
| 249 |
|
|---|
| 250 | try:
|
|---|
| 251 | params, method = xmlrpclib.loads(data)
|
|---|
| 252 |
|
|---|
| 253 | # generate response
|
|---|
| 254 | if dispatch_method is not None:
|
|---|
| 255 | response = dispatch_method(method, params)
|
|---|
| 256 | else:
|
|---|
| 257 | response = self._dispatch(method, params)
|
|---|
| 258 | # wrap response in a singleton tuple
|
|---|
| 259 | response = (response,)
|
|---|
| 260 | response = xmlrpclib.dumps(response, methodresponse=1,
|
|---|
| 261 | allow_none=self.allow_none, encoding=self.encoding)
|
|---|
| 262 | except Fault, fault:
|
|---|
| 263 | response = xmlrpclib.dumps(fault, allow_none=self.allow_none,
|
|---|
| 264 | encoding=self.encoding)
|
|---|
| 265 | except:
|
|---|
| 266 | # report exception back to server
|
|---|
| 267 | response = xmlrpclib.dumps(
|
|---|
| 268 | xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value)),
|
|---|
| 269 | encoding=self.encoding, allow_none=self.allow_none,
|
|---|
| 270 | )
|
|---|
| 271 |
|
|---|
| 272 | return response
|
|---|
| 273 |
|
|---|
| 274 | def system_listMethods(self):
|
|---|
| 275 | """system.listMethods() => ['add', 'subtract', 'multiple']
|
|---|
| 276 |
|
|---|
| 277 | Returns a list of the methods supported by the server."""
|
|---|
| 278 |
|
|---|
| 279 | methods = self.funcs.keys()
|
|---|
| 280 | if self.instance is not None:
|
|---|
| 281 | # Instance can implement _listMethod to return a list of
|
|---|
| 282 | # methods
|
|---|
| 283 | if hasattr(self.instance, '_listMethods'):
|
|---|
| 284 | methods = remove_duplicates(
|
|---|
| 285 | methods + self.instance._listMethods()
|
|---|
| 286 | )
|
|---|
| 287 | # if the instance has a _dispatch method then we
|
|---|
| 288 | # don't have enough information to provide a list
|
|---|
| 289 | # of methods
|
|---|
| 290 | elif not hasattr(self.instance, '_dispatch'):
|
|---|
| 291 | methods = remove_duplicates(
|
|---|
| 292 | methods + list_public_methods(self.instance)
|
|---|
| 293 | )
|
|---|
| 294 | methods.sort()
|
|---|
| 295 | return methods
|
|---|
| 296 |
|
|---|
| 297 | def system_methodSignature(self, method_name):
|
|---|
| 298 | """system.methodSignature('add') => [double, int, int]
|
|---|
| 299 |
|
|---|
| 300 | Returns a list describing the signature of the method. In the
|
|---|
| 301 | above example, the add method takes two integers as arguments
|
|---|
| 302 | and returns a double result.
|
|---|
| 303 |
|
|---|
| 304 | This server does NOT support system.methodSignature."""
|
|---|
| 305 |
|
|---|
| 306 | # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
|
|---|
| 307 |
|
|---|
| 308 | return 'signatures not supported'
|
|---|
| 309 |
|
|---|
| 310 | def system_methodHelp(self, method_name):
|
|---|
| 311 | """system.methodHelp('add') => "Adds two integers together"
|
|---|
| 312 |
|
|---|
| 313 | Returns a string containing documentation for the specified method."""
|
|---|
| 314 |
|
|---|
| 315 | method = None
|
|---|
| 316 | if self.funcs.has_key(method_name):
|
|---|
| 317 | method = self.funcs[method_name]
|
|---|
| 318 | elif self.instance is not None:
|
|---|
| 319 | # Instance can implement _methodHelp to return help for a method
|
|---|
| 320 | if hasattr(self.instance, '_methodHelp'):
|
|---|
| 321 | return self.instance._methodHelp(method_name)
|
|---|
| 322 | # if the instance has a _dispatch method then we
|
|---|
| 323 | # don't have enough information to provide help
|
|---|
| 324 | elif not hasattr(self.instance, '_dispatch'):
|
|---|
| 325 | try:
|
|---|
| 326 | method = resolve_dotted_attribute(
|
|---|
| 327 | self.instance,
|
|---|
| 328 | method_name,
|
|---|
| 329 | self.allow_dotted_names
|
|---|
| 330 | )
|
|---|
| 331 | except AttributeError:
|
|---|
| 332 | pass
|
|---|
| 333 |
|
|---|
| 334 | # Note that we aren't checking that the method actually
|
|---|
| 335 | # be a callable object of some kind
|
|---|
| 336 | if method is None:
|
|---|
| 337 | return ""
|
|---|
| 338 | else:
|
|---|
| 339 | import pydoc
|
|---|
| 340 | return pydoc.getdoc(method)
|
|---|
| 341 |
|
|---|
| 342 | def system_multicall(self, call_list):
|
|---|
| 343 | """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
|
|---|
| 344 | [[4], ...]
|
|---|
| 345 |
|
|---|
| 346 | Allows the caller to package multiple XML-RPC calls into a single
|
|---|
| 347 | request.
|
|---|
| 348 |
|
|---|
| 349 | See http://www.xmlrpc.com/discuss/msgReader$1208
|
|---|
| 350 | """
|
|---|
| 351 |
|
|---|
| 352 | results = []
|
|---|
| 353 | for call in call_list:
|
|---|
| 354 | method_name = call['methodName']
|
|---|
| 355 | params = call['params']
|
|---|
| 356 |
|
|---|
| 357 | try:
|
|---|
| 358 | # XXX A marshalling error in any response will fail the entire
|
|---|
| 359 | # multicall. If someone cares they should fix this.
|
|---|
| 360 | results.append([self._dispatch(method_name, params)])
|
|---|
| 361 | except Fault, fault:
|
|---|
| 362 | results.append(
|
|---|
| 363 | {'faultCode' : fault.faultCode,
|
|---|
| 364 | 'faultString' : fault.faultString}
|
|---|
| 365 | )
|
|---|
| 366 | except:
|
|---|
| 367 | results.append(
|
|---|
| 368 | {'faultCode' : 1,
|
|---|
| 369 | 'faultString' : "%s:%s" % (sys.exc_type, sys.exc_value)}
|
|---|
| 370 | )
|
|---|
| 371 | return results
|
|---|
| 372 |
|
|---|
| 373 | def _dispatch(self, method, params):
|
|---|
| 374 | """Dispatches the XML-RPC method.
|
|---|
| 375 |
|
|---|
| 376 | XML-RPC calls are forwarded to a registered function that
|
|---|
| 377 | matches the called XML-RPC method name. If no such function
|
|---|
| 378 | exists then the call is forwarded to the registered instance,
|
|---|
| 379 | if available.
|
|---|
| 380 |
|
|---|
| 381 | If the registered instance has a _dispatch method then that
|
|---|
| 382 | method will be called with the name of the XML-RPC method and
|
|---|
| 383 | its parameters as a tuple
|
|---|
| 384 | e.g. instance._dispatch('add',(2,3))
|
|---|
| 385 |
|
|---|
| 386 | If the registered instance does not have a _dispatch method
|
|---|
| 387 | then the instance will be searched to find a matching method
|
|---|
| 388 | and, if found, will be called.
|
|---|
| 389 |
|
|---|
| 390 | Methods beginning with an '_' are considered private and will
|
|---|
| 391 | not be called.
|
|---|
| 392 | """
|
|---|
| 393 |
|
|---|
| 394 | func = None
|
|---|
| 395 | try:
|
|---|
| 396 | # check to see if a matching function has been registered
|
|---|
| 397 | func = self.funcs[method]
|
|---|
| 398 | except KeyError:
|
|---|
| 399 | if self.instance is not None:
|
|---|
| 400 | # check for a _dispatch method
|
|---|
| 401 | if hasattr(self.instance, '_dispatch'):
|
|---|
| 402 | return self.instance._dispatch(method, params)
|
|---|
| 403 | else:
|
|---|
| 404 | # call instance method directly
|
|---|
| 405 | try:
|
|---|
| 406 | func = resolve_dotted_attribute(
|
|---|
| 407 | self.instance,
|
|---|
| 408 | method,
|
|---|
| 409 | self.allow_dotted_names
|
|---|
| 410 | )
|
|---|
| 411 | except AttributeError:
|
|---|
| 412 | pass
|
|---|
| 413 |
|
|---|
| 414 | if func is not None:
|
|---|
| 415 | return func(*params)
|
|---|
| 416 | else:
|
|---|
| 417 | raise Exception('method "%s" is not supported' % method)
|
|---|
| 418 |
|
|---|
| 419 | class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|---|
| 420 | """Simple XML-RPC request handler class.
|
|---|
| 421 |
|
|---|
| 422 | Handles all HTTP POST requests and attempts to decode them as
|
|---|
| 423 | XML-RPC requests.
|
|---|
| 424 | """
|
|---|
| 425 |
|
|---|
| 426 | # Class attribute listing the accessible path components;
|
|---|
| 427 | # paths not on this list will result in a 404 error.
|
|---|
| 428 | rpc_paths = ('/', '/RPC2')
|
|---|
| 429 |
|
|---|
| 430 | def is_rpc_path_valid(self):
|
|---|
| 431 | if self.rpc_paths:
|
|---|
| 432 | return self.path in self.rpc_paths
|
|---|
| 433 | else:
|
|---|
| 434 | # If .rpc_paths is empty, just assume all paths are legal
|
|---|
| 435 | return True
|
|---|
| 436 |
|
|---|
| 437 | def do_POST(self):
|
|---|
| 438 | """Handles the HTTP POST request.
|
|---|
| 439 |
|
|---|
| 440 | Attempts to interpret all HTTP POST requests as XML-RPC calls,
|
|---|
| 441 | which are forwarded to the server's _dispatch method for handling.
|
|---|
| 442 | """
|
|---|
| 443 |
|
|---|
| 444 | # Check that the path is legal
|
|---|
| 445 | if not self.is_rpc_path_valid():
|
|---|
| 446 | self.report_404()
|
|---|
| 447 | return
|
|---|
| 448 |
|
|---|
| 449 | try:
|
|---|
| 450 | # Get arguments by reading body of request.
|
|---|
| 451 | # We read this in chunks to avoid straining
|
|---|
| 452 | # socket.read(); around the 10 or 15Mb mark, some platforms
|
|---|
| 453 | # begin to have problems (bug #792570).
|
|---|
| 454 | max_chunk_size = 10*1024*1024
|
|---|
| 455 | size_remaining = int(self.headers["content-length"])
|
|---|
| 456 | L = []
|
|---|
| 457 | while size_remaining:
|
|---|
| 458 | chunk_size = min(size_remaining, max_chunk_size)
|
|---|
| 459 | L.append(self.rfile.read(chunk_size))
|
|---|
| 460 | size_remaining -= len(L[-1])
|
|---|
| 461 | data = ''.join(L)
|
|---|
| 462 |
|
|---|
| 463 | # In previous versions of SimpleXMLRPCServer, _dispatch
|
|---|
| 464 | # could be overridden in this class, instead of in
|
|---|
| 465 | # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
|
|---|
| 466 | # check to see if a subclass implements _dispatch and dispatch
|
|---|
| 467 | # using that method if present.
|
|---|
| 468 | response = self.server._marshaled_dispatch(
|
|---|
| 469 | data, getattr(self, '_dispatch', None)
|
|---|
| 470 | )
|
|---|
| 471 | except: # This should only happen if the module is buggy
|
|---|
| 472 | # internal error, report as HTTP server error
|
|---|
| 473 | self.send_response(500)
|
|---|
| 474 | self.end_headers()
|
|---|
| 475 | else:
|
|---|
| 476 | # got a valid XML RPC response
|
|---|
| 477 | self.send_response(200)
|
|---|
| 478 | self.send_header("Content-type", "text/xml")
|
|---|
| 479 | self.send_header("Content-length", str(len(response)))
|
|---|
| 480 | self.end_headers()
|
|---|
| 481 | self.wfile.write(response)
|
|---|
| 482 |
|
|---|
| 483 | # shut down the connection
|
|---|
| 484 | self.wfile.flush()
|
|---|
| 485 | self.connection.shutdown(1)
|
|---|
| 486 |
|
|---|
| 487 | def report_404 (self):
|
|---|
| 488 | # Report a 404 error
|
|---|
| 489 | self.send_response(404)
|
|---|
| 490 | response = 'No such page'
|
|---|
| 491 | self.send_header("Content-type", "text/plain")
|
|---|
| 492 | self.send_header("Content-length", str(len(response)))
|
|---|
| 493 | self.end_headers()
|
|---|
| 494 | self.wfile.write(response)
|
|---|
| 495 | # shut down the connection
|
|---|
| 496 | self.wfile.flush()
|
|---|
| 497 | self.connection.shutdown(1)
|
|---|
| 498 |
|
|---|
| 499 | def log_request(self, code='-', size='-'):
|
|---|
| 500 | """Selectively log an accepted request."""
|
|---|
| 501 |
|
|---|
| 502 | if self.server.logRequests:
|
|---|
| 503 | BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
|
|---|
| 504 |
|
|---|
| 505 | class SimpleXMLRPCServer(SocketServer.TCPServer,
|
|---|
| 506 | SimpleXMLRPCDispatcher):
|
|---|
| 507 | """Simple XML-RPC server.
|
|---|
| 508 |
|
|---|
| 509 | Simple XML-RPC server that allows functions and a single instance
|
|---|
| 510 | to be installed to handle requests. The default implementation
|
|---|
| 511 | attempts to dispatch XML-RPC calls to the functions or instance
|
|---|
| 512 | installed in the server. Override the _dispatch method inhereted
|
|---|
| 513 | from SimpleXMLRPCDispatcher to change this behavior.
|
|---|
| 514 | """
|
|---|
| 515 |
|
|---|
| 516 | allow_reuse_address = True
|
|---|
| 517 |
|
|---|
| 518 | def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
|
|---|
| 519 | logRequests=True, allow_none=False, encoding=None):
|
|---|
| 520 | self.logRequests = logRequests
|
|---|
| 521 |
|
|---|
| 522 | SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
|
|---|
| 523 | SocketServer.TCPServer.__init__(self, addr, requestHandler)
|
|---|
| 524 |
|
|---|
| 525 | # [Bug #1222790] If possible, set close-on-exec flag; if a
|
|---|
| 526 | # method spawns a subprocess, the subprocess shouldn't have
|
|---|
| 527 | # the listening socket open.
|
|---|
| 528 | if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
|
|---|
| 529 | flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
|
|---|
| 530 | flags |= fcntl.FD_CLOEXEC
|
|---|
| 531 | fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
|
|---|
| 532 |
|
|---|
| 533 | class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
|
|---|
| 534 | """Simple handler for XML-RPC data passed through CGI."""
|
|---|
| 535 |
|
|---|
| 536 | def __init__(self, allow_none=False, encoding=None):
|
|---|
| 537 | SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
|
|---|
| 538 |
|
|---|
| 539 | def handle_xmlrpc(self, request_text):
|
|---|
| 540 | """Handle a single XML-RPC request"""
|
|---|
| 541 |
|
|---|
| 542 | response = self._marshaled_dispatch(request_text)
|
|---|
| 543 |
|
|---|
| 544 | print 'Content-Type: text/xml'
|
|---|
| 545 | print 'Content-Length: %d' % len(response)
|
|---|
| 546 | print
|
|---|
| 547 | sys.stdout.write(response)
|
|---|
| 548 |
|
|---|
| 549 | def handle_get(self):
|
|---|
| 550 | """Handle a single HTTP GET request.
|
|---|
| 551 |
|
|---|
| 552 | Default implementation indicates an error because
|
|---|
| 553 | XML-RPC uses the POST method.
|
|---|
| 554 | """
|
|---|
| 555 |
|
|---|
| 556 | code = 400
|
|---|
| 557 | message, explain = \
|
|---|
| 558 | BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
|
|---|
| 559 |
|
|---|
| 560 | response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
|
|---|
| 561 | {
|
|---|
| 562 | 'code' : code,
|
|---|
| 563 | 'message' : message,
|
|---|
| 564 | 'explain' : explain
|
|---|
| 565 | }
|
|---|
| 566 | print 'Status: %d %s' % (code, message)
|
|---|
| 567 | print 'Content-Type: text/html'
|
|---|
| 568 | print 'Content-Length: %d' % len(response)
|
|---|
| 569 | print
|
|---|
| 570 | sys.stdout.write(response)
|
|---|
| 571 |
|
|---|
| 572 | def handle_request(self, request_text = None):
|
|---|
| 573 | """Handle a single XML-RPC request passed through a CGI post method.
|
|---|
| 574 |
|
|---|
| 575 | If no XML data is given then it is read from stdin. The resulting
|
|---|
| 576 | XML-RPC response is printed to stdout along with the correct HTTP
|
|---|
| 577 | headers.
|
|---|
| 578 | """
|
|---|
| 579 |
|
|---|
| 580 | if request_text is None and \
|
|---|
| 581 | os.environ.get('REQUEST_METHOD', None) == 'GET':
|
|---|
| 582 | self.handle_get()
|
|---|
| 583 | else:
|
|---|
| 584 | # POST data is normally available through stdin
|
|---|
| 585 | if request_text is None:
|
|---|
| 586 | request_text = sys.stdin.read()
|
|---|
| 587 |
|
|---|
| 588 | self.handle_xmlrpc(request_text)
|
|---|
| 589 |
|
|---|
| 590 | if __name__ == '__main__':
|
|---|
| 591 | print 'Running XML-RPC server on port 8000'
|
|---|
| 592 | server = SimpleXMLRPCServer(("localhost", 8000))
|
|---|
| 593 | server.register_function(pow)
|
|---|
| 594 | server.register_function(lambda x,y: x+y, 'add')
|
|---|
| 595 | server.serve_forever()
|
|---|