Package rosgraph :: Module xmlrpc
[frames] | no frames]

Source Code for Module rosgraph.xmlrpc

  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   
 35  from __future__ import print_function 
 36   
 37  """ 
 38  Common XML-RPC for higher-level libraries running XML-RPC libraries in 
 39  ROS. In particular, this library provides common handling for URI 
 40  calculation based on ROS environment variables. 
 41   
 42  The common entry point for most libraries is the L{XmlRpcNode} class. 
 43  """ 
 44   
 45  import errno 
 46  import logging 
 47  import select 
 48  import socket 
 49   
 50  try: 
 51      import _thread 
 52  except ImportError: 
 53      import thread as _thread 
 54   
 55  import traceback 
 56   
 57  try: 
 58      from xmlrpc.server import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler #Python 3.x 
 59  except ImportError: 
 60      from SimpleXMLRPCServer import SimpleXMLRPCServer #Python 2.x 
 61      from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler #Python 2.x 
 62   
 63  try: 
 64      import socketserver 
 65  except ImportError: 
 66      import SocketServer as socketserver 
 67   
 68  import rosgraph.network 
 69   
70 -def isstring(s):
71 """Small helper version to check an object is a string in a way that works 72 for both Python 2 and 3 73 """ 74 try: 75 return isinstance(s, basestring) 76 except NameError: 77 return isinstance(s, str)
78
79 -class SilenceableXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
80 - def log_message(self, format, *args):
81 if 0: 82 SimpleXMLRPCRequestHandler.log_message(self, format, *args)
83
84 -class ThreadingXMLRPCServer(socketserver.ThreadingMixIn, SimpleXMLRPCServer):
85 """ 86 Adds ThreadingMixin to SimpleXMLRPCServer to support multiple concurrent 87 requests via threading. Also makes logging toggleable. 88 """
89 - def __init__(self, addr, log_requests=1):
90 """ 91 Overrides SimpleXMLRPCServer to set option to allow_reuse_address. 92 """ 93 # allow_reuse_address defaults to False in Python 2.4. We set it 94 # to True to allow quick restart on the same port. This is equivalent 95 # to calling setsockopt(SOL_SOCKET,SO_REUSEADDR,1) 96 self.allow_reuse_address = True 97 # Increase request_queue_size to handle issues with many simultaneous 98 # connections in OSX 10.11 99 self.request_queue_size = min(socket.SOMAXCONN, 128) 100 if rosgraph.network.use_ipv6(): 101 logger = logging.getLogger('xmlrpc') 102 # The XMLRPC library does not support IPv6 out of the box 103 # We have to monipulate private members and duplicate 104 # code from the constructor. 105 # TODO IPV6: Get this into SimpleXMLRPCServer 106 SimpleXMLRPCServer.__init__(self, addr, SilenceableXMLRPCRequestHandler, log_requests, bind_and_activate=False) 107 self.address_family = socket.AF_INET6 108 self.socket = socket.socket(self.address_family, self.socket_type) 109 logger.info('binding ipv6 xmlrpc socket to' + str(addr)) 110 # TODO: set IPV6_V6ONLY to 0: 111 # self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) 112 self.server_bind() 113 self.server_activate() 114 logger.info('bound to ' + str(self.socket.getsockname()[0:2])) 115 else: 116 SimpleXMLRPCServer.__init__(self, addr, SilenceableXMLRPCRequestHandler, log_requests)
117
118 - def handle_error(self, request, client_address):
119 """ 120 override ThreadingMixin, which sends errors to stderr 121 """ 122 if logging and traceback: 123 logger = logging.getLogger('xmlrpc') 124 if logger: 125 logger.error(traceback.format_exc())
126
127 -class ForkingXMLRPCServer(socketserver.ForkingMixIn, SimpleXMLRPCServer):
128 """ 129 Adds ThreadingMixin to SimpleXMLRPCServer to support multiple concurrent 130 requests via forking. Also makes logging toggleable. 131 """
132 - def __init__(self, addr, request_handler=SilenceableXMLRPCRequestHandler, log_requests=1):
133 SimpleXMLRPCServer.__init__(self, addr, request_handler, log_requests)
134 135
136 -class XmlRpcHandler(object):
137 """ 138 Base handler API for handlers used with XmlRpcNode. Public methods will be 139 exported as XML RPC methods. 140 """ 141
142 - def _ready(self, uri):
143 """ 144 callback into handler to inform it of XML-RPC URI 145 """ 146 pass
147
148 - def _shutdown(self, reason):
149 """ 150 callback into handler to inform it of shutdown 151 """ 152 pass
153
154 -class XmlRpcNode(object):
155 """ 156 Generic XML-RPC node. Handles the additional complexity of binding 157 an XML-RPC server to an arbitrary port. 158 XmlRpcNode is initialized when the uri field has a value. 159 """ 160
161 - def __init__(self, port=0, rpc_handler=None, on_run_error=None):
162 """ 163 XML RPC Node constructor 164 :param port: port to use for starting XML-RPC API. Set to 0 or omit to bind to any available port, ``int`` 165 :param rpc_handler: XML-RPC API handler for node, `XmlRpcHandler` 166 :param on_run_error: function to invoke if server.run() throws 167 Exception. Server always terminates if run() throws, but this 168 enables cleanup routines to be invoked if server goes down, as 169 well as include additional debugging. ``fn(Exception)`` 170 """ 171 super(XmlRpcNode, self).__init__() 172 173 self.handler = rpc_handler 174 self.uri = None # initialize the property now so it can be tested against, will be filled in later 175 self.server = None 176 if port and isstring(port): 177 port = int(port) 178 self.port = port 179 self.is_shutdown = False 180 self.on_run_error = on_run_error
181
182 - def shutdown(self, reason):
183 """ 184 Terminate i/o connections for this server. 185 186 :param reason: human-readable debug string, ``str`` 187 """ 188 self.is_shutdown = True 189 if self.server: 190 server = self.server 191 handler = self.handler 192 self.handler = self.server = self.port = self.uri = None 193 if handler: 194 handler._shutdown(reason) 195 if server: 196 server.socket.close() 197 server.server_close()
198
199 - def start(self):
200 """ 201 Initiate a thread to run the XML RPC server. Uses thread.start_new_thread. 202 """ 203 _thread.start_new_thread(self.run, ())
204
205 - def set_uri(self, uri):
206 """ 207 Sets the XML-RPC URI. Defined as a separate method as a hood 208 for subclasses to bootstrap initialization. Should not be called externally. 209 210 :param uri: XMLRPC URI, ``str`` 211 """ 212 self.uri = uri
213
214 - def run(self):
215 try: 216 self._run() 217 except Exception as e: 218 if self.is_shutdown: 219 pass 220 elif self.on_run_error is not None: 221 self.on_run_error(e) 222 else: 223 raise
224 225 # separated out for easier testing
226 - def _run_init(self):
227 logger = logging.getLogger('xmlrpc') 228 try: 229 log_requests = 0 230 port = self.port or 0 #0 = any 231 232 bind_address = rosgraph.network.get_bind_address() 233 logger.info("XML-RPC server binding to %s:%d" % (bind_address, port)) 234 235 self.server = ThreadingXMLRPCServer((bind_address, port), log_requests) 236 self.port = self.server.server_address[1] #set the port to whatever server bound to 237 if not self.port: 238 self.port = self.server.socket.getsockname()[1] #Python 2.4 239 240 assert self.port, "Unable to retrieve local address binding" 241 242 # #528: semi-complicated logic for determining XML-RPC URI 243 # - if ROS_IP/ROS_HOSTNAME is set, use that address 244 # - if the hostname returns a non-localhost value, use that 245 # - use whatever rosgraph.network.get_local_address() returns 246 uri = None 247 override = rosgraph.network.get_address_override() 248 if override: 249 uri = 'http://%s:%s/'%(override, self.port) 250 else: 251 try: 252 hostname = socket.gethostname() 253 if hostname and not hostname == 'localhost' and not hostname.startswith('127.') and hostname != '::': 254 uri = 'http://%s:%s/'%(hostname, self.port) 255 except: 256 pass 257 if not uri: 258 uri = 'http://%s:%s/'%(rosgraph.network.get_local_address(), self.port) 259 self.set_uri(uri) 260 261 logger.info("Started XML-RPC server [%s]", self.uri) 262 263 self.server.register_multicall_functions() 264 self.server.register_instance(self.handler) 265 266 except socket.error as e: 267 if e.errno == errno.EADDRINUSE: 268 msg = "ERROR: Unable to start XML-RPC server, port %s is already in use"%self.port 269 else: 270 msg = "ERROR: Unable to start XML-RPC server: %s" % e.strerror 271 logger.error(msg) 272 print(msg) 273 raise #let higher level catch this 274 275 if self.handler is not None: 276 self.handler._ready(self.uri) 277 logger.info("xml rpc node: starting XML-RPC server")
278
279 - def _run(self):
280 """ 281 Main processing thread body. 282 :raises: :exc:`socket.error` If server cannot bind 283 284 """ 285 self._run_init() 286 while not self.is_shutdown: 287 try: 288 self.server.serve_forever() 289 except (IOError, select.error) as e: 290 # check for interrupted call, which can occur if we're 291 # embedded in a program using signals. All other 292 # exceptions break _run. 293 if self.is_shutdown: 294 pass 295 elif e.errno != errno.EINTR: 296 self.is_shutdown = True 297 logging.getLogger('xmlrpc').error("serve forever IOError: %s, %s"%(e.errno, e.strerror))
298