ยปCore Development>Code coverage>Lib/xmlrpc/server.py

Python code coverage for Lib/xmlrpc/server.py

#countcontent
1n/ar"""XML-RPC Servers.
2n/a
3n/aThis module can be used to create simple XML-RPC servers
4n/aby creating a server and either installing functions, a
5n/aclass instance, or by extending the SimpleXMLRPCServer
6n/aclass.
7n/a
8n/aIt can also be used to handle XML-RPC requests in a CGI
9n/aenvironment using CGIXMLRPCRequestHandler.
10n/a
11n/aThe Doc* classes can be used to create XML-RPC servers that
12n/aserve pydoc-style documentation in response to HTTP
13n/aGET requests. This documentation is dynamically generated
14n/abased on the functions and methods registered with the
15n/aserver.
16n/a
17n/aA list of possible usage patterns follows:
18n/a
19n/a1. Install functions:
20n/a
21n/aserver = SimpleXMLRPCServer(("localhost", 8000))
22n/aserver.register_function(pow)
23n/aserver.register_function(lambda x,y: x+y, 'add')
24n/aserver.serve_forever()
25n/a
26n/a2. Install an instance:
27n/a
28n/aclass MyFuncs:
29n/a def __init__(self):
30n/a # make all of the sys functions available through sys.func_name
31n/a import sys
32n/a self.sys = sys
33n/a def _listMethods(self):
34n/a # implement this method so that system.listMethods
35n/a # knows to advertise the sys methods
36n/a return list_public_methods(self) + \
37n/a ['sys.' + method for method in list_public_methods(self.sys)]
38n/a def pow(self, x, y): return pow(x, y)
39n/a def add(self, x, y) : return x + y
40n/a
41n/aserver = SimpleXMLRPCServer(("localhost", 8000))
42n/aserver.register_introspection_functions()
43n/aserver.register_instance(MyFuncs())
44n/aserver.serve_forever()
45n/a
46n/a3. Install an instance with custom dispatch method:
47n/a
48n/aclass Math:
49n/a def _listMethods(self):
50n/a # this method must be present for system.listMethods
51n/a # to work
52n/a return ['add', 'pow']
53n/a def _methodHelp(self, method):
54n/a # this method must be present for system.methodHelp
55n/a # to work
56n/a if method == 'add':
57n/a return "add(2,3) => 5"
58n/a elif method == 'pow':
59n/a return "pow(x, y[, z]) => number"
60n/a else:
61n/a # By convention, return empty
62n/a # string if no help is available
63n/a return ""
64n/a def _dispatch(self, method, params):
65n/a if method == 'pow':
66n/a return pow(*params)
67n/a elif method == 'add':
68n/a return params[0] + params[1]
69n/a else:
70n/a raise ValueError('bad method')
71n/a
72n/aserver = SimpleXMLRPCServer(("localhost", 8000))
73n/aserver.register_introspection_functions()
74n/aserver.register_instance(Math())
75n/aserver.serve_forever()
76n/a
77n/a4. Subclass SimpleXMLRPCServer:
78n/a
79n/aclass MathServer(SimpleXMLRPCServer):
80n/a def _dispatch(self, method, params):
81n/a try:
82n/a # We are forcing the 'export_' prefix on methods that are
83n/a # callable through XML-RPC to prevent potential security
84n/a # problems
85n/a func = getattr(self, 'export_' + method)
86n/a except AttributeError:
87n/a raise Exception('method "%s" is not supported' % method)
88n/a else:
89n/a return func(*params)
90n/a
91n/a def export_add(self, x, y):
92n/a return x + y
93n/a
94n/aserver = MathServer(("localhost", 8000))
95n/aserver.serve_forever()
96n/a
97n/a5. CGI script:
98n/a
99n/aserver = CGIXMLRPCRequestHandler()
100n/aserver.register_function(pow)
101n/aserver.handle_request()
102n/a"""
103n/a
104n/a# Written by Brian Quinlan (brian@sweetapp.com).
105n/a# Based on code written by Fredrik Lundh.
106n/a
107n/afrom xmlrpc.client import Fault, dumps, loads, gzip_encode, gzip_decode
108n/afrom http.server import BaseHTTPRequestHandler
109n/aimport http.server
110n/aimport socketserver
111n/aimport sys
112n/aimport os
113n/aimport re
114n/aimport pydoc
115n/aimport inspect
116n/aimport traceback
117n/atry:
118n/a import fcntl
119n/aexcept ImportError:
120n/a fcntl = None
121n/a
122n/adef resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
123n/a """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
124n/a
125n/a Resolves a dotted attribute name to an object. Raises
126n/a an AttributeError if any attribute in the chain starts with a '_'.
127n/a
128n/a If the optional allow_dotted_names argument is false, dots are not
129n/a supported and this function operates similar to getattr(obj, attr).
130n/a """
131n/a
132n/a if allow_dotted_names:
133n/a attrs = attr.split('.')
134n/a else:
135n/a attrs = [attr]
136n/a
137n/a for i in attrs:
138n/a if i.startswith('_'):
139n/a raise AttributeError(
140n/a 'attempt to access private attribute "%s"' % i
141n/a )
142n/a else:
143n/a obj = getattr(obj,i)
144n/a return obj
145n/a
146n/adef list_public_methods(obj):
147n/a """Returns a list of attribute strings, found in the specified
148n/a object, which represent callable attributes"""
149n/a
150n/a return [member for member in dir(obj)
151n/a if not member.startswith('_') and
152n/a callable(getattr(obj, member))]
153n/a
154n/aclass SimpleXMLRPCDispatcher:
155n/a """Mix-in class that dispatches XML-RPC requests.
156n/a
157n/a This class is used to register XML-RPC method handlers
158n/a and then to dispatch them. This class doesn't need to be
159n/a instanced directly when used by SimpleXMLRPCServer but it
160n/a can be instanced when used by the MultiPathXMLRPCServer
161n/a """
162n/a
163n/a def __init__(self, allow_none=False, encoding=None,
164n/a use_builtin_types=False):
165n/a self.funcs = {}
166n/a self.instance = None
167n/a self.allow_none = allow_none
168n/a self.encoding = encoding or 'utf-8'
169n/a self.use_builtin_types = use_builtin_types
170n/a
171n/a def register_instance(self, instance, allow_dotted_names=False):
172n/a """Registers an instance to respond to XML-RPC requests.
173n/a
174n/a Only one instance can be installed at a time.
175n/a
176n/a If the registered instance has a _dispatch method then that
177n/a method will be called with the name of the XML-RPC method and
178n/a its parameters as a tuple
179n/a e.g. instance._dispatch('add',(2,3))
180n/a
181n/a If the registered instance does not have a _dispatch method
182n/a then the instance will be searched to find a matching method
183n/a and, if found, will be called. Methods beginning with an '_'
184n/a are considered private and will not be called by
185n/a SimpleXMLRPCServer.
186n/a
187n/a If a registered function matches an XML-RPC request, then it
188n/a will be called instead of the registered instance.
189n/a
190n/a If the optional allow_dotted_names argument is true and the
191n/a instance does not have a _dispatch method, method names
192n/a containing dots are supported and resolved, as long as none of
193n/a the name segments start with an '_'.
194n/a
195n/a *** SECURITY WARNING: ***
196n/a
197n/a Enabling the allow_dotted_names options allows intruders
198n/a to access your module's global variables and may allow
199n/a intruders to execute arbitrary code on your machine. Only
200n/a use this option on a secure, closed network.
201n/a
202n/a """
203n/a
204n/a self.instance = instance
205n/a self.allow_dotted_names = allow_dotted_names
206n/a
207n/a def register_function(self, function, name=None):
208n/a """Registers a function to respond to XML-RPC requests.
209n/a
210n/a The optional name argument can be used to set a Unicode name
211n/a for the function.
212n/a """
213n/a
214n/a if name is None:
215n/a name = function.__name__
216n/a self.funcs[name] = function
217n/a
218n/a def register_introspection_functions(self):
219n/a """Registers the XML-RPC introspection methods in the system
220n/a namespace.
221n/a
222n/a see http://xmlrpc.usefulinc.com/doc/reserved.html
223n/a """
224n/a
225n/a self.funcs.update({'system.listMethods' : self.system_listMethods,
226n/a 'system.methodSignature' : self.system_methodSignature,
227n/a 'system.methodHelp' : self.system_methodHelp})
228n/a
229n/a def register_multicall_functions(self):
230n/a """Registers the XML-RPC multicall method in the system
231n/a namespace.
232n/a
233n/a see http://www.xmlrpc.com/discuss/msgReader$1208"""
234n/a
235n/a self.funcs.update({'system.multicall' : self.system_multicall})
236n/a
237n/a def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
238n/a """Dispatches an XML-RPC method from marshalled (XML) data.
239n/a
240n/a XML-RPC methods are dispatched from the marshalled (XML) data
241n/a using the _dispatch method and the result is returned as
242n/a marshalled data. For backwards compatibility, a dispatch
243n/a function can be provided as an argument (see comment in
244n/a SimpleXMLRPCRequestHandler.do_POST) but overriding the
245n/a existing method through subclassing is the preferred means
246n/a of changing method dispatch behavior.
247n/a """
248n/a
249n/a try:
250n/a params, method = loads(data, use_builtin_types=self.use_builtin_types)
251n/a
252n/a # generate response
253n/a if dispatch_method is not None:
254n/a response = dispatch_method(method, params)
255n/a else:
256n/a response = self._dispatch(method, params)
257n/a # wrap response in a singleton tuple
258n/a response = (response,)
259n/a response = dumps(response, methodresponse=1,
260n/a allow_none=self.allow_none, encoding=self.encoding)
261n/a except Fault as fault:
262n/a response = dumps(fault, allow_none=self.allow_none,
263n/a encoding=self.encoding)
264n/a except:
265n/a # report exception back to server
266n/a exc_type, exc_value, exc_tb = sys.exc_info()
267n/a response = dumps(
268n/a Fault(1, "%s:%s" % (exc_type, exc_value)),
269n/a encoding=self.encoding, allow_none=self.allow_none,
270n/a )
271n/a
272n/a return response.encode(self.encoding, 'xmlcharrefreplace')
273n/a
274n/a def system_listMethods(self):
275n/a """system.listMethods() => ['add', 'subtract', 'multiple']
276n/a
277n/a Returns a list of the methods supported by the server."""
278n/a
279n/a methods = set(self.funcs.keys())
280n/a if self.instance is not None:
281n/a # Instance can implement _listMethod to return a list of
282n/a # methods
283n/a if hasattr(self.instance, '_listMethods'):
284n/a methods |= set(self.instance._listMethods())
285n/a # if the instance has a _dispatch method then we
286n/a # don't have enough information to provide a list
287n/a # of methods
288n/a elif not hasattr(self.instance, '_dispatch'):
289n/a methods |= set(list_public_methods(self.instance))
290n/a return sorted(methods)
291n/a
292n/a def system_methodSignature(self, method_name):
293n/a """system.methodSignature('add') => [double, int, int]
294n/a
295n/a Returns a list describing the signature of the method. In the
296n/a above example, the add method takes two integers as arguments
297n/a and returns a double result.
298n/a
299n/a This server does NOT support system.methodSignature."""
300n/a
301n/a # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
302n/a
303n/a return 'signatures not supported'
304n/a
305n/a def system_methodHelp(self, method_name):
306n/a """system.methodHelp('add') => "Adds two integers together"
307n/a
308n/a Returns a string containing documentation for the specified method."""
309n/a
310n/a method = None
311n/a if method_name in self.funcs:
312n/a method = self.funcs[method_name]
313n/a elif self.instance is not None:
314n/a # Instance can implement _methodHelp to return help for a method
315n/a if hasattr(self.instance, '_methodHelp'):
316n/a return self.instance._methodHelp(method_name)
317n/a # if the instance has a _dispatch method then we
318n/a # don't have enough information to provide help
319n/a elif not hasattr(self.instance, '_dispatch'):
320n/a try:
321n/a method = resolve_dotted_attribute(
322n/a self.instance,
323n/a method_name,
324n/a self.allow_dotted_names
325n/a )
326n/a except AttributeError:
327n/a pass
328n/a
329n/a # Note that we aren't checking that the method actually
330n/a # be a callable object of some kind
331n/a if method is None:
332n/a return ""
333n/a else:
334n/a return pydoc.getdoc(method)
335n/a
336n/a def system_multicall(self, call_list):
337n/a """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
338n/a[[4], ...]
339n/a
340n/a Allows the caller to package multiple XML-RPC calls into a single
341n/a request.
342n/a
343n/a See http://www.xmlrpc.com/discuss/msgReader$1208
344n/a """
345n/a
346n/a results = []
347n/a for call in call_list:
348n/a method_name = call['methodName']
349n/a params = call['params']
350n/a
351n/a try:
352n/a # XXX A marshalling error in any response will fail the entire
353n/a # multicall. If someone cares they should fix this.
354n/a results.append([self._dispatch(method_name, params)])
355n/a except Fault as fault:
356n/a results.append(
357n/a {'faultCode' : fault.faultCode,
358n/a 'faultString' : fault.faultString}
359n/a )
360n/a except:
361n/a exc_type, exc_value, exc_tb = sys.exc_info()
362n/a results.append(
363n/a {'faultCode' : 1,
364n/a 'faultString' : "%s:%s" % (exc_type, exc_value)}
365n/a )
366n/a return results
367n/a
368n/a def _dispatch(self, method, params):
369n/a """Dispatches the XML-RPC method.
370n/a
371n/a XML-RPC calls are forwarded to a registered function that
372n/a matches the called XML-RPC method name. If no such function
373n/a exists then the call is forwarded to the registered instance,
374n/a if available.
375n/a
376n/a If the registered instance has a _dispatch method then that
377n/a method will be called with the name of the XML-RPC method and
378n/a its parameters as a tuple
379n/a e.g. instance._dispatch('add',(2,3))
380n/a
381n/a If the registered instance does not have a _dispatch method
382n/a then the instance will be searched to find a matching method
383n/a and, if found, will be called.
384n/a
385n/a Methods beginning with an '_' are considered private and will
386n/a not be called.
387n/a """
388n/a
389n/a func = None
390n/a try:
391n/a # check to see if a matching function has been registered
392n/a func = self.funcs[method]
393n/a except KeyError:
394n/a if self.instance is not None:
395n/a # check for a _dispatch method
396n/a if hasattr(self.instance, '_dispatch'):
397n/a return self.instance._dispatch(method, params)
398n/a else:
399n/a # call instance method directly
400n/a try:
401n/a func = resolve_dotted_attribute(
402n/a self.instance,
403n/a method,
404n/a self.allow_dotted_names
405n/a )
406n/a except AttributeError:
407n/a pass
408n/a
409n/a if func is not None:
410n/a return func(*params)
411n/a else:
412n/a raise Exception('method "%s" is not supported' % method)
413n/a
414n/aclass SimpleXMLRPCRequestHandler(BaseHTTPRequestHandler):
415n/a """Simple XML-RPC request handler class.
416n/a
417n/a Handles all HTTP POST requests and attempts to decode them as
418n/a XML-RPC requests.
419n/a """
420n/a
421n/a # Class attribute listing the accessible path components;
422n/a # paths not on this list will result in a 404 error.
423n/a rpc_paths = ('/', '/RPC2')
424n/a
425n/a #if not None, encode responses larger than this, if possible
426n/a encode_threshold = 1400 #a common MTU
427n/a
428n/a #Override form StreamRequestHandler: full buffering of output
429n/a #and no Nagle.
430n/a wbufsize = -1
431n/a disable_nagle_algorithm = True
432n/a
433n/a # a re to match a gzip Accept-Encoding
434n/a aepattern = re.compile(r"""
435n/a \s* ([^\s;]+) \s* #content-coding
436n/a (;\s* q \s*=\s* ([0-9\.]+))? #q
437n/a """, re.VERBOSE | re.IGNORECASE)
438n/a
439n/a def accept_encodings(self):
440n/a r = {}
441n/a ae = self.headers.get("Accept-Encoding", "")
442n/a for e in ae.split(","):
443n/a match = self.aepattern.match(e)
444n/a if match:
445n/a v = match.group(3)
446n/a v = float(v) if v else 1.0
447n/a r[match.group(1)] = v
448n/a return r
449n/a
450n/a def is_rpc_path_valid(self):
451n/a if self.rpc_paths:
452n/a return self.path in self.rpc_paths
453n/a else:
454n/a # If .rpc_paths is empty, just assume all paths are legal
455n/a return True
456n/a
457n/a def do_POST(self):
458n/a """Handles the HTTP POST request.
459n/a
460n/a Attempts to interpret all HTTP POST requests as XML-RPC calls,
461n/a which are forwarded to the server's _dispatch method for handling.
462n/a """
463n/a
464n/a # Check that the path is legal
465n/a if not self.is_rpc_path_valid():
466n/a self.report_404()
467n/a return
468n/a
469n/a try:
470n/a # Get arguments by reading body of request.
471n/a # We read this in chunks to avoid straining
472n/a # socket.read(); around the 10 or 15Mb mark, some platforms
473n/a # begin to have problems (bug #792570).
474n/a max_chunk_size = 10*1024*1024
475n/a size_remaining = int(self.headers["content-length"])
476n/a L = []
477n/a while size_remaining:
478n/a chunk_size = min(size_remaining, max_chunk_size)
479n/a chunk = self.rfile.read(chunk_size)
480n/a if not chunk:
481n/a break
482n/a L.append(chunk)
483n/a size_remaining -= len(L[-1])
484n/a data = b''.join(L)
485n/a
486n/a data = self.decode_request_content(data)
487n/a if data is None:
488n/a return #response has been sent
489n/a
490n/a # In previous versions of SimpleXMLRPCServer, _dispatch
491n/a # could be overridden in this class, instead of in
492n/a # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
493n/a # check to see if a subclass implements _dispatch and dispatch
494n/a # using that method if present.
495n/a response = self.server._marshaled_dispatch(
496n/a data, getattr(self, '_dispatch', None), self.path
497n/a )
498n/a except Exception as e: # This should only happen if the module is buggy
499n/a # internal error, report as HTTP server error
500n/a self.send_response(500)
501n/a
502n/a # Send information about the exception if requested
503n/a if hasattr(self.server, '_send_traceback_header') and \
504n/a self.server._send_traceback_header:
505n/a self.send_header("X-exception", str(e))
506n/a trace = traceback.format_exc()
507n/a trace = str(trace.encode('ASCII', 'backslashreplace'), 'ASCII')
508n/a self.send_header("X-traceback", trace)
509n/a
510n/a self.send_header("Content-length", "0")
511n/a self.end_headers()
512n/a else:
513n/a self.send_response(200)
514n/a self.send_header("Content-type", "text/xml")
515n/a if self.encode_threshold is not None:
516n/a if len(response) > self.encode_threshold:
517n/a q = self.accept_encodings().get("gzip", 0)
518n/a if q:
519n/a try:
520n/a response = gzip_encode(response)
521n/a self.send_header("Content-Encoding", "gzip")
522n/a except NotImplementedError:
523n/a pass
524n/a self.send_header("Content-length", str(len(response)))
525n/a self.end_headers()
526n/a self.wfile.write(response)
527n/a
528n/a def decode_request_content(self, data):
529n/a #support gzip encoding of request
530n/a encoding = self.headers.get("content-encoding", "identity").lower()
531n/a if encoding == "identity":
532n/a return data
533n/a if encoding == "gzip":
534n/a try:
535n/a return gzip_decode(data)
536n/a except NotImplementedError:
537n/a self.send_response(501, "encoding %r not supported" % encoding)
538n/a except ValueError:
539n/a self.send_response(400, "error decoding gzip content")
540n/a else:
541n/a self.send_response(501, "encoding %r not supported" % encoding)
542n/a self.send_header("Content-length", "0")
543n/a self.end_headers()
544n/a
545n/a def report_404 (self):
546n/a # Report a 404 error
547n/a self.send_response(404)
548n/a response = b'No such page'
549n/a self.send_header("Content-type", "text/plain")
550n/a self.send_header("Content-length", str(len(response)))
551n/a self.end_headers()
552n/a self.wfile.write(response)
553n/a
554n/a def log_request(self, code='-', size='-'):
555n/a """Selectively log an accepted request."""
556n/a
557n/a if self.server.logRequests:
558n/a BaseHTTPRequestHandler.log_request(self, code, size)
559n/a
560n/aclass SimpleXMLRPCServer(socketserver.TCPServer,
561n/a SimpleXMLRPCDispatcher):
562n/a """Simple XML-RPC server.
563n/a
564n/a Simple XML-RPC server that allows functions and a single instance
565n/a to be installed to handle requests. The default implementation
566n/a attempts to dispatch XML-RPC calls to the functions or instance
567n/a installed in the server. Override the _dispatch method inherited
568n/a from SimpleXMLRPCDispatcher to change this behavior.
569n/a """
570n/a
571n/a allow_reuse_address = True
572n/a
573n/a # Warning: this is for debugging purposes only! Never set this to True in
574n/a # production code, as will be sending out sensitive information (exception
575n/a # and stack trace details) when exceptions are raised inside
576n/a # SimpleXMLRPCRequestHandler.do_POST
577n/a _send_traceback_header = False
578n/a
579n/a def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
580n/a logRequests=True, allow_none=False, encoding=None,
581n/a bind_and_activate=True, use_builtin_types=False):
582n/a self.logRequests = logRequests
583n/a
584n/a SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding, use_builtin_types)
585n/a socketserver.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
586n/a
587n/a
588n/aclass MultiPathXMLRPCServer(SimpleXMLRPCServer):
589n/a """Multipath XML-RPC Server
590n/a This specialization of SimpleXMLRPCServer allows the user to create
591n/a multiple Dispatcher instances and assign them to different
592n/a HTTP request paths. This makes it possible to run two or more
593n/a 'virtual XML-RPC servers' at the same port.
594n/a Make sure that the requestHandler accepts the paths in question.
595n/a """
596n/a def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
597n/a logRequests=True, allow_none=False, encoding=None,
598n/a bind_and_activate=True, use_builtin_types=False):
599n/a
600n/a SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests, allow_none,
601n/a encoding, bind_and_activate, use_builtin_types)
602n/a self.dispatchers = {}
603n/a self.allow_none = allow_none
604n/a self.encoding = encoding or 'utf-8'
605n/a
606n/a def add_dispatcher(self, path, dispatcher):
607n/a self.dispatchers[path] = dispatcher
608n/a return dispatcher
609n/a
610n/a def get_dispatcher(self, path):
611n/a return self.dispatchers[path]
612n/a
613n/a def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
614n/a try:
615n/a response = self.dispatchers[path]._marshaled_dispatch(
616n/a data, dispatch_method, path)
617n/a except:
618n/a # report low level exception back to server
619n/a # (each dispatcher should have handled their own
620n/a # exceptions)
621n/a exc_type, exc_value = sys.exc_info()[:2]
622n/a response = dumps(
623n/a Fault(1, "%s:%s" % (exc_type, exc_value)),
624n/a encoding=self.encoding, allow_none=self.allow_none)
625n/a response = response.encode(self.encoding, 'xmlcharrefreplace')
626n/a return response
627n/a
628n/aclass CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
629n/a """Simple handler for XML-RPC data passed through CGI."""
630n/a
631n/a def __init__(self, allow_none=False, encoding=None, use_builtin_types=False):
632n/a SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding, use_builtin_types)
633n/a
634n/a def handle_xmlrpc(self, request_text):
635n/a """Handle a single XML-RPC request"""
636n/a
637n/a response = self._marshaled_dispatch(request_text)
638n/a
639n/a print('Content-Type: text/xml')
640n/a print('Content-Length: %d' % len(response))
641n/a print()
642n/a sys.stdout.flush()
643n/a sys.stdout.buffer.write(response)
644n/a sys.stdout.buffer.flush()
645n/a
646n/a def handle_get(self):
647n/a """Handle a single HTTP GET request.
648n/a
649n/a Default implementation indicates an error because
650n/a XML-RPC uses the POST method.
651n/a """
652n/a
653n/a code = 400
654n/a message, explain = BaseHTTPRequestHandler.responses[code]
655n/a
656n/a response = http.server.DEFAULT_ERROR_MESSAGE % \
657n/a {
658n/a 'code' : code,
659n/a 'message' : message,
660n/a 'explain' : explain
661n/a }
662n/a response = response.encode('utf-8')
663n/a print('Status: %d %s' % (code, message))
664n/a print('Content-Type: %s' % http.server.DEFAULT_ERROR_CONTENT_TYPE)
665n/a print('Content-Length: %d' % len(response))
666n/a print()
667n/a sys.stdout.flush()
668n/a sys.stdout.buffer.write(response)
669n/a sys.stdout.buffer.flush()
670n/a
671n/a def handle_request(self, request_text=None):
672n/a """Handle a single XML-RPC request passed through a CGI post method.
673n/a
674n/a If no XML data is given then it is read from stdin. The resulting
675n/a XML-RPC response is printed to stdout along with the correct HTTP
676n/a headers.
677n/a """
678n/a
679n/a if request_text is None and \
680n/a os.environ.get('REQUEST_METHOD', None) == 'GET':
681n/a self.handle_get()
682n/a else:
683n/a # POST data is normally available through stdin
684n/a try:
685n/a length = int(os.environ.get('CONTENT_LENGTH', None))
686n/a except (ValueError, TypeError):
687n/a length = -1
688n/a if request_text is None:
689n/a request_text = sys.stdin.read(length)
690n/a
691n/a self.handle_xmlrpc(request_text)
692n/a
693n/a
694n/a# -----------------------------------------------------------------------------
695n/a# Self documenting XML-RPC Server.
696n/a
697n/aclass ServerHTMLDoc(pydoc.HTMLDoc):
698n/a """Class used to generate pydoc HTML document for a server"""
699n/a
700n/a def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
701n/a """Mark up some plain text, given a context of symbols to look for.
702n/a Each context dictionary maps object names to anchor names."""
703n/a escape = escape or self.escape
704n/a results = []
705n/a here = 0
706n/a
707n/a # XXX Note that this regular expression does not allow for the
708n/a # hyperlinking of arbitrary strings being used as method
709n/a # names. Only methods with names consisting of word characters
710n/a # and '.'s are hyperlinked.
711n/a pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
712n/a r'RFC[- ]?(\d+)|'
713n/a r'PEP[- ]?(\d+)|'
714n/a r'(self\.)?((?:\w|\.)+))\b')
715n/a while 1:
716n/a match = pattern.search(text, here)
717n/a if not match: break
718n/a start, end = match.span()
719n/a results.append(escape(text[here:start]))
720n/a
721n/a all, scheme, rfc, pep, selfdot, name = match.groups()
722n/a if scheme:
723n/a url = escape(all).replace('"', '"')
724n/a results.append('<a href="%s">%s</a>' % (url, url))
725n/a elif rfc:
726n/a url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
727n/a results.append('<a href="%s">%s</a>' % (url, escape(all)))
728n/a elif pep:
729n/a url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep)
730n/a results.append('<a href="%s">%s</a>' % (url, escape(all)))
731n/a elif text[end:end+1] == '(':
732n/a results.append(self.namelink(name, methods, funcs, classes))
733n/a elif selfdot:
734n/a results.append('self.<strong>%s</strong>' % name)
735n/a else:
736n/a results.append(self.namelink(name, classes))
737n/a here = end
738n/a results.append(escape(text[here:]))
739n/a return ''.join(results)
740n/a
741n/a def docroutine(self, object, name, mod=None,
742n/a funcs={}, classes={}, methods={}, cl=None):
743n/a """Produce HTML documentation for a function or method object."""
744n/a
745n/a anchor = (cl and cl.__name__ or '') + '-' + name
746n/a note = ''
747n/a
748n/a title = '<a name="%s"><strong>%s</strong></a>' % (
749n/a self.escape(anchor), self.escape(name))
750n/a
751n/a if inspect.ismethod(object):
752n/a args = inspect.getfullargspec(object)
753n/a # exclude the argument bound to the instance, it will be
754n/a # confusing to the non-Python user
755n/a argspec = inspect.formatargspec (
756n/a args.args[1:],
757n/a args.varargs,
758n/a args.varkw,
759n/a args.defaults,
760n/a annotations=args.annotations,
761n/a formatvalue=self.formatvalue
762n/a )
763n/a elif inspect.isfunction(object):
764n/a args = inspect.getfullargspec(object)
765n/a argspec = inspect.formatargspec(
766n/a args.args, args.varargs, args.varkw, args.defaults,
767n/a annotations=args.annotations,
768n/a formatvalue=self.formatvalue)
769n/a else:
770n/a argspec = '(...)'
771n/a
772n/a if isinstance(object, tuple):
773n/a argspec = object[0] or argspec
774n/a docstring = object[1] or ""
775n/a else:
776n/a docstring = pydoc.getdoc(object)
777n/a
778n/a decl = title + argspec + (note and self.grey(
779n/a '<font face="helvetica, arial">%s</font>' % note))
780n/a
781n/a doc = self.markup(
782n/a docstring, self.preformat, funcs, classes, methods)
783n/a doc = doc and '<dd><tt>%s</tt></dd>' % doc
784n/a return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
785n/a
786n/a def docserver(self, server_name, package_documentation, methods):
787n/a """Produce HTML documentation for an XML-RPC server."""
788n/a
789n/a fdict = {}
790n/a for key, value in methods.items():
791n/a fdict[key] = '#-' + key
792n/a fdict[value] = fdict[key]
793n/a
794n/a server_name = self.escape(server_name)
795n/a head = '<big><big><strong>%s</strong></big></big>' % server_name
796n/a result = self.heading(head, '#ffffff', '#7799ee')
797n/a
798n/a doc = self.markup(package_documentation, self.preformat, fdict)
799n/a doc = doc and '<tt>%s</tt>' % doc
800n/a result = result + '<p>%s</p>\n' % doc
801n/a
802n/a contents = []
803n/a method_items = sorted(methods.items())
804n/a for key, value in method_items:
805n/a contents.append(self.docroutine(value, key, funcs=fdict))
806n/a result = result + self.bigsection(
807n/a 'Methods', '#ffffff', '#eeaa77', ''.join(contents))
808n/a
809n/a return result
810n/a
811n/aclass XMLRPCDocGenerator:
812n/a """Generates documentation for an XML-RPC server.
813n/a
814n/a This class is designed as mix-in and should not
815n/a be constructed directly.
816n/a """
817n/a
818n/a def __init__(self):
819n/a # setup variables used for HTML documentation
820n/a self.server_name = 'XML-RPC Server Documentation'
821n/a self.server_documentation = \
822n/a "This server exports the following methods through the XML-RPC "\
823n/a "protocol."
824n/a self.server_title = 'XML-RPC Server Documentation'
825n/a
826n/a def set_server_title(self, server_title):
827n/a """Set the HTML title of the generated server documentation"""
828n/a
829n/a self.server_title = server_title
830n/a
831n/a def set_server_name(self, server_name):
832n/a """Set the name of the generated HTML server documentation"""
833n/a
834n/a self.server_name = server_name
835n/a
836n/a def set_server_documentation(self, server_documentation):
837n/a """Set the documentation string for the entire server."""
838n/a
839n/a self.server_documentation = server_documentation
840n/a
841n/a def generate_html_documentation(self):
842n/a """generate_html_documentation() => html documentation for the server
843n/a
844n/a Generates HTML documentation for the server using introspection for
845n/a installed functions and instances that do not implement the
846n/a _dispatch method. Alternatively, instances can choose to implement
847n/a the _get_method_argstring(method_name) method to provide the
848n/a argument string used in the documentation and the
849n/a _methodHelp(method_name) method to provide the help text used
850n/a in the documentation."""
851n/a
852n/a methods = {}
853n/a
854n/a for method_name in self.system_listMethods():
855n/a if method_name in self.funcs:
856n/a method = self.funcs[method_name]
857n/a elif self.instance is not None:
858n/a method_info = [None, None] # argspec, documentation
859n/a if hasattr(self.instance, '_get_method_argstring'):
860n/a method_info[0] = self.instance._get_method_argstring(method_name)
861n/a if hasattr(self.instance, '_methodHelp'):
862n/a method_info[1] = self.instance._methodHelp(method_name)
863n/a
864n/a method_info = tuple(method_info)
865n/a if method_info != (None, None):
866n/a method = method_info
867n/a elif not hasattr(self.instance, '_dispatch'):
868n/a try:
869n/a method = resolve_dotted_attribute(
870n/a self.instance,
871n/a method_name
872n/a )
873n/a except AttributeError:
874n/a method = method_info
875n/a else:
876n/a method = method_info
877n/a else:
878n/a assert 0, "Could not find method in self.functions and no "\
879n/a "instance installed"
880n/a
881n/a methods[method_name] = method
882n/a
883n/a documenter = ServerHTMLDoc()
884n/a documentation = documenter.docserver(
885n/a self.server_name,
886n/a self.server_documentation,
887n/a methods
888n/a )
889n/a
890n/a return documenter.page(self.server_title, documentation)
891n/a
892n/aclass DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
893n/a """XML-RPC and documentation request handler class.
894n/a
895n/a Handles all HTTP POST requests and attempts to decode them as
896n/a XML-RPC requests.
897n/a
898n/a Handles all HTTP GET requests and interprets them as requests
899n/a for documentation.
900n/a """
901n/a
902n/a def do_GET(self):
903n/a """Handles the HTTP GET request.
904n/a
905n/a Interpret all HTTP GET requests as requests for server
906n/a documentation.
907n/a """
908n/a # Check that the path is legal
909n/a if not self.is_rpc_path_valid():
910n/a self.report_404()
911n/a return
912n/a
913n/a response = self.server.generate_html_documentation().encode('utf-8')
914n/a self.send_response(200)
915n/a self.send_header("Content-type", "text/html")
916n/a self.send_header("Content-length", str(len(response)))
917n/a self.end_headers()
918n/a self.wfile.write(response)
919n/a
920n/aclass DocXMLRPCServer( SimpleXMLRPCServer,
921n/a XMLRPCDocGenerator):
922n/a """XML-RPC and HTML documentation server.
923n/a
924n/a Adds the ability to serve server documentation to the capabilities
925n/a of SimpleXMLRPCServer.
926n/a """
927n/a
928n/a def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler,
929n/a logRequests=True, allow_none=False, encoding=None,
930n/a bind_and_activate=True, use_builtin_types=False):
931n/a SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests,
932n/a allow_none, encoding, bind_and_activate,
933n/a use_builtin_types)
934n/a XMLRPCDocGenerator.__init__(self)
935n/a
936n/aclass DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler,
937n/a XMLRPCDocGenerator):
938n/a """Handler for XML-RPC data and documentation requests passed through
939n/a CGI"""
940n/a
941n/a def handle_get(self):
942n/a """Handles the HTTP GET request.
943n/a
944n/a Interpret all HTTP GET requests as requests for server
945n/a documentation.
946n/a """
947n/a
948n/a response = self.generate_html_documentation().encode('utf-8')
949n/a
950n/a print('Content-Type: text/html')
951n/a print('Content-Length: %d' % len(response))
952n/a print()
953n/a sys.stdout.flush()
954n/a sys.stdout.buffer.write(response)
955n/a sys.stdout.buffer.flush()
956n/a
957n/a def __init__(self):
958n/a CGIXMLRPCRequestHandler.__init__(self)
959n/a XMLRPCDocGenerator.__init__(self)
960n/a
961n/a
962n/aif __name__ == '__main__':
963n/a import datetime
964n/a
965n/a class ExampleService:
966n/a def getData(self):
967n/a return '42'
968n/a
969n/a class currentTime:
970n/a @staticmethod
971n/a def getCurrentTime():
972n/a return datetime.datetime.now()
973n/a
974n/a with SimpleXMLRPCServer(("localhost", 8000)) as server:
975n/a server.register_function(pow)
976n/a server.register_function(lambda x,y: x+y, 'add')
977n/a server.register_instance(ExampleService(), allow_dotted_names=True)
978n/a server.register_multicall_functions()
979n/a print('Serving XML-RPC on localhost port 8000')
980n/a print('It is advisable to run this example server within a secure, closed network.')
981n/a try:
982n/a server.serve_forever()
983n/a except KeyboardInterrupt:
984n/a print("\nKeyboard interrupt received, exiting.")
985n/a sys.exit(0)