全部博文(68)
分类: LINUX
2011-11-01 13:48:57
003 | This module can be used to create simple XML-RPC servers |
004 | by creating a server and either installing functions, a |
005 | class instance, or by extending the SimpleXMLRPCServer |
006 | class. |
007 |
008 | It can also be used to handle XML-RPC requests in a CGI |
009 | environment using CGIXMLRPCRequestHandler. |
010 |
011 | A list of possible usage patterns follows: |
012 |
013 | 1. Install functions: |
014 |
015 | server = SimpleXMLRPCServer(("localhost", 8000)) |
016 | server.register_function(pow) |
017 | server.register_function(lambda x,y: x+y, 'add') |
018 | server.serve_forever() |
019 |
020 | 2. Install an instance: |
021 |
022 | class MyFuncs: |
023 | def __init__(self): |
024 | # make all of the string functions available through |
025 | # string.func_name |
026 | import string |
027 | self.string = string |
028 | def _listMethods(self): |
029 | # implement this method so that system.listMethods |
030 | # knows to advertise the strings methods |
031 | return list_public_methods(self) + \ |
032 | ['string.' + method for method in list_public_methods(self.string)] |
033 | def pow(self, x, y): return pow(x, y) |
034 | def add(self, x, y) : return x + y |
035 |
036 | server = SimpleXMLRPCServer(("localhost", 8000)) |
037 | server.register_introspection_functions() |
038 | server.register_instance(MyFuncs()) |
039 | server.serve_forever() |
040 |
041 | 3. Install an instance with custom dispatch method: |
042 |
043 | class Math: |
044 | def _listMethods(self): |
045 | # this method must be present for system.listMethods |
046 | # to work |
047 | return ['add', 'pow'] |
048 | def _methodHelp(self, method): |
049 | # this method must be present for system.methodHelp |
050 | # to work |
051 | if method == 'add': |
052 | return "add(2,3) => 5" |
053 | elif method == 'pow': |
054 | return "pow(x, y[, z]) => number" |
055 | else: |
056 | # By convention, return empty |
057 | # string if no help is available |
058 | return "" |
059 | def _dispatch(self, method, params): |
060 | if method == 'pow': |
061 | return pow(*params) |
062 | elif method == 'add': |
063 | return params[0] + params[1] |
064 | else: |
065 | raise 'bad method' |
066 |
067 | server = SimpleXMLRPCServer(("localhost", 8000)) |
068 | server.register_introspection_functions() |
069 | server.register_instance(Math()) |
070 | server.serve_forever() |
071 |
072 | 4. Subclass SimpleXMLRPCServer: |
073 |
074 | class MathServer(SimpleXMLRPCServer): |
075 | def _dispatch(self, method, params): |
076 | try: |
077 | # We are forcing the 'export_' prefix on methods that are |
078 | # callable through XML-RPC to prevent potential security |
079 | # problems |
080 | func = getattr(self, 'export_' + method) |
081 | except AttributeError: |
082 | raise Exception('method "%s" is not supported' % method) |
083 | else: |
084 | return func(*params) |
085 |
086 | def export_add(self, x, y): |
087 | return x + y |
088 |
089 | server = MathServer(("localhost", 8000)) |
090 | server.serve_forever() |
091 |
092 | 5. CGI script: |
093 |
094 | server = CGIXMLRPCRequestHandler() |
095 | server.register_function(pow) |
096 | server.handle_request() |
097 | """ |
098 |
099 | # Written by Brian Quinlan (brian@sweetapp.com). |
100 | # Based on code written by Fredrik Lundh. |
101 |
102 | import xmlrpclib |
103 | from xmlrpclib import Fault |
104 | import SocketServer |
105 | import BaseHTTPServer |
106 | import sys |
107 | import os |
108 |
109 | def resolve_dotted_attribute(obj, attr, allow_dotted_names=True): |
110 | """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d |
111 |
112 | Resolves a dotted attribute name to an object. Raises |
113 | an AttributeError if any attribute in the chain starts with a '_'. |
114 |
115 | If the optional allow_dotted_names argument is false, dots are not |
116 | supported and this function operates similar to getattr(obj, attr). |
117 | """ |
118 |
119 | if allow_dotted_names: |
120 | attrs = attr.split('.') |
121 | else: |
122 | attrs = [attr] |
123 |
124 | for i in attrs: |
125 | if i.startswith('_'): |
126 | raise AttributeError( |
127 | 'attempt to access private attribute "%s"' % i |
128 | ) |
129 | else: |
130 | obj = getattr(obj,i) |
131 | return obj |
132 |
133 | def list_public_methods(obj): |
134 | """Returns a list of attribute strings, found in the specified |
135 | object, which represent callable attributes""" |
136 |
137 | return [member for member in dir(obj) |
138 | if not member.startswith('_') and |
139 | callable(getattr(obj, member))] |
140 |
141 | def remove_duplicates(lst): |
142 | """remove_duplicates([2,2,2,1,3,3]) => [3,1,2] |
143 |
144 | Returns a copy of a list without duplicates. Every list |
145 | item must be hashable and the order of the items in the |
146 | resulting list is not defined. |
147 | """ |
148 | u = {} |
149 | for x in lst: |
150 | u[x] = 1 |
151 |
152 | return u.keys() |
153 |
154 | class SimpleXMLRPCDispatcher: |
155 | """Mix-in class that dispatches XML-RPC requests. |
156 |
157 | This class is used to register XML-RPC method handlers |
158 | and then to dispatch them. There should never be any |
159 | reason to instantiate this class directly. |
160 | """ |
161 |
162 | def __init__(self): |
163 | self.funcs = {} |
164 | self.instance = None |
165 |
166 | def register_instance(self, instance, allow_dotted_names=False): |
167 | """Registers an instance to respond to XML-RPC requests. |
168 |
169 | Only one instance can be installed at a time. |
170 |
171 | If the registered instance has a _dispatch method then that |
172 | method will be called with the name of the XML-RPC method and |
173 | its parameters as a tuple |
174 | e.g. instance._dispatch('add',(2,3)) |
175 |
176 | If the registered instance does not have a _dispatch method |
177 | then the instance will be searched to find a matching method |
178 | and, if found, will be called. Methods beginning with an '_' |
179 | are considered private and will not be called by |
180 | SimpleXMLRPCServer. |
181 |
182 | If a registered function matches a XML-RPC request, then it |
183 | will be called instead of the registered instance. |
184 |
185 | If the optional allow_dotted_names argument is true and the |
186 | instance does not have a _dispatch method, method names |
187 | containing dots are supported and resolved, as long as none of |
188 | the name segments start with an '_'. |
189 |
190 | *** SECURITY WARNING: *** |
191 |
192 | Enabling the allow_dotted_names options allows intruders |
193 | to access your module's global variables and may allow |
194 | intruders to execute arbitrary code on your machine. Only |
195 | use this option on a secure, closed network. |
196 |
197 | """ |
198 |
199 | self.instance = instance |
200 | self.allow_dotted_names = allow_dotted_names |
201 |
202 | def register_function(self, function, name = None): |
203 | """Registers a function to respond to XML-RPC requests. |
204 |
205 | The optional name argument can be used to set a Unicode name |
206 | for the function. |
207 | """ |
208 |
209 | if name is None: |
210 | name = function.__name__ |
211 | self.funcs[name] = function |
212 |
213 | def register_introspection_functions(self): |
214 | """Registers the XML-RPC introspection methods in the system |
215 | namespace. |
216 |
217 | see |
218 | """ |
219 |
220 | self.funcs.update({'system.listMethods' : self.system_listMethods, |
221 | 'system.methodSignature' : self.system_methodSignature, |
222 | 'system.methodHelp' : self.system_methodHelp}) |
223 |
224 | def register_multicall_functions(self): |
225 | """Registers the XML-RPC multicall method in the system |
226 | namespace. |
227 |
228 | see http://www.xmlrpc.com/discuss/msgReader$1208""" |
229 |
230 | self.funcs.update({'system.multicall' : self.system_multicall}) |
231 |
232 | def _marshaled_dispatch(self, data, dispatch_method = None): |
233 | """Dispatches an XML-RPC method from marshalled (XML) data. |
234 |
235 | XML-RPC methods are dispatched from the marshalled (XML) data |
236 | using the _dispatch method and the result is returned as |
237 | marshalled data. For backwards compatibility, a dispatch |
238 | function can be provided as an argument (see comment in |
239 | SimpleXMLRPCRequestHandler.do_POST) but overriding the |
240 | existing method through subclassing is the prefered means |
241 | of changing method dispatch behavior. |
242 | """ |
243 |
244 | params, method = xmlrpclib.loads(data) |
245 |
246 | # generate response |
247 | try: |
248 | if dispatch_method is not None: |
249 | response = dispatch_method(method, params) |
250 | else: |
251 | response = self._dispatch(method, params) |
252 | # wrap response in a singleton tuple |
253 | response = (response,) |
254 | response = xmlrpclib.dumps(response, methodresponse=1) |
255 | except Fault, fault: |
256 | response = xmlrpclib.dumps(fault) |
257 | except: |
258 | # report exception back to server |
259 | response = xmlrpclib.dumps( |
260 | xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value)) |
261 | ) |
262 |
263 | return response |
264 |
265 | def system_listMethods(self): |
266 | """system.listMethods() => ['add', 'subtract', 'multiple'] |
267 |
268 | Returns a list of the methods supported by the server.""" |
269 |
270 | methods = self.funcs.keys() |
271 | if self.instance is not None: |
272 | # Instance can implement _listMethod to return a list of |
273 | # methods |
274 | if hasattr(self.instance, '_listMethods'): |
275 | methods = remove_duplicates( |
276 | methods + self.instance._listMethods() |
277 | ) |
278 | # if the instance has a _dispatch method then we |
279 | # don't have enough information to provide a list |
280 | # of methods |
281 | elif not hasattr(self.instance, '_dispatch'): |
282 | methods = remove_duplicates( |
283 | methods + list_public_methods(self.instance) |
284 | ) |
285 | methods.sort() |
286 | return methods |
287 |
288 | def system_methodSignature(self, method_name): |
289 | """system.methodSignature('add') => [double, int, int] |
290 |
291 | Returns a list describing the signature of the method. In the |
292 | above example, the add method takes two integers as arguments |
293 | and returns a double result. |
294 |
295 | This server does NOT support system.methodSignature.""" |
296 |
297 | # See |
298 |
299 | return 'signatures not supported' |
300 |
301 | def system_methodHelp(self, method_name): |
302 | """system.methodHelp('add') => "Adds two integers together" |
303 |
304 | Returns a string containing documentation for the specified method.""" |
305 |
306 | method = None |
307 | if self.funcs.has_key(method_name): |
308 | method = self.funcs[method_name] |
309 | elif self.instance is not None: |
310 | # Instance can implement _methodHelp to return help for a method |
311 | if hasattr(self.instance, '_methodHelp'): |
312 | return self.instance._methodHelp(method_name) |
313 | # if the instance has a _dispatch method then we |
314 | # don't have enough information to provide help |
315 | elif not hasattr(self.instance, '_dispatch'): |
316 | try: |
317 | method = resolve_dotted_attribute( |
318 | self.instance, |
319 | method_name, |
320 | self.allow_dotted_names |
321 | ) |
322 | except AttributeError: |
323 | pass |
324 |
325 | # Note that we aren't checking that the method actually |
326 | # be a callable object of some kind |
327 | if method is None: |
328 | return "" |
329 | else: |
330 | import pydoc |
331 | return pydoc.getdoc(method) |
332 |
333 | def system_multicall(self, call_list): |
334 | """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \ |
335 | [[4], ...] |
336 |
337 | Allows the caller to package multiple XML-RPC calls into a single |
338 | request. |
339 |
340 | See http://www.xmlrpc.com/discuss/msgReader$1208 |
341 | """ |
342 |
343 | results = [] |
344 | for call in call_list: |
345 | method_name = call['methodName'] |
346 | params = call['params'] |
347 |
348 | try: |
349 | # XXX A marshalling error in any response will fail the entire |
350 | # multicall. If someone cares they should fix this. |
351 | results.append([self._dispatch(method_name, params)]) |
352 | except Fault, fault: |
353 | results.append( |
354 | {'faultCode' : fault.faultCode, |
355 | 'faultString' : fault.faultString} |
356 | ) |
357 | except: |
358 | results.append( |
359 | {'faultCode' : 1, |
360 | 'faultString' : "%s:%s" % (sys.exc_type, sys.exc_value)} |
361 | ) |
362 | return results |
363 |
364 | def _dispatch(self, method, params): |
365 | """Dispatches the XML-RPC method. |
366 |
367 | XML-RPC calls are forwarded to a registered function that |
368 | matches the called XML-RPC method name. If no such function |
369 | exists then the call is forwarded to the registered instance, |
370 | if available. |
371 |
372 | If the registered instance has a _dispatch method then that |
373 | method will be called with the name of the XML-RPC method and |
374 | its parameters as a tuple |
375 | e.g. instance._dispatch('add',(2,3)) |
376 |
377 | If the registered instance does not have a _dispatch method |
378 | then the instance will be searched to find a matching method |
379 | and, if found, will be called. |
380 |
381 | Methods beginning with an '_' are considered private and will |
382 | not be called. |
383 | """ |
384 |
385 | func = None |
386 | try: |
387 | # check to see if a matching function has been registered |
388 | func = self.funcs[method] |
389 | except KeyError: |
390 | if self.instance is not None: |
391 | # check for a _dispatch method |
392 | if hasattr(self.instance, '_dispatch'): |
393 | return self.instance._dispatch(method, params) |
394 | else: |
395 | # call instance method directly |
396 | try: |
397 | func = resolve_dotted_attribute( |
398 | self.instance, |
399 | method, |
400 | self.allow_dotted_names |
401 | ) |
402 | except AttributeError: |
403 | pass |
404 |
405 | if func is not None: |
406 | return func(*params) |
407 | else: |
408 | raise Exception('method "%s" is not supported' % method) |
409 |
410 | class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
411 | """Simple XML-RPC request handler class. |
412 |
413 | Handles all HTTP POST requests and attempts to decode them as |
414 | XML-RPC requests. |
415 | """ |
416 |
417 | def do_POST(self): |
418 | """Handles the HTTP POST request. |
419 |
420 | Attempts to interpret all HTTP POST requests as XML-RPC calls, |
421 | which are forwarded to the server's _dispatch method for handling. |
422 | """ |
423 |
424 | try: |
425 | # get arguments |
426 | data = self.rfile.read(int(self.headers["content-length"])) |
427 | # In previous versions of SimpleXMLRPCServer, _dispatch |
428 | # could be overridden in this class, instead of in |
429 | # SimpleXMLRPCDispatcher. To maintain backwards compatibility, |
430 | # check to see if a subclass implements _dispatch and dispatch |
431 | # using that method if present. |
432 | response = self.server._marshaled_dispatch( |
433 | data, getattr(self, '_dispatch', None) |
434 | ) |
435 | except: # This should only happen if the module is buggy |
436 | # internal error, report as HTTP server error |
437 | self.send_response(500) |
438 | self.end_headers() |
439 | else: |
440 | # got a valid XML RPC response |
441 | self.send_response(200) |
442 | self.send_header("Content-type", "text/xml") |
443 | self.send_header("Content-length", str(len(response))) |
444 | self.end_headers() |
445 | self.wfile.write(response) |
446 |
447 | # shut down the connection |
448 | self.wfile.flush() |
449 | self.connection.shutdown(1) |
450 |
451 | def log_request(self, code='-', size='-'): |
452 | """Selectively log an accepted request.""" |
453 |
454 | if self.server.logRequests: |
455 | BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size) |
456 |
457 | class SimpleXMLRPCServer(SocketServer.TCPServer, |
458 | SimpleXMLRPCDispatcher): |
459 | """Simple XML-RPC server. |
460 |
461 | Simple XML-RPC server that allows functions and a single instance |
462 | to be installed to handle requests. The default implementation |
463 | attempts to dispatch XML-RPC calls to the functions or instance |
464 | installed in the server. Override the _dispatch method inhereted |
465 | from SimpleXMLRPCDispatcher to change this behavior. |
466 | """ |
467 |
468 | def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler, |
469 | logRequests=1): |
470 | self.logRequests = logRequests |
471 |
472 | SimpleXMLRPCDispatcher.__init__(self) |
473 | SocketServer.TCPServer.__init__(self, addr, requestHandler) |
474 |
475 | class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher): |
476 | """Simple handler for XML-RPC data passed through CGI.""" |
477 |
478 | def __init__(self): |
479 | SimpleXMLRPCDispatcher.__init__(self) |
480 |
481 | def handle_xmlrpc(self, request_text): |
482 | """Handle a single XML-RPC request""" |
483 |
484 | response = self._marshaled_dispatch(request_text) |
485 |
486 | print 'Content-Type: text/xml' |
487 | print 'Content-Length: %d' % len(response) |
488 |
489 | sys.stdout.write(response) |
490 |
491 | def handle_get(self): |
492 | """Handle a single HTTP GET request. |
493 |
494 | Default implementation indicates an error because |
495 | XML-RPC uses the POST method. |
496 | """ |
497 |
498 | code = 400 |
499 | message, explain = \ |
500 | BaseHTTPServer.BaseHTTPRequestHandler.responses[code] |
501 |
502 | response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \ |
503 | { |
504 | 'code' : code, |
505 | 'message' : message, |
506 | 'explain' : explain |
507 | } |
508 | print 'Status: %d %s' % (code, message) |
509 | print 'Content-Type: text/html' |
510 | print 'Content-Length: %d' % len(response) |
511 |
512 | sys.stdout.write(response) |
513 |
514 | def handle_request(self, request_text = None): |
515 | """Handle a single XML-RPC request passed through a CGI post method. |
516 |
517 | If no XML data is given then it is read from stdin. The resulting |
518 | XML-RPC response is printed to stdout along with the correct HTTP |
519 | headers. |
520 | """ |
521 |
522 | if request_text is None and \ |
523 | os.environ.get('REQUEST_METHOD', None) == 'GET': |
524 | self.handle_get() |
525 | else: |
526 | # POST data is normally available through stdin |
527 | if request_text is None: |
528 | request_text = sys.stdin.read() |
529 |
530 | self.handle_xmlrpc(request_text) |
531 |
532 | if __name__ == '__main__': |
533 | server = SimpleXMLRPCServer(("localhost", 8000)) |
534 | server.register_function(pow) |
535 | server.register_function(lambda x,y: x+y, 'add') |
536 | server.serve_forever() |