~openerp/openobject-server/web-dashboard

« back to all changes in this revision

Viewing changes to python25-compat/SimpleXMLRPCServer.py

  • Committer: P. Christeas
  • Date: 2009-09-10 00:02:41 UTC
  • mto: (1884.1.1 p_christeas_server)
  • mto: This revision was merged to the branch mainline in revision 1900.
  • Revision ID: p_christ@hol.gr-20090910000241-3o1o5hcll10efa2y
Backport the HTTP server code from python 2.6 to 2.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Simple XML-RPC Server.
 
2
 
 
3
This module can be used to create simple XML-RPC servers
 
4
by creating a server and either installing functions, a
 
5
class instance, or by extending the SimpleXMLRPCServer
 
6
class.
 
7
 
 
8
It can also be used to handle XML-RPC requests in a CGI
 
9
environment using CGIXMLRPCRequestHandler.
 
10
 
 
11
A list of possible usage patterns follows:
 
12
 
 
13
1. Install functions:
 
14
 
 
15
server = SimpleXMLRPCServer(("localhost", 8000))
 
16
server.register_function(pow)
 
17
server.register_function(lambda x,y: x+y, 'add')
 
18
server.serve_forever()
 
19
 
 
20
2. Install an instance:
 
21
 
 
22
class MyFuncs:
 
23
    def __init__(self):
 
24
        # make all of the string functions available through
 
25
        # string.func_name
 
26
        import string
 
27
        self.string = string
 
28
    def _listMethods(self):
 
29
        # implement this method so that system.listMethods
 
30
        # knows to advertise the strings methods
 
31
        return list_public_methods(self) + \
 
32
                ['string.' + method for method in list_public_methods(self.string)]
 
33
    def pow(self, x, y): return pow(x, y)
 
34
    def add(self, x, y) : return x + y
 
35
 
 
36
server = SimpleXMLRPCServer(("localhost", 8000))
 
37
server.register_introspection_functions()
 
38
server.register_instance(MyFuncs())
 
39
server.serve_forever()
 
40
 
 
41
3. Install an instance with custom dispatch method:
 
42
 
 
43
class Math:
 
44
    def _listMethods(self):
 
45
        # this method must be present for system.listMethods
 
46
        # to work
 
47
        return ['add', 'pow']
 
48
    def _methodHelp(self, method):
 
49
        # this method must be present for system.methodHelp
 
50
        # to work
 
51
        if method == 'add':
 
52
            return "add(2,3) => 5"
 
53
        elif method == 'pow':
 
54
            return "pow(x, y[, z]) => number"
 
55
        else:
 
56
            # By convention, return empty
 
57
            # string if no help is available
 
58
            return ""
 
59
    def _dispatch(self, method, params):
 
60
        if method == 'pow':
 
61
            return pow(*params)
 
62
        elif method == 'add':
 
63
            return params[0] + params[1]
 
64
        else:
 
65
            raise 'bad method'
 
66
 
 
67
server = SimpleXMLRPCServer(("localhost", 8000))
 
68
server.register_introspection_functions()
 
69
server.register_instance(Math())
 
70
server.serve_forever()
 
71
 
 
72
4. Subclass SimpleXMLRPCServer:
 
73
 
 
74
class MathServer(SimpleXMLRPCServer):
 
75
    def _dispatch(self, method, params):
 
76
        try:
 
77
            # We are forcing the 'export_' prefix on methods that are
 
78
            # callable through XML-RPC to prevent potential security
 
79
            # problems
 
80
            func = getattr(self, 'export_' + method)
 
81
        except AttributeError:
 
82
            raise Exception('method "%s" is not supported' % method)
 
83
        else:
 
84
            return func(*params)
 
85
 
 
86
    def export_add(self, x, y):
 
87
        return x + y
 
88
 
 
89
server = MathServer(("localhost", 8000))
 
90
server.serve_forever()
 
91
 
 
92
5. CGI script:
 
93
 
 
94
server = CGIXMLRPCRequestHandler()
 
95
server.register_function(pow)
 
96
server.handle_request()
 
97
"""
 
98
 
 
99
# 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
import traceback
 
109
try:
 
110
    import fcntl
 
111
except ImportError:
 
112
    fcntl = None
 
113
 
 
114
def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
 
115
    """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
 
116
 
 
117
    Resolves a dotted attribute name to an object.  Raises
 
118
    an AttributeError if any attribute in the chain starts with a '_'.
 
119
 
 
120
    If the optional allow_dotted_names argument is false, dots are not
 
121
    supported and this function operates similar to getattr(obj, attr).
 
122
    """
 
123
 
 
124
    if allow_dotted_names:
 
125
        attrs = attr.split('.')
 
126
    else:
 
127
        attrs = [attr]
 
128
 
 
129
    for i in attrs:
 
130
        if i.startswith('_'):
 
131
            raise AttributeError(
 
132
                'attempt to access private attribute "%s"' % i
 
133
                )
 
134
        else:
 
135
            obj = getattr(obj,i)
 
136
    return obj
 
137
 
 
138
def list_public_methods(obj):
 
139
    """Returns a list of attribute strings, found in the specified
 
140
    object, which represent callable attributes"""
 
141
 
 
142
    return [member for member in dir(obj)
 
143
                if not member.startswith('_') and
 
144
                    hasattr(getattr(obj, member), '__call__')]
 
145
 
 
146
def remove_duplicates(lst):
 
147
    """remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
 
148
 
 
149
    Returns a copy of a list without duplicates. Every list
 
150
    item must be hashable and the order of the items in the
 
151
    resulting list is not defined.
 
152
    """
 
153
    u = {}
 
154
    for x in lst:
 
155
        u[x] = 1
 
156
 
 
157
    return u.keys()
 
158
 
 
159
class SimpleXMLRPCDispatcher:
 
160
    """Mix-in class that dispatches XML-RPC requests.
 
161
 
 
162
    This class is used to register XML-RPC method handlers
 
163
    and then to dispatch them. There should never be any
 
164
    reason to instantiate this class directly.
 
165
    """
 
166
 
 
167
    def __init__(self, allow_none, encoding):
 
168
        self.funcs = {}
 
169
        self.instance = None
 
170
        self.allow_none = allow_none
 
171
        self.encoding = encoding
 
172
 
 
173
    def register_instance(self, instance, allow_dotted_names=False):
 
174
        """Registers an instance to respond to XML-RPC requests.
 
175
 
 
176
        Only one instance can be installed at a time.
 
177
 
 
178
        If the registered instance has a _dispatch method then that
 
179
        method will be called with the name of the XML-RPC method and
 
180
        its parameters as a tuple
 
181
        e.g. instance._dispatch('add',(2,3))
 
182
 
 
183
        If the registered instance does not have a _dispatch method
 
184
        then the instance will be searched to find a matching method
 
185
        and, if found, will be called. Methods beginning with an '_'
 
186
        are considered private and will not be called by
 
187
        SimpleXMLRPCServer.
 
188
 
 
189
        If a registered function matches a XML-RPC request, then it
 
190
        will be called instead of the registered instance.
 
191
 
 
192
        If the optional allow_dotted_names argument is true and the
 
193
        instance does not have a _dispatch method, method names
 
194
        containing dots are supported and resolved, as long as none of
 
195
        the name segments start with an '_'.
 
196
 
 
197
            *** SECURITY WARNING: ***
 
198
 
 
199
            Enabling the allow_dotted_names options allows intruders
 
200
            to access your module's global variables and may allow
 
201
            intruders to execute arbitrary code on your machine.  Only
 
202
            use this option on a secure, closed network.
 
203
 
 
204
        """
 
205
 
 
206
        self.instance = instance
 
207
        self.allow_dotted_names = allow_dotted_names
 
208
 
 
209
    def register_function(self, function, name = None):
 
210
        """Registers a function to respond to XML-RPC requests.
 
211
 
 
212
        The optional name argument can be used to set a Unicode name
 
213
        for the function.
 
214
        """
 
215
 
 
216
        if name is None:
 
217
            name = function.__name__
 
218
        self.funcs[name] = function
 
219
 
 
220
    def register_introspection_functions(self):
 
221
        """Registers the XML-RPC introspection methods in the system
 
222
        namespace.
 
223
 
 
224
        see http://xmlrpc.usefulinc.com/doc/reserved.html
 
225
        """
 
226
 
 
227
        self.funcs.update({'system.listMethods' : self.system_listMethods,
 
228
                      'system.methodSignature' : self.system_methodSignature,
 
229
                      'system.methodHelp' : self.system_methodHelp})
 
230
 
 
231
    def register_multicall_functions(self):
 
232
        """Registers the XML-RPC multicall method in the system
 
233
        namespace.
 
234
 
 
235
        see http://www.xmlrpc.com/discuss/msgReader$1208"""
 
236
 
 
237
        self.funcs.update({'system.multicall' : self.system_multicall})
 
238
 
 
239
    def _marshaled_dispatch(self, data, dispatch_method = None):
 
240
        """Dispatches an XML-RPC method from marshalled (XML) data.
 
241
 
 
242
        XML-RPC methods are dispatched from the marshalled (XML) data
 
243
        using the _dispatch method and the result is returned as
 
244
        marshalled data. For backwards compatibility, a dispatch
 
245
        function can be provided as an argument (see comment in
 
246
        SimpleXMLRPCRequestHandler.do_POST) but overriding the
 
247
        existing method through subclassing is the prefered means
 
248
        of changing method dispatch behavior.
 
249
        """
 
250
 
 
251
        try:
 
252
            params, method = xmlrpclib.loads(data)
 
253
 
 
254
            # generate response
 
255
            if dispatch_method is not None:
 
256
                response = dispatch_method(method, params)
 
257
            else:
 
258
                response = self._dispatch(method, params)
 
259
            # wrap response in a singleton tuple
 
260
            response = (response,)
 
261
            response = xmlrpclib.dumps(response, methodresponse=1,
 
262
                                       allow_none=self.allow_none, encoding=self.encoding)
 
263
        except Fault, fault:
 
264
            response = xmlrpclib.dumps(fault, allow_none=self.allow_none,
 
265
                                       encoding=self.encoding)
 
266
        except:
 
267
            # report exception back to server
 
268
            exc_type, exc_value, exc_tb = sys.exc_info()
 
269
            response = xmlrpclib.dumps(
 
270
                xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)),
 
271
                encoding=self.encoding, allow_none=self.allow_none,
 
272
                )
 
273
 
 
274
        return response
 
275
 
 
276
    def system_listMethods(self):
 
277
        """system.listMethods() => ['add', 'subtract', 'multiple']
 
278
 
 
279
        Returns a list of the methods supported by the server."""
 
280
 
 
281
        methods = self.funcs.keys()
 
282
        if self.instance is not None:
 
283
            # Instance can implement _listMethod to return a list of
 
284
            # methods
 
285
            if hasattr(self.instance, '_listMethods'):
 
286
                methods = remove_duplicates(
 
287
                        methods + self.instance._listMethods()
 
288
                    )
 
289
            # if the instance has a _dispatch method then we
 
290
            # don't have enough information to provide a list
 
291
            # of methods
 
292
            elif not hasattr(self.instance, '_dispatch'):
 
293
                methods = remove_duplicates(
 
294
                        methods + list_public_methods(self.instance)
 
295
                    )
 
296
        methods.sort()
 
297
        return methods
 
298
 
 
299
    def system_methodSignature(self, method_name):
 
300
        """system.methodSignature('add') => [double, int, int]
 
301
 
 
302
        Returns a list describing the signature of the method. In the
 
303
        above example, the add method takes two integers as arguments
 
304
        and returns a double result.
 
305
 
 
306
        This server does NOT support system.methodSignature."""
 
307
 
 
308
        # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
 
309
 
 
310
        return 'signatures not supported'
 
311
 
 
312
    def system_methodHelp(self, method_name):
 
313
        """system.methodHelp('add') => "Adds two integers together"
 
314
 
 
315
        Returns a string containing documentation for the specified method."""
 
316
 
 
317
        method = None
 
318
        if method_name in self.funcs:
 
319
            method = self.funcs[method_name]
 
320
        elif self.instance is not None:
 
321
            # Instance can implement _methodHelp to return help for a method
 
322
            if hasattr(self.instance, '_methodHelp'):
 
323
                return self.instance._methodHelp(method_name)
 
324
            # if the instance has a _dispatch method then we
 
325
            # don't have enough information to provide help
 
326
            elif not hasattr(self.instance, '_dispatch'):
 
327
                try:
 
328
                    method = resolve_dotted_attribute(
 
329
                                self.instance,
 
330
                                method_name,
 
331
                                self.allow_dotted_names
 
332
                                )
 
333
                except AttributeError:
 
334
                    pass
 
335
 
 
336
        # Note that we aren't checking that the method actually
 
337
        # be a callable object of some kind
 
338
        if method is None:
 
339
            return ""
 
340
        else:
 
341
            import pydoc
 
342
            return pydoc.getdoc(method)
 
343
 
 
344
    def system_multicall(self, call_list):
 
345
        """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
 
346
[[4], ...]
 
347
 
 
348
        Allows the caller to package multiple XML-RPC calls into a single
 
349
        request.
 
350
 
 
351
        See http://www.xmlrpc.com/discuss/msgReader$1208
 
352
        """
 
353
 
 
354
        results = []
 
355
        for call in call_list:
 
356
            method_name = call['methodName']
 
357
            params = call['params']
 
358
 
 
359
            try:
 
360
                # XXX A marshalling error in any response will fail the entire
 
361
                # multicall. If someone cares they should fix this.
 
362
                results.append([self._dispatch(method_name, params)])
 
363
            except Fault, fault:
 
364
                results.append(
 
365
                    {'faultCode' : fault.faultCode,
 
366
                     'faultString' : fault.faultString}
 
367
                    )
 
368
            except:
 
369
                exc_type, exc_value, exc_tb = sys.exc_info()
 
370
                results.append(
 
371
                    {'faultCode' : 1,
 
372
                     'faultString' : "%s:%s" % (exc_type, exc_value)}
 
373
                    )
 
374
        return results
 
375
 
 
376
    def _dispatch(self, method, params):
 
377
        """Dispatches the XML-RPC method.
 
378
 
 
379
        XML-RPC calls are forwarded to a registered function that
 
380
        matches the called XML-RPC method name. If no such function
 
381
        exists then the call is forwarded to the registered instance,
 
382
        if available.
 
383
 
 
384
        If the registered instance has a _dispatch method then that
 
385
        method will be called with the name of the XML-RPC method and
 
386
        its parameters as a tuple
 
387
        e.g. instance._dispatch('add',(2,3))
 
388
 
 
389
        If the registered instance does not have a _dispatch method
 
390
        then the instance will be searched to find a matching method
 
391
        and, if found, will be called.
 
392
 
 
393
        Methods beginning with an '_' are considered private and will
 
394
        not be called.
 
395
        """
 
396
 
 
397
        func = None
 
398
        try:
 
399
            # check to see if a matching function has been registered
 
400
            func = self.funcs[method]
 
401
        except KeyError:
 
402
            if self.instance is not None:
 
403
                # check for a _dispatch method
 
404
                if hasattr(self.instance, '_dispatch'):
 
405
                    return self.instance._dispatch(method, params)
 
406
                else:
 
407
                    # call instance method directly
 
408
                    try:
 
409
                        func = resolve_dotted_attribute(
 
410
                            self.instance,
 
411
                            method,
 
412
                            self.allow_dotted_names
 
413
                            )
 
414
                    except AttributeError:
 
415
                        pass
 
416
 
 
417
        if func is not None:
 
418
            return func(*params)
 
419
        else:
 
420
            raise Exception('method "%s" is not supported' % method)
 
421
 
 
422
class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
 
423
    """Simple XML-RPC request handler class.
 
424
 
 
425
    Handles all HTTP POST requests and attempts to decode them as
 
426
    XML-RPC requests.
 
427
    """
 
428
 
 
429
    # Class attribute listing the accessible path components;
 
430
    # paths not on this list will result in a 404 error.
 
431
    rpc_paths = ('/', '/RPC2')
 
432
 
 
433
    def is_rpc_path_valid(self):
 
434
        if self.rpc_paths:
 
435
            return self.path in self.rpc_paths
 
436
        else:
 
437
            # If .rpc_paths is empty, just assume all paths are legal
 
438
            return True
 
439
 
 
440
    def do_POST(self):
 
441
        """Handles the HTTP POST request.
 
442
 
 
443
        Attempts to interpret all HTTP POST requests as XML-RPC calls,
 
444
        which are forwarded to the server's _dispatch method for handling.
 
445
        """
 
446
 
 
447
        # Check that the path is legal
 
448
        if not self.is_rpc_path_valid():
 
449
            self.report_404()
 
450
            return
 
451
 
 
452
        try:
 
453
            # Get arguments by reading body of request.
 
454
            # We read this in chunks to avoid straining
 
455
            # socket.read(); around the 10 or 15Mb mark, some platforms
 
456
            # begin to have problems (bug #792570).
 
457
            max_chunk_size = 10*1024*1024
 
458
            size_remaining = int(self.headers["content-length"])
 
459
            L = []
 
460
            while size_remaining:
 
461
                chunk_size = min(size_remaining, max_chunk_size)
 
462
                L.append(self.rfile.read(chunk_size))
 
463
                size_remaining -= len(L[-1])
 
464
            data = ''.join(L)
 
465
 
 
466
            # In previous versions of SimpleXMLRPCServer, _dispatch
 
467
            # could be overridden in this class, instead of in
 
468
            # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
 
469
            # check to see if a subclass implements _dispatch and dispatch
 
470
            # using that method if present.
 
471
            response = self.server._marshaled_dispatch(
 
472
                    data, getattr(self, '_dispatch', None)
 
473
                )
 
474
        except Exception, e: # This should only happen if the module is buggy
 
475
            # internal error, report as HTTP server error
 
476
            self.send_response(500)
 
477
 
 
478
            # Send information about the exception if requested
 
479
            if hasattr(self.server, '_send_traceback_header') and \
 
480
                    self.server._send_traceback_header:
 
481
                self.send_header("X-exception", str(e))
 
482
                self.send_header("X-traceback", traceback.format_exc())
 
483
 
 
484
            self.end_headers()
 
485
        else:
 
486
            # got a valid XML RPC response
 
487
            self.send_response(200)
 
488
            self.send_header("Content-type", "text/xml")
 
489
            self.send_header("Content-length", str(len(response)))
 
490
            self.end_headers()
 
491
            self.wfile.write(response)
 
492
 
 
493
            # shut down the connection
 
494
            self.wfile.flush()
 
495
            self.connection.shutdown(1)
 
496
 
 
497
    def report_404 (self):
 
498
            # Report a 404 error
 
499
        self.send_response(404)
 
500
        response = 'No such page'
 
501
        self.send_header("Content-type", "text/plain")
 
502
        self.send_header("Content-length", str(len(response)))
 
503
        self.end_headers()
 
504
        self.wfile.write(response)
 
505
        # shut down the connection
 
506
        self.wfile.flush()
 
507
        self.connection.shutdown(1)
 
508
 
 
509
    def log_request(self, code='-', size='-'):
 
510
        """Selectively log an accepted request."""
 
511
 
 
512
        if self.server.logRequests:
 
513
            BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
 
514
 
 
515
class SimpleXMLRPCServer(SocketServer.TCPServer,
 
516
                         SimpleXMLRPCDispatcher):
 
517
    """Simple XML-RPC server.
 
518
 
 
519
    Simple XML-RPC server that allows functions and a single instance
 
520
    to be installed to handle requests. The default implementation
 
521
    attempts to dispatch XML-RPC calls to the functions or instance
 
522
    installed in the server. Override the _dispatch method inhereted
 
523
    from SimpleXMLRPCDispatcher to change this behavior.
 
524
    """
 
525
 
 
526
    allow_reuse_address = True
 
527
 
 
528
    # Warning: this is for debugging purposes only! Never set this to True in
 
529
    # production code, as will be sending out sensitive information (exception
 
530
    # and stack trace details) when exceptions are raised inside
 
531
    # SimpleXMLRPCRequestHandler.do_POST
 
532
    _send_traceback_header = False
 
533
 
 
534
    def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
 
535
                 logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
 
536
        self.logRequests = logRequests
 
537
 
 
538
        SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
 
539
        SocketServer.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
 
540
 
 
541
        # [Bug #1222790] If possible, set close-on-exec flag; if a
 
542
        # method spawns a subprocess, the subprocess shouldn't have
 
543
        # the listening socket open.
 
544
        if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
 
545
            flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
 
546
            flags |= fcntl.FD_CLOEXEC
 
547
            fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
 
548
 
 
549
class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
 
550
    """Simple handler for XML-RPC data passed through CGI."""
 
551
 
 
552
    def __init__(self, allow_none=False, encoding=None):
 
553
        SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
 
554
 
 
555
    def handle_xmlrpc(self, request_text):
 
556
        """Handle a single XML-RPC request"""
 
557
 
 
558
        response = self._marshaled_dispatch(request_text)
 
559
 
 
560
        print 'Content-Type: text/xml'
 
561
        print 'Content-Length: %d' % len(response)
 
562
        print
 
563
        sys.stdout.write(response)
 
564
 
 
565
    def handle_get(self):
 
566
        """Handle a single HTTP GET request.
 
567
 
 
568
        Default implementation indicates an error because
 
569
        XML-RPC uses the POST method.
 
570
        """
 
571
 
 
572
        code = 400
 
573
        message, explain = \
 
574
                 BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
 
575
 
 
576
        response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
 
577
            {
 
578
             'code' : code,
 
579
             'message' : message,
 
580
             'explain' : explain
 
581
            }
 
582
        print 'Status: %d %s' % (code, message)
 
583
        print 'Content-Type: text/html'
 
584
        print 'Content-Length: %d' % len(response)
 
585
        print
 
586
        sys.stdout.write(response)
 
587
 
 
588
    def handle_request(self, request_text = None):
 
589
        """Handle a single XML-RPC request passed through a CGI post method.
 
590
 
 
591
        If no XML data is given then it is read from stdin. The resulting
 
592
        XML-RPC response is printed to stdout along with the correct HTTP
 
593
        headers.
 
594
        """
 
595
 
 
596
        if request_text is None and \
 
597
            os.environ.get('REQUEST_METHOD', None) == 'GET':
 
598
            self.handle_get()
 
599
        else:
 
600
            # POST data is normally available through stdin
 
601
            if request_text is None:
 
602
                request_text = sys.stdin.read()
 
603
 
 
604
            self.handle_xmlrpc(request_text)
 
605
 
 
606
if __name__ == '__main__':
 
607
    print 'Running XML-RPC server on port 8000'
 
608
    server = SimpleXMLRPCServer(("localhost", 8000))
 
609
    server.register_function(pow)
 
610
    server.register_function(lambda x,y: x+y, 'add')
 
611
    server.serve_forever()