PageRenderTime 38ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/tools/rosgraph/src/rosgraph/xmlrpc.py

https://gitlab.com/F34140r/ros_comm
Python | 296 lines | 144 code | 35 blank | 117 comment | 44 complexity | c8e80a4f9b67c91264727f360048c5c1 MD5 | raw file
Possible License(s): LGPL-2.1
  1. # Software License Agreement (BSD License)
  2. #
  3. # Copyright (c) 2008, Willow Garage, Inc.
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions
  8. # are met:
  9. #
  10. # * Redistributions of source code must retain the above copyright
  11. # notice, this list of conditions and the following disclaimer.
  12. # * Redistributions in binary form must reproduce the above
  13. # copyright notice, this list of conditions and the following
  14. # disclaimer in the documentation and/or other materials provided
  15. # with the distribution.
  16. # * Neither the name of Willow Garage, Inc. nor the names of its
  17. # contributors may be used to endorse or promote products derived
  18. # from this software without specific prior written permission.
  19. #
  20. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  23. # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  24. # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  25. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  26. # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  27. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  28. # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  29. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  30. # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  31. # POSSIBILITY OF SUCH DAMAGE.
  32. #
  33. # Revision $Id: xmlrpc.py 15336 2011-11-07 20:43:00Z kwc $
  34. from __future__ import print_function
  35. """
  36. Common XML-RPC for higher-level libraries running XML-RPC libraries in
  37. ROS. In particular, this library provides common handling for URI
  38. calculation based on ROS environment variables.
  39. The common entry point for most libraries is the L{XmlRpcNode} class.
  40. """
  41. import logging
  42. import select
  43. import socket
  44. try:
  45. import _thread
  46. except ImportError:
  47. import thread as _thread
  48. import traceback
  49. try:
  50. from xmlrpc.server import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler #Python 3.x
  51. except ImportError:
  52. from SimpleXMLRPCServer import SimpleXMLRPCServer #Python 2.x
  53. from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler #Python 2.x
  54. try:
  55. import socketserver
  56. except ImportError:
  57. import SocketServer as socketserver
  58. import rosgraph.network
  59. def isstring(s):
  60. """Small helper version to check an object is a string in a way that works
  61. for both Python 2 and 3
  62. """
  63. try:
  64. return isinstance(s, basestring)
  65. except NameError:
  66. return isinstance(s, str)
  67. class SilenceableXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
  68. def log_message(self, format, *args):
  69. if 0:
  70. SimpleXMLRPCRequestHandler.log_message(self, format, *args)
  71. class ThreadingXMLRPCServer(socketserver.ThreadingMixIn, SimpleXMLRPCServer):
  72. """
  73. Adds ThreadingMixin to SimpleXMLRPCServer to support multiple concurrent
  74. requests via threading. Also makes logging toggleable.
  75. """
  76. def __init__(self, addr, log_requests=1):
  77. """
  78. Overrides SimpleXMLRPCServer to set option to allow_reuse_address.
  79. """
  80. # allow_reuse_address defaults to False in Python 2.4. We set it
  81. # to True to allow quick restart on the same port. This is equivalent
  82. # to calling setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
  83. self.allow_reuse_address = True
  84. if rosgraph.network.use_ipv6():
  85. logger = logging.getLogger('xmlrpc')
  86. # The XMLRPC library does not support IPv6 out of the box
  87. # We have to monipulate private members and duplicate
  88. # code from the constructor.
  89. # TODO IPV6: Get this into SimpleXMLRPCServer
  90. SimpleXMLRPCServer.__init__(self, addr, SilenceableXMLRPCRequestHandler, log_requests, bind_and_activate=False)
  91. self.address_family = socket.AF_INET6
  92. self.socket = socket.socket(self.address_family, self.socket_type)
  93. logger.info('binding ipv6 xmlrpc socket to' + str(addr))
  94. # TODO: set IPV6_V6ONLY to 0:
  95. # self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
  96. self.server_bind()
  97. self.server_activate()
  98. logger.info('bound to ' + str(self.socket.getsockname()[0:2]))
  99. else:
  100. SimpleXMLRPCServer.__init__(self, addr, SilenceableXMLRPCRequestHandler, log_requests)
  101. def handle_error(self, request, client_address):
  102. """
  103. override ThreadingMixin, which sends errors to stderr
  104. """
  105. if logging and traceback:
  106. logger = logging.getLogger('xmlrpc')
  107. if logger:
  108. logger.error(traceback.format_exc())
  109. class ForkingXMLRPCServer(socketserver.ForkingMixIn, SimpleXMLRPCServer):
  110. """
  111. Adds ThreadingMixin to SimpleXMLRPCServer to support multiple concurrent
  112. requests via forking. Also makes logging toggleable.
  113. """
  114. def __init__(self, addr, request_handler=SilenceableXMLRPCRequestHandler, log_requests=1):
  115. SimpleXMLRPCServer.__init__(self, addr, request_handler, log_requests)
  116. class XmlRpcHandler(object):
  117. """
  118. Base handler API for handlers used with XmlRpcNode. Public methods will be
  119. exported as XML RPC methods.
  120. """
  121. def _ready(self, uri):
  122. """
  123. callback into handler to inform it of XML-RPC URI
  124. """
  125. pass
  126. def _shutdown(self, reason):
  127. """
  128. callback into handler to inform it of shutdown
  129. """
  130. pass
  131. class XmlRpcNode(object):
  132. """
  133. Generic XML-RPC node. Handles the additional complexity of binding
  134. an XML-RPC server to an arbitrary port.
  135. XmlRpcNode is initialized when the uri field has a value.
  136. """
  137. def __init__(self, port=0, rpc_handler=None, on_run_error=None):
  138. """
  139. XML RPC Node constructor
  140. :param port: port to use for starting XML-RPC API. Set to 0 or omit to bind to any available port, ``int``
  141. :param rpc_handler: XML-RPC API handler for node, `XmlRpcHandler`
  142. :param on_run_error: function to invoke if server.run() throws
  143. Exception. Server always terminates if run() throws, but this
  144. enables cleanup routines to be invoked if server goes down, as
  145. well as include additional debugging. ``fn(Exception)``
  146. """
  147. super(XmlRpcNode, self).__init__()
  148. self.handler = rpc_handler
  149. self.uri = None # initialize the property now so it can be tested against, will be filled in later
  150. self.server = None
  151. if port and isstring(port):
  152. port = int(port)
  153. self.port = port
  154. self.is_shutdown = False
  155. self.on_run_error = on_run_error
  156. def shutdown(self, reason):
  157. """
  158. Terminate i/o connections for this server.
  159. :param reason: human-readable debug string, ``str``
  160. """
  161. self.is_shutdown = True
  162. if self.server:
  163. server = self.server
  164. handler = self.handler
  165. self.handler = self.server = self.port = self.uri = None
  166. if handler:
  167. handler._shutdown(reason)
  168. if server:
  169. server.socket.close()
  170. server.server_close()
  171. def start(self):
  172. """
  173. Initiate a thread to run the XML RPC server. Uses thread.start_new_thread.
  174. """
  175. _thread.start_new_thread(self.run, ())
  176. def set_uri(self, uri):
  177. """
  178. Sets the XML-RPC URI. Defined as a separate method as a hood
  179. for subclasses to bootstrap initialization. Should not be called externally.
  180. :param uri: XMLRPC URI, ``str``
  181. """
  182. self.uri = uri
  183. def run(self):
  184. try:
  185. self._run()
  186. except Exception as e:
  187. if self.is_shutdown:
  188. pass
  189. elif self.on_run_error is not None:
  190. self.on_run_error(e)
  191. else:
  192. raise
  193. # separated out for easier testing
  194. def _run_init(self):
  195. logger = logging.getLogger('xmlrpc')
  196. try:
  197. log_requests = 0
  198. port = self.port or 0 #0 = any
  199. bind_address = rosgraph.network.get_bind_address()
  200. logger.info("XML-RPC server binding to %s:%d" % (bind_address, port))
  201. self.server = ThreadingXMLRPCServer((bind_address, port), log_requests)
  202. self.port = self.server.server_address[1] #set the port to whatever server bound to
  203. if not self.port:
  204. self.port = self.server.socket.getsockname()[1] #Python 2.4
  205. assert self.port, "Unable to retrieve local address binding"
  206. # #528: semi-complicated logic for determining XML-RPC URI
  207. # - if ROS_IP/ROS_HOSTNAME is set, use that address
  208. # - if the hostname returns a non-localhost value, use that
  209. # - use whatever rosgraph.network.get_local_address() returns
  210. uri = None
  211. override = rosgraph.network.get_address_override()
  212. if override:
  213. uri = 'http://%s:%s/'%(override, self.port)
  214. else:
  215. try:
  216. hostname = socket.gethostname()
  217. if hostname and not hostname == 'localhost' and not hostname.startswith('127.') and hostname != '::':
  218. uri = 'http://%s:%s/'%(hostname, self.port)
  219. except:
  220. pass
  221. if not uri:
  222. uri = 'http://%s:%s/'%(rosgraph.network.get_local_address(), self.port)
  223. self.set_uri(uri)
  224. logger.info("Started XML-RPC server [%s]", self.uri)
  225. self.server.register_multicall_functions()
  226. self.server.register_instance(self.handler)
  227. except socket.error as e:
  228. (n, errstr) = e
  229. if n == 98:
  230. msg = "ERROR: Unable to start XML-RPC server, port %s is already in use"%self.port
  231. else:
  232. msg = "ERROR: Unable to start XML-RPC server: %s"%errstr
  233. logger.error(msg)
  234. print(msg)
  235. raise #let higher level catch this
  236. if self.handler is not None:
  237. self.handler._ready(self.uri)
  238. logger.info("xml rpc node: starting XML-RPC server")
  239. def _run(self):
  240. """
  241. Main processing thread body.
  242. :raises: :exc:`socket.error` If server cannot bind
  243. """
  244. self._run_init()
  245. while not self.is_shutdown:
  246. try:
  247. self.server.serve_forever()
  248. except (IOError, select.error) as e:
  249. (errno, errstr) = e
  250. # check for interrupted call, which can occur if we're
  251. # embedded in a program using signals. All other
  252. # exceptions break _run.
  253. if self.is_shutdown:
  254. pass
  255. elif errno != 4:
  256. self.is_shutdown = True
  257. logging.getLogger('xmlrpc').error("serve forever IOError: %s, %s"%(errno, errstr))