+++ /dev/null
-"""Simple XML-RPC Server.\r
-\r
-This module can be used to create simple XML-RPC servers\r
-by creating a server and either installing functions, a\r
-class instance, or by extending the SimpleXMLRPCServer\r
-class.\r
-\r
-It can also be used to handle XML-RPC requests in a CGI\r
-environment using CGIXMLRPCRequestHandler.\r
-\r
-A list of possible usage patterns follows:\r
-\r
-1. Install functions:\r
-\r
-server = SimpleXMLRPCServer(("localhost", 8000))\r
-server.register_function(pow)\r
-server.register_function(lambda x,y: x+y, 'add')\r
-server.serve_forever()\r
-\r
-2. Install an instance:\r
-\r
-class MyFuncs:\r
- def __init__(self):\r
- # make all of the string functions available through\r
- # string.func_name\r
- import string\r
- self.string = string\r
- def _listMethods(self):\r
- # implement this method so that system.listMethods\r
- # knows to advertise the strings methods\r
- return list_public_methods(self) + \\r
- ['string.' + method for method in list_public_methods(self.string)]\r
- def pow(self, x, y): return pow(x, y)\r
- def add(self, x, y) : return x + y\r
-\r
-server = SimpleXMLRPCServer(("localhost", 8000))\r
-server.register_introspection_functions()\r
-server.register_instance(MyFuncs())\r
-server.serve_forever()\r
-\r
-3. Install an instance with custom dispatch method:\r
-\r
-class Math:\r
- def _listMethods(self):\r
- # this method must be present for system.listMethods\r
- # to work\r
- return ['add', 'pow']\r
- def _methodHelp(self, method):\r
- # this method must be present for system.methodHelp\r
- # to work\r
- if method == 'add':\r
- return "add(2,3) => 5"\r
- elif method == 'pow':\r
- return "pow(x, y[, z]) => number"\r
- else:\r
- # By convention, return empty\r
- # string if no help is available\r
- return ""\r
- def _dispatch(self, method, params):\r
- if method == 'pow':\r
- return pow(*params)\r
- elif method == 'add':\r
- return params[0] + params[1]\r
- else:\r
- raise 'bad method'\r
-\r
-server = SimpleXMLRPCServer(("localhost", 8000))\r
-server.register_introspection_functions()\r
-server.register_instance(Math())\r
-server.serve_forever()\r
-\r
-4. Subclass SimpleXMLRPCServer:\r
-\r
-class MathServer(SimpleXMLRPCServer):\r
- def _dispatch(self, method, params):\r
- try:\r
- # We are forcing the 'export_' prefix on methods that are\r
- # callable through XML-RPC to prevent potential security\r
- # problems\r
- func = getattr(self, 'export_' + method)\r
- except AttributeError:\r
- raise Exception('method "%s" is not supported' % method)\r
- else:\r
- return func(*params)\r
-\r
- def export_add(self, x, y):\r
- return x + y\r
-\r
-server = MathServer(("localhost", 8000))\r
-server.serve_forever()\r
-\r
-5. CGI script:\r
-\r
-server = CGIXMLRPCRequestHandler()\r
-server.register_function(pow)\r
-server.handle_request()\r
-"""\r
-\r
-# Written by Brian Quinlan (brian@sweetapp.com).\r
-# Based on code written by Fredrik Lundh.\r
-\r
-import xmlrpclib\r
-from xmlrpclib import Fault\r
-import SocketServer\r
-import BaseHTTPServer\r
-import sys\r
-import os\r
-import traceback\r
-import re\r
-try:\r
- import fcntl\r
-except ImportError:\r
- fcntl = None\r
-\r
-def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):\r
- """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d\r
-\r
- Resolves a dotted attribute name to an object. Raises\r
- an AttributeError if any attribute in the chain starts with a '_'.\r
-\r
- If the optional allow_dotted_names argument is false, dots are not\r
- supported and this function operates similar to getattr(obj, attr).\r
- """\r
-\r
- if allow_dotted_names:\r
- attrs = attr.split('.')\r
- else:\r
- attrs = [attr]\r
-\r
- for i in attrs:\r
- if i.startswith('_'):\r
- raise AttributeError(\r
- 'attempt to access private attribute "%s"' % i\r
- )\r
- else:\r
- obj = getattr(obj,i)\r
- return obj\r
-\r
-def list_public_methods(obj):\r
- """Returns a list of attribute strings, found in the specified\r
- object, which represent callable attributes"""\r
-\r
- return [member for member in dir(obj)\r
- if not member.startswith('_') and\r
- hasattr(getattr(obj, member), '__call__')]\r
-\r
-def remove_duplicates(lst):\r
- """remove_duplicates([2,2,2,1,3,3]) => [3,1,2]\r
-\r
- Returns a copy of a list without duplicates. Every list\r
- item must be hashable and the order of the items in the\r
- resulting list is not defined.\r
- """\r
- u = {}\r
- for x in lst:\r
- u[x] = 1\r
-\r
- return u.keys()\r
-\r
-class SimpleXMLRPCDispatcher:\r
- """Mix-in class that dispatches XML-RPC requests.\r
-\r
- This class is used to register XML-RPC method handlers\r
- and then to dispatch them. This class doesn't need to be\r
- instanced directly when used by SimpleXMLRPCServer but it\r
- can be instanced when used by the MultiPathXMLRPCServer.\r
- """\r
-\r
- def __init__(self, allow_none=False, encoding=None):\r
- self.funcs = {}\r
- self.instance = None\r
- self.allow_none = allow_none\r
- self.encoding = encoding\r
-\r
- def register_instance(self, instance, allow_dotted_names=False):\r
- """Registers an instance to respond to XML-RPC requests.\r
-\r
- Only one instance can be installed at a time.\r
-\r
- If the registered instance has a _dispatch method then that\r
- method will be called with the name of the XML-RPC method and\r
- its parameters as a tuple\r
- e.g. instance._dispatch('add',(2,3))\r
-\r
- If the registered instance does not have a _dispatch method\r
- then the instance will be searched to find a matching method\r
- and, if found, will be called. Methods beginning with an '_'\r
- are considered private and will not be called by\r
- SimpleXMLRPCServer.\r
-\r
- If a registered function matches a XML-RPC request, then it\r
- will be called instead of the registered instance.\r
-\r
- If the optional allow_dotted_names argument is true and the\r
- instance does not have a _dispatch method, method names\r
- containing dots are supported and resolved, as long as none of\r
- the name segments start with an '_'.\r
-\r
- *** SECURITY WARNING: ***\r
-\r
- Enabling the allow_dotted_names options allows intruders\r
- to access your module's global variables and may allow\r
- intruders to execute arbitrary code on your machine. Only\r
- use this option on a secure, closed network.\r
-\r
- """\r
-\r
- self.instance = instance\r
- self.allow_dotted_names = allow_dotted_names\r
-\r
- def register_function(self, function, name = None):\r
- """Registers a function to respond to XML-RPC requests.\r
-\r
- The optional name argument can be used to set a Unicode name\r
- for the function.\r
- """\r
-\r
- if name is None:\r
- name = function.__name__\r
- self.funcs[name] = function\r
-\r
- def register_introspection_functions(self):\r
- """Registers the XML-RPC introspection methods in the system\r
- namespace.\r
-\r
- see http://xmlrpc.usefulinc.com/doc/reserved.html\r
- """\r
-\r
- self.funcs.update({'system.listMethods' : self.system_listMethods,\r
- 'system.methodSignature' : self.system_methodSignature,\r
- 'system.methodHelp' : self.system_methodHelp})\r
-\r
- def register_multicall_functions(self):\r
- """Registers the XML-RPC multicall method in the system\r
- namespace.\r
-\r
- see http://www.xmlrpc.com/discuss/msgReader$1208"""\r
-\r
- self.funcs.update({'system.multicall' : self.system_multicall})\r
-\r
- def _marshaled_dispatch(self, data, dispatch_method = None, path = None):\r
- """Dispatches an XML-RPC method from marshalled (XML) data.\r
-\r
- XML-RPC methods are dispatched from the marshalled (XML) data\r
- using the _dispatch method and the result is returned as\r
- marshalled data. For backwards compatibility, a dispatch\r
- function can be provided as an argument (see comment in\r
- SimpleXMLRPCRequestHandler.do_POST) but overriding the\r
- existing method through subclassing is the preferred means\r
- of changing method dispatch behavior.\r
- """\r
-\r
- try:\r
- params, method = xmlrpclib.loads(data)\r
-\r
- # generate response\r
- if dispatch_method is not None:\r
- response = dispatch_method(method, params)\r
- else:\r
- response = self._dispatch(method, params)\r
- # wrap response in a singleton tuple\r
- response = (response,)\r
- response = xmlrpclib.dumps(response, methodresponse=1,\r
- allow_none=self.allow_none, encoding=self.encoding)\r
- except Fault, fault:\r
- response = xmlrpclib.dumps(fault, allow_none=self.allow_none,\r
- encoding=self.encoding)\r
- except:\r
- # report exception back to server\r
- exc_type, exc_value, exc_tb = sys.exc_info()\r
- response = xmlrpclib.dumps(\r
- xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)),\r
- encoding=self.encoding, allow_none=self.allow_none,\r
- )\r
-\r
- return response\r
-\r
- def system_listMethods(self):\r
- """system.listMethods() => ['add', 'subtract', 'multiple']\r
-\r
- Returns a list of the methods supported by the server."""\r
-\r
- methods = self.funcs.keys()\r
- if self.instance is not None:\r
- # Instance can implement _listMethod to return a list of\r
- # methods\r
- if hasattr(self.instance, '_listMethods'):\r
- methods = remove_duplicates(\r
- methods + self.instance._listMethods()\r
- )\r
- # if the instance has a _dispatch method then we\r
- # don't have enough information to provide a list\r
- # of methods\r
- elif not hasattr(self.instance, '_dispatch'):\r
- methods = remove_duplicates(\r
- methods + list_public_methods(self.instance)\r
- )\r
- methods.sort()\r
- return methods\r
-\r
- def system_methodSignature(self, method_name):\r
- """system.methodSignature('add') => [double, int, int]\r
-\r
- Returns a list describing the signature of the method. In the\r
- above example, the add method takes two integers as arguments\r
- and returns a double result.\r
-\r
- This server does NOT support system.methodSignature."""\r
-\r
- # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html\r
-\r
- return 'signatures not supported'\r
-\r
- def system_methodHelp(self, method_name):\r
- """system.methodHelp('add') => "Adds two integers together"\r
-\r
- Returns a string containing documentation for the specified method."""\r
-\r
- method = None\r
- if method_name in self.funcs:\r
- method = self.funcs[method_name]\r
- elif self.instance is not None:\r
- # Instance can implement _methodHelp to return help for a method\r
- if hasattr(self.instance, '_methodHelp'):\r
- return self.instance._methodHelp(method_name)\r
- # if the instance has a _dispatch method then we\r
- # don't have enough information to provide help\r
- elif not hasattr(self.instance, '_dispatch'):\r
- try:\r
- method = resolve_dotted_attribute(\r
- self.instance,\r
- method_name,\r
- self.allow_dotted_names\r
- )\r
- except AttributeError:\r
- pass\r
-\r
- # Note that we aren't checking that the method actually\r
- # be a callable object of some kind\r
- if method is None:\r
- return ""\r
- else:\r
- import pydoc\r
- return pydoc.getdoc(method)\r
-\r
- def system_multicall(self, call_list):\r
- """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \\r
-[[4], ...]\r
-\r
- Allows the caller to package multiple XML-RPC calls into a single\r
- request.\r
-\r
- See http://www.xmlrpc.com/discuss/msgReader$1208\r
- """\r
-\r
- results = []\r
- for call in call_list:\r
- method_name = call['methodName']\r
- params = call['params']\r
-\r
- try:\r
- # XXX A marshalling error in any response will fail the entire\r
- # multicall. If someone cares they should fix this.\r
- results.append([self._dispatch(method_name, params)])\r
- except Fault, fault:\r
- results.append(\r
- {'faultCode' : fault.faultCode,\r
- 'faultString' : fault.faultString}\r
- )\r
- except:\r
- exc_type, exc_value, exc_tb = sys.exc_info()\r
- results.append(\r
- {'faultCode' : 1,\r
- 'faultString' : "%s:%s" % (exc_type, exc_value)}\r
- )\r
- return results\r
-\r
- def _dispatch(self, method, params):\r
- """Dispatches the XML-RPC method.\r
-\r
- XML-RPC calls are forwarded to a registered function that\r
- matches the called XML-RPC method name. If no such function\r
- exists then the call is forwarded to the registered instance,\r
- if available.\r
-\r
- If the registered instance has a _dispatch method then that\r
- method will be called with the name of the XML-RPC method and\r
- its parameters as a tuple\r
- e.g. instance._dispatch('add',(2,3))\r
-\r
- If the registered instance does not have a _dispatch method\r
- then the instance will be searched to find a matching method\r
- and, if found, will be called.\r
-\r
- Methods beginning with an '_' are considered private and will\r
- not be called.\r
- """\r
-\r
- func = None\r
- try:\r
- # check to see if a matching function has been registered\r
- func = self.funcs[method]\r
- except KeyError:\r
- if self.instance is not None:\r
- # check for a _dispatch method\r
- if hasattr(self.instance, '_dispatch'):\r
- return self.instance._dispatch(method, params)\r
- else:\r
- # call instance method directly\r
- try:\r
- func = resolve_dotted_attribute(\r
- self.instance,\r
- method,\r
- self.allow_dotted_names\r
- )\r
- except AttributeError:\r
- pass\r
-\r
- if func is not None:\r
- return func(*params)\r
- else:\r
- raise Exception('method "%s" is not supported' % method)\r
-\r
-class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):\r
- """Simple XML-RPC request handler class.\r
-\r
- Handles all HTTP POST requests and attempts to decode them as\r
- XML-RPC requests.\r
- """\r
-\r
- # Class attribute listing the accessible path components;\r
- # paths not on this list will result in a 404 error.\r
- rpc_paths = ('/', '/RPC2')\r
-\r
- #if not None, encode responses larger than this, if possible\r
- encode_threshold = 1400 #a common MTU\r
-\r
- #Override form StreamRequestHandler: full buffering of output\r
- #and no Nagle.\r
- wbufsize = -1\r
- disable_nagle_algorithm = True\r
-\r
- # a re to match a gzip Accept-Encoding\r
- aepattern = re.compile(r"""\r
- \s* ([^\s;]+) \s* #content-coding\r
- (;\s* q \s*=\s* ([0-9\.]+))? #q\r
- """, re.VERBOSE | re.IGNORECASE)\r
-\r
- def accept_encodings(self):\r
- r = {}\r
- ae = self.headers.get("Accept-Encoding", "")\r
- for e in ae.split(","):\r
- match = self.aepattern.match(e)\r
- if match:\r
- v = match.group(3)\r
- v = float(v) if v else 1.0\r
- r[match.group(1)] = v\r
- return r\r
-\r
- def is_rpc_path_valid(self):\r
- if self.rpc_paths:\r
- return self.path in self.rpc_paths\r
- else:\r
- # If .rpc_paths is empty, just assume all paths are legal\r
- return True\r
-\r
- def do_POST(self):\r
- """Handles the HTTP POST request.\r
-\r
- Attempts to interpret all HTTP POST requests as XML-RPC calls,\r
- which are forwarded to the server's _dispatch method for handling.\r
- """\r
-\r
- # Check that the path is legal\r
- if not self.is_rpc_path_valid():\r
- self.report_404()\r
- return\r
-\r
- try:\r
- # Get arguments by reading body of request.\r
- # We read this in chunks to avoid straining\r
- # socket.read(); around the 10 or 15Mb mark, some platforms\r
- # begin to have problems (bug #792570).\r
- max_chunk_size = 10*1024*1024\r
- size_remaining = int(self.headers["content-length"])\r
- L = []\r
- while size_remaining:\r
- chunk_size = min(size_remaining, max_chunk_size)\r
- L.append(self.rfile.read(chunk_size))\r
- size_remaining -= len(L[-1])\r
- data = ''.join(L)\r
-\r
- data = self.decode_request_content(data)\r
- if data is None:\r
- return #response has been sent\r
-\r
- # In previous versions of SimpleXMLRPCServer, _dispatch\r
- # could be overridden in this class, instead of in\r
- # SimpleXMLRPCDispatcher. To maintain backwards compatibility,\r
- # check to see if a subclass implements _dispatch and dispatch\r
- # using that method if present.\r
- response = self.server._marshaled_dispatch(\r
- data, getattr(self, '_dispatch', None), self.path\r
- )\r
- except Exception, e: # This should only happen if the module is buggy\r
- # internal error, report as HTTP server error\r
- self.send_response(500)\r
-\r
- # Send information about the exception if requested\r
- if hasattr(self.server, '_send_traceback_header') and \\r
- self.server._send_traceback_header:\r
- self.send_header("X-exception", str(e))\r
- self.send_header("X-traceback", traceback.format_exc())\r
-\r
- self.send_header("Content-length", "0")\r
- self.end_headers()\r
- else:\r
- # got a valid XML RPC response\r
- self.send_response(200)\r
- self.send_header("Content-type", "text/xml")\r
- if self.encode_threshold is not None:\r
- if len(response) > self.encode_threshold:\r
- q = self.accept_encodings().get("gzip", 0)\r
- if q:\r
- try:\r
- response = xmlrpclib.gzip_encode(response)\r
- self.send_header("Content-Encoding", "gzip")\r
- except NotImplementedError:\r
- pass\r
- self.send_header("Content-length", str(len(response)))\r
- self.end_headers()\r
- self.wfile.write(response)\r
-\r
- def decode_request_content(self, data):\r
- #support gzip encoding of request\r
- encoding = self.headers.get("content-encoding", "identity").lower()\r
- if encoding == "identity":\r
- return data\r
- if encoding == "gzip":\r
- try:\r
- return xmlrpclib.gzip_decode(data)\r
- except NotImplementedError:\r
- self.send_response(501, "encoding %r not supported" % encoding)\r
- except ValueError:\r
- self.send_response(400, "error decoding gzip content")\r
- else:\r
- self.send_response(501, "encoding %r not supported" % encoding)\r
- self.send_header("Content-length", "0")\r
- self.end_headers()\r
-\r
- def report_404 (self):\r
- # Report a 404 error\r
- self.send_response(404)\r
- response = 'No such page'\r
- self.send_header("Content-type", "text/plain")\r
- self.send_header("Content-length", str(len(response)))\r
- self.end_headers()\r
- self.wfile.write(response)\r
-\r
- def log_request(self, code='-', size='-'):\r
- """Selectively log an accepted request."""\r
-\r
- if self.server.logRequests:\r
- BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)\r
-\r
-class SimpleXMLRPCServer(SocketServer.TCPServer,\r
- SimpleXMLRPCDispatcher):\r
- """Simple XML-RPC server.\r
-\r
- Simple XML-RPC server that allows functions and a single instance\r
- to be installed to handle requests. The default implementation\r
- attempts to dispatch XML-RPC calls to the functions or instance\r
- installed in the server. Override the _dispatch method inhereted\r
- from SimpleXMLRPCDispatcher to change this behavior.\r
- """\r
-\r
- allow_reuse_address = True\r
-\r
- # Warning: this is for debugging purposes only! Never set this to True in\r
- # production code, as will be sending out sensitive information (exception\r
- # and stack trace details) when exceptions are raised inside\r
- # SimpleXMLRPCRequestHandler.do_POST\r
- _send_traceback_header = False\r
-\r
- def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,\r
- logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):\r
- self.logRequests = logRequests\r
-\r
- SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)\r
- SocketServer.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)\r
-\r
- # [Bug #1222790] If possible, set close-on-exec flag; if a\r
- # method spawns a subprocess, the subprocess shouldn't have\r
- # the listening socket open.\r
- if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):\r
- flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)\r
- flags |= fcntl.FD_CLOEXEC\r
- fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)\r
-\r
-class MultiPathXMLRPCServer(SimpleXMLRPCServer):\r
- """Multipath XML-RPC Server\r
- This specialization of SimpleXMLRPCServer allows the user to create\r
- multiple Dispatcher instances and assign them to different\r
- HTTP request paths. This makes it possible to run two or more\r
- 'virtual XML-RPC servers' at the same port.\r
- Make sure that the requestHandler accepts the paths in question.\r
- """\r
- def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,\r
- logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):\r
-\r
- SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests, allow_none,\r
- encoding, bind_and_activate)\r
- self.dispatchers = {}\r
- self.allow_none = allow_none\r
- self.encoding = encoding\r
-\r
- def add_dispatcher(self, path, dispatcher):\r
- self.dispatchers[path] = dispatcher\r
- return dispatcher\r
-\r
- def get_dispatcher(self, path):\r
- return self.dispatchers[path]\r
-\r
- def _marshaled_dispatch(self, data, dispatch_method = None, path = None):\r
- try:\r
- response = self.dispatchers[path]._marshaled_dispatch(\r
- data, dispatch_method, path)\r
- except:\r
- # report low level exception back to server\r
- # (each dispatcher should have handled their own\r
- # exceptions)\r
- exc_type, exc_value = sys.exc_info()[:2]\r
- response = xmlrpclib.dumps(\r
- xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)),\r
- encoding=self.encoding, allow_none=self.allow_none)\r
- return response\r
-\r
-class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):\r
- """Simple handler for XML-RPC data passed through CGI."""\r
-\r
- def __init__(self, allow_none=False, encoding=None):\r
- SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)\r
-\r
- def handle_xmlrpc(self, request_text):\r
- """Handle a single XML-RPC request"""\r
-\r
- response = self._marshaled_dispatch(request_text)\r
-\r
- print 'Content-Type: text/xml'\r
- print 'Content-Length: %d' % len(response)\r
- print\r
- sys.stdout.write(response)\r
-\r
- def handle_get(self):\r
- """Handle a single HTTP GET request.\r
-\r
- Default implementation indicates an error because\r
- XML-RPC uses the POST method.\r
- """\r
-\r
- code = 400\r
- message, explain = \\r
- BaseHTTPServer.BaseHTTPRequestHandler.responses[code]\r
-\r
- response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \\r
- {\r
- 'code' : code,\r
- 'message' : message,\r
- 'explain' : explain\r
- }\r
- print 'Status: %d %s' % (code, message)\r
- print 'Content-Type: %s' % BaseHTTPServer.DEFAULT_ERROR_CONTENT_TYPE\r
- print 'Content-Length: %d' % len(response)\r
- print\r
- sys.stdout.write(response)\r
-\r
- def handle_request(self, request_text = None):\r
- """Handle a single XML-RPC request passed through a CGI post method.\r
-\r
- If no XML data is given then it is read from stdin. The resulting\r
- XML-RPC response is printed to stdout along with the correct HTTP\r
- headers.\r
- """\r
-\r
- if request_text is None and \\r
- os.environ.get('REQUEST_METHOD', None) == 'GET':\r
- self.handle_get()\r
- else:\r
- # POST data is normally available through stdin\r
- try:\r
- length = int(os.environ.get('CONTENT_LENGTH', None))\r
- except (TypeError, ValueError):\r
- length = -1\r
- if request_text is None:\r
- request_text = sys.stdin.read(length)\r
-\r
- self.handle_xmlrpc(request_text)\r
-\r
-if __name__ == '__main__':\r
- print 'Running XML-RPC server on port 8000'\r
- server = SimpleXMLRPCServer(("localhost", 8000))\r
- server.register_function(pow)\r
- server.register_function(lambda x,y: x+y, 'add')\r
- server.serve_forever()\r