PageRenderTime 51ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/lib-python/2.7/SimpleXMLRPCServer.py

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