1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
59 except ImportError:
60 from SimpleXMLRPCServer import SimpleXMLRPCServer
61 from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
62
63 try:
64 import socketserver
65 except ImportError:
66 import SocketServer as socketserver
67
68 import rosgraph.network
69
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
81 if 0:
82 SimpleXMLRPCRequestHandler.log_message(self, format, *args)
83
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
94
95
96 self.allow_reuse_address = True
97
98
99 self.request_queue_size = min(socket.SOMAXCONN, 128)
100 if rosgraph.network.use_ipv6():
101 logger = logging.getLogger('xmlrpc')
102
103
104
105
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
111
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
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
128 """
129 Adds ThreadingMixin to SimpleXMLRPCServer to support multiple concurrent
130 requests via forking. Also makes logging toggleable.
131 """
133 SimpleXMLRPCServer.__init__(self, addr, request_handler, log_requests)
134
135
137 """
138 Base handler API for handlers used with XmlRpcNode. Public methods will be
139 exported as XML RPC methods.
140 """
141
143 """
144 callback into handler to inform it of XML-RPC URI
145 """
146 pass
147
149 """
150 callback into handler to inform it of shutdown
151 """
152 pass
153
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
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
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
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
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
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
227 logger = logging.getLogger('xmlrpc')
228 try:
229 log_requests = 0
230 port = self.port or 0
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]
237 if not self.port:
238 self.port = self.server.socket.getsockname()[1]
239
240 assert self.port, "Unable to retrieve local address binding"
241
242
243
244
245
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
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
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
291
292
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