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

Source Code for Module roslib.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 15335 2011-11-07 20:42:37Z 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 logging 
 46  import select 
 47  import socket 
 48  import string 
 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 roslib.network 
 69  import roslib.exceptions 
 70   
71 -def isstring(s):
72 """Small helper version to check an object is a string in a way that works 73 for both Python 2 and 3 74 """ 75 try: 76 return isinstance(s, basestring) 77 except NameError: 78 return isinstance(s, str)
79
80 -class SilenceableXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
81 - def log_message(self, format, *args):
82 if 0: 83 SimpleXMLRPCRequestHandler.log_message(self, format, *args)
84
85 -class ThreadingXMLRPCServer(socketserver.ThreadingMixIn, SimpleXMLRPCServer):
86 """ 87 Adds ThreadingMixin to SimpleXMLRPCServer to support multiple concurrent 88 requests via threading. Also makes logging toggleable. 89 """
90 - def __init__(self, addr, log_requests=1):
91 """ 92 Overrides SimpleXMLRPCServer to set option to allow_reuse_address. 93 """ 94 # allow_reuse_address defaults to False in Python 2.4. We set it 95 # to True to allow quick restart on the same port. This is equivalent 96 # to calling setsockopt(SOL_SOCKET,SO_REUSEADDR,1) 97 self.allow_reuse_address = True 98 SimpleXMLRPCServer.__init__(self, addr, SilenceableXMLRPCRequestHandler, log_requests)
99
100 - def handle_error(self, request, client_address):
101 """ 102 override ThreadingMixin, which sends errors to stderr 103 """ 104 if logging and traceback: 105 logger = logging.getLogger('xmlrpc') 106 if logger: 107 logger.error(traceback.format_exc())
108
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 117
118 -class XmlRpcHandler(object):
119 """ 120 Base handler API for handlers used with XmlRpcNode. Public methods will be 121 exported as XML RPC methods. 122 """ 123
124 - def _ready(self, uri):
125 """ 126 callback into handler to inform it of XML-RPC URI 127 """ 128 pass
129
130 -class XmlRpcNode(object):
131 """ 132 Generic XML-RPC node. Handles the additional complexity of binding 133 an XML-RPC server to an arbitrary port. 134 XmlRpcNode is initialized when the uri field has a value. 135 """ 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. 141 @type port: int 142 @param rpc_handler: XML-RPC API handler for node. 143 @type rpc_handler: XmlRpcHandler 144 @param on_run_error: function to invoke if server.run() throws 145 Exception. Server always terminates if run() throws, but this 146 enables cleanup routines to be invoked if server goes down, as 147 well as include additional debugging. 148 @type on_run_error: fn(Exception) 149 """ 150 super(XmlRpcNode, self).__init__() 151 152 self.handler = rpc_handler 153 self.uri = None # initialize the property now so it can be tested against, will be filled in later 154 self.server = None 155 if port and isstring(port): 156 port = int(port) 157 self.port = port 158 self.is_shutdown = False 159 self.on_run_error = on_run_error
160
161 - def shutdown(self, reason):
162 """ 163 Terminate i/o connections for this server. 164 @param reason: human-readable debug string 165 @type reason: str 166 """ 167 self.is_shutdown = True 168 if self.server: 169 server = self.server 170 handler = self.handler 171 self.handler = self.server = self.port = self.uri = None 172 if handler: 173 handler._shutdown(reason) 174 if server: 175 server.socket.close() 176 server.server_close()
177
178 - def start(self):
179 """ 180 Initiate a thread to run the XML RPC server. Uses thread.start_new_thread. 181 """ 182 _thread.start_new_thread(self.run, ())
183
184 - def set_uri(self, uri):
185 """ 186 Sets the XML-RPC URI. Defined as a separate method as a hood 187 for subclasses to bootstrap initialization. Should not be called externally. 188 @param uri: XMLRPC URI. 189 @type uri: str 190 """ 191 self.uri = uri
192
193 - def run(self):
194 try: 195 self._run() 196 except Exception as e: 197 if self.is_shutdown: 198 pass 199 elif self.on_run_error is not None: 200 self.on_run_error(e) 201 else: 202 raise
203
204 - def _run(self):
205 """ 206 Main processing thread body. 207 @raise socket.error: If server cannot bind 208 @raise roslib.exceptions.ROSLibException: If unknown error occurs 209 """ 210 logger = logging.getLogger('xmlrpc') 211 try: 212 log_requests = 0 213 port = self.port or 0 #0 = any 214 215 bind_address = roslib.network.get_bind_address() 216 logger.info("XML-RPC server binding to %s"%bind_address) 217 218 self.server = ThreadingXMLRPCServer((bind_address, port), log_requests) 219 self.port = self.server.server_address[1] #set the port to whatever server bound to 220 if not self.port: 221 self.port = self.server.socket.getsockname()[1] #Python 2.4 222 if not self.port: 223 raise roslib.exceptions.ROSLibException("Unable to retrieve local address binding") 224 225 # #528: semi-complicated logic for determining XML-RPC URI 226 # - if ROS_IP/ROS_HOSTNAME is set, use that address 227 # - if the hostname returns a non-localhost value, use that 228 # - use whatever roslib.network.get_local_address() returns 229 uri = None 230 override = roslib.network.get_address_override() 231 if override: 232 uri = 'http://%s:%s/'%(override, self.port) 233 else: 234 try: 235 hostname = socket.gethostname() 236 if hostname and not hostname == 'localhost' and not hostname.startswith('127.'): 237 uri = 'http://%s:%s/'%(hostname, self.port) 238 except: 239 pass 240 if not uri: 241 uri = 'http://%s:%s/'%(roslib.network.get_local_address(), self.port) 242 self.set_uri(uri) 243 244 #print "... started XML-RPC Server", self.uri 245 logger.info("Started XML-RPC server [%s]", self.uri) 246 247 self.server.register_multicall_functions() 248 self.server.register_instance(self.handler) 249 250 except socket.error as e: 251 (n, errstr) = e 252 if n == 98: 253 msg = "ERROR: Unable to start XML-RPC server, port %s is already in use"%self.port 254 else: 255 msg = "ERROR: Unable to start XML-RPC server: %s"%errstr 256 logger.error(msg) 257 print(msg) 258 raise #let higher level catch this 259 260 if self.handler is not None: 261 self.handler._ready(self.uri) 262 logger.info("xml rpc node: starting XML-RPC server") 263 while not self.is_shutdown: 264 try: 265 self.server.serve_forever() 266 except (IOError, select.error) as e: 267 (errno, errstr) = e 268 # check for interrupted call, which can occur if we're 269 # embedded in a program using signals. All other 270 # exceptions break _run. 271 if self.is_shutdown: 272 pass 273 elif errno != 4: 274 self.is_shutdown = True 275 logger.error("serve forever IOError: %s, %s"%(errno, errstr))
276