| 1 |
#!/usr/bin/python |
|---|
| 2 |
# -*- coding: utf-8 -*- |
|---|
| 3 |
# SimpleYAMLRPCServer.py |
|---|
| 4 |
"""Simple YAML-RPC Server. |
|---|
| 5 |
|
|---|
| 6 |
This module can be used to create simple YAML-RPC servers |
|---|
| 7 |
by creating a server and either installing functions, a |
|---|
| 8 |
class instance, or by extending the SimpleYAMLRPCServer |
|---|
| 9 |
class. |
|---|
| 10 |
|
|---|
| 11 |
It can also be used to handle YAML-RPC requests in a CGI |
|---|
| 12 |
environment using CGIYAMLRPCRequestHandler. |
|---|
| 13 |
|
|---|
| 14 |
A list of possible usage patterns follows: |
|---|
| 15 |
|
|---|
| 16 |
1. Install functions: |
|---|
| 17 |
|
|---|
| 18 |
server = SimpleYAMLRPCServer(("localhost", 8000)) |
|---|
| 19 |
server.register_function(pow) |
|---|
| 20 |
server.register_function(lambda x,y: x+y, 'add') |
|---|
| 21 |
server.serve_forever() |
|---|
| 22 |
|
|---|
| 23 |
2. Install an instance: |
|---|
| 24 |
|
|---|
| 25 |
class MyFuncs: |
|---|
| 26 |
def __init__(self): |
|---|
| 27 |
# make all of the string functions available through |
|---|
| 28 |
# string.func_name |
|---|
| 29 |
import string |
|---|
| 30 |
self.string = string |
|---|
| 31 |
def _listMethods(self): |
|---|
| 32 |
# implement this method so that system.listMethods |
|---|
| 33 |
# knows to advertise the strings methods |
|---|
| 34 |
return list_public_methods(self) + \ |
|---|
| 35 |
['string.' + method for method in list_public_methods(self.string)] |
|---|
| 36 |
def pow(self, x, y): return pow(x, y) |
|---|
| 37 |
def add(self, x, y) : return x + y |
|---|
| 38 |
|
|---|
| 39 |
server = SimpleYAMLRPCServer(("localhost", 8000)) |
|---|
| 40 |
server.register_introspection_functions() |
|---|
| 41 |
server.register_instance(MyFuncs()) |
|---|
| 42 |
server.serve_forever() |
|---|
| 43 |
|
|---|
| 44 |
3. Install an instance with custom dispatch method: |
|---|
| 45 |
|
|---|
| 46 |
class Math: |
|---|
| 47 |
def _listMethods(self): |
|---|
| 48 |
# this method must be present for system.listMethods |
|---|
| 49 |
# to work |
|---|
| 50 |
return ['add', 'pow'] |
|---|
| 51 |
def _methodHelp(self, method): |
|---|
| 52 |
# this method must be present for system.methodHelp |
|---|
| 53 |
# to work |
|---|
| 54 |
if method == 'add': |
|---|
| 55 |
return "add(2,3) => 5" |
|---|
| 56 |
elif method == 'pow': |
|---|
| 57 |
return "pow(x, y[, z]) => number" |
|---|
| 58 |
else: |
|---|
| 59 |
# By convention, return empty |
|---|
| 60 |
# string if no help is available |
|---|
| 61 |
return "" |
|---|
| 62 |
def _dispatch(self, method, params): |
|---|
| 63 |
if method == 'pow': |
|---|
| 64 |
return pow(*params) |
|---|
| 65 |
elif method == 'add': |
|---|
| 66 |
return params[0] + params[1] |
|---|
| 67 |
else: |
|---|
| 68 |
raise 'bad method' |
|---|
| 69 |
|
|---|
| 70 |
server = SimpleXMLRPCServer(("localhost", 8000)) |
|---|
| 71 |
server.register_introspection_functions() |
|---|
| 72 |
server.register_instance(Math()) |
|---|
| 73 |
server.serve_forever() |
|---|
| 74 |
|
|---|
| 75 |
4. Subclass SimpleYAMLRPCServer: |
|---|
| 76 |
|
|---|
| 77 |
class MathServer(SimpleYAMLRPCServer): |
|---|
| 78 |
def _dispatch(self, method, params): |
|---|
| 79 |
try: |
|---|
| 80 |
# We are forcing the 'export_' prefix on methods that are |
|---|
| 81 |
# callable through YAML-RPC to prevent potential security |
|---|
| 82 |
# problems |
|---|
| 83 |
func = getattr(self, 'export_' + method) |
|---|
| 84 |
except AttributeError: |
|---|
| 85 |
raise Exception('method "%s" is not supported' % method) |
|---|
| 86 |
else: |
|---|
| 87 |
return func(*params) |
|---|
| 88 |
|
|---|
| 89 |
def export_add(self, x, y): |
|---|
| 90 |
return x + y |
|---|
| 91 |
|
|---|
| 92 |
server = MathServer(("localhost", 8000)) |
|---|
| 93 |
server.serve_forever() |
|---|
| 94 |
|
|---|
| 95 |
5. CGI script: |
|---|
| 96 |
|
|---|
| 97 |
server = CGIYAMLRPCRequestHandler() |
|---|
| 98 |
server.register_function(pow) |
|---|
| 99 |
server.handle_request() |
|---|
| 100 |
""" |
|---|
| 101 |
|
|---|
| 102 |
# This implementation is based on SimpleJSONRPCServer, which was |
|---|
| 103 |
# was converted from SimpleXMLRPCServer by David McNab |
|---|
| 104 |
# (david@rebirthing.co.nz). Current implementation written by Peter |
|---|
| 105 |
# Murphy. |
|---|
| 106 |
|
|---|
| 107 |
# Original SimpleXMLRPCServer module was written by Brian |
|---|
| 108 |
# Quinlan (brian@sweetapp.com), Based on code written by Fredrik Lundh. |
|---|
| 109 |
|
|---|
| 110 |
import xmlrpclib |
|---|
| 111 |
from xmlrpclib import Fault |
|---|
| 112 |
import SocketServer |
|---|
| 113 |
import BaseHTTPServer |
|---|
| 114 |
import sys |
|---|
| 115 |
import os |
|---|
| 116 |
|
|---|
| 117 |
import SimpleXMLRPCServer |
|---|
| 118 |
import yaml |
|---|
| 119 |
from yamlrpcbasic import * |
|---|
| 120 |
|
|---|
| 121 |
import traceback |
|---|
| 122 |
|
|---|
| 123 |
class SimpleYAMLRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher): |
|---|
| 124 |
""" Mix-in class that dispatches YAML-RPC requests. Based on |
|---|
| 125 |
SimpleXMLRPCDispatcher, but overrides marshaled_dispatch for |
|---|
| 126 |
YAML-RPC. |
|---|
| 127 |
|
|---|
| 128 |
This class is used to register YAML-RPC method handlers and then to |
|---|
| 129 |
dispatch them. There should never be any reason to instantiate this |
|---|
| 130 |
class directly. |
|---|
| 131 |
""" |
|---|
| 132 |
|
|---|
| 133 |
def __init__(self, safe_loader = True, json_compat = True): |
|---|
| 134 |
""" The parameters are: |
|---|
| 135 |
safe_loader: the default is True: only allows "simple" YAML data to |
|---|
| 136 |
be loaded - sequences, dictionaries and most immutable types. If |
|---|
| 137 |
this parameter is False, then arbitrary YAML data can be loaded. |
|---|
| 138 |
This can be a security risk. (Only set this parameter to False with |
|---|
| 139 |
trusted clients!) |
|---|
| 140 |
|
|---|
| 141 |
json_compat: if True, all complex objects are sent to YAML-RPC |
|---|
| 142 |
clients as dictionaries; no special tagging printed out. If False, |
|---|
| 143 |
YAML-RPC responses are delivered with the tag '!yamlrpcret'. The |
|---|
| 144 |
default is True. |
|---|
| 145 |
""" |
|---|
| 146 |
self.json_compat = json_compat; |
|---|
| 147 |
self.loadfunc(safe_loader); |
|---|
| 148 |
SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, "utf-8"); |
|---|
| 149 |
|
|---|
| 150 |
def loadfunc(self, safe_loader): |
|---|
| 151 |
""" Sets the load function. """ |
|---|
| 152 |
self.safe_loader = safe_loader; |
|---|
| 153 |
if self.safe_loader: |
|---|
| 154 |
self._loadfunc = yaml.safe_load; |
|---|
| 155 |
else: |
|---|
| 156 |
self._loadfunc = yaml.load; |
|---|
| 157 |
|
|---|
| 158 |
def _marshaled_dispatch(self, data, dispatch_method = None): |
|---|
| 159 |
""" Dispatches a YAML-RPC method from marshalled (YAML) data. |
|---|
| 160 |
|
|---|
| 161 |
YAML-RPC methods are dispatched from the marshalled (YAML) data |
|---|
| 162 |
using the _dispatch method and the result is returned as |
|---|
| 163 |
marshalled data. For backwards compatibility, a dispatch |
|---|
| 164 |
function can be provided as an argument (see comment in |
|---|
| 165 |
SimpleYAMLRPCRequestHandler.do_POST) but overriding the |
|---|
| 166 |
existing method through subclassing is the prefered means of |
|---|
| 167 |
changing method dispatch behavior. |
|---|
| 168 |
|
|---|
| 169 |
The "data" part can be one of the following: |
|---|
| 170 |
(a) An object with tag '!yamlrpccall'. |
|---|
| 171 |
(b) A dictionary/YAML map (with the same keys). This is to allow |
|---|
| 172 |
backward compatibilty with JSON-RPC. |
|---|
| 173 |
|
|---|
| 174 |
""" |
|---|
| 175 |
rawreq = self._loadfunc(data) |
|---|
| 176 |
id = getAttrWay(rawreq, "id", None); |
|---|
| 177 |
method = getAttrWay(rawreq, "method", None); |
|---|
| 178 |
params = getAttrWay(rawreq, "params", []); |
|---|
| 179 |
OurResponseObj = YAMLRPCReturn(id, "1.0", None, None); |
|---|
| 180 |
|
|---|
| 181 |
|
|---|
| 182 |
# We generate a response. |
|---|
| 183 |
try: |
|---|
| 184 |
if dispatch_method is not None: |
|---|
| 185 |
response = dispatch_method(method, params) |
|---|
| 186 |
else: |
|---|
| 187 |
response = self._dispatch(method, params) |
|---|
| 188 |
OurResponseObj.result = response |
|---|
| 189 |
# except Fault, fault: |
|---|
| 190 |
#response = xmlrpclib.dumps(fault) |
|---|
| 191 |
# OurResponseObj.error = YAMLRPCError("YAMLRPCError", 666, repr(response), None); |
|---|
| 192 |
except: |
|---|
| 193 |
OurResponseObj.error = YAMLRPCError("YAMLRPCError", 667, |
|---|
| 194 |
"%s:%s" % (sys.exc_type, sys.exc_value), None); |
|---|
| 195 |
|
|---|
| 196 |
# The following code is for JSON compatibility. |
|---|
| 197 |
|
|---|
| 198 |
if self.json_compat: |
|---|
| 199 |
OurResponseObj = OurResponseObj.__dict__; |
|---|
| 200 |
OurError = OurResponseObj["error"]; |
|---|
| 201 |
if OurError != None: |
|---|
| 202 |
OurResponseObj["error"] = OurError.__dict__; |
|---|
| 203 |
return yaml.dump(OurResponseObj); |
|---|
| 204 |
|
|---|
| 205 |
|
|---|
| 206 |
class SimpleYAMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): |
|---|
| 207 |
"""Simple YAML-RPC request handler class. |
|---|
| 208 |
|
|---|
| 209 |
Handles all HTTP POST requests and attempts to decode them as |
|---|
| 210 |
XML-RPC requests. |
|---|
| 211 |
""" |
|---|
| 212 |
def do_POST(self): |
|---|
| 213 |
"""Handles the HTTP POST request. |
|---|
| 214 |
|
|---|
| 215 |
Attempts to interpret all HTTP POST requests as YAML-RPC calls, |
|---|
| 216 |
which are forwarded to the server's _dispatch method for handling. |
|---|
| 217 |
""" |
|---|
| 218 |
try: |
|---|
| 219 |
# get arguments |
|---|
| 220 |
data = self.rfile.read(int(self.headers["content-length"])) |
|---|
| 221 |
# In previous versions of SimpleXMLRPCServer, _dispatch |
|---|
| 222 |
# could be overridden in this class, instead of in |
|---|
| 223 |
# SimpleXMLRPCDispatcher. To maintain backwards compatibility, |
|---|
| 224 |
# check to see if a subclass implements _dispatch and dispatch |
|---|
| 225 |
# using that method if present. |
|---|
| 226 |
response = self.server._marshaled_dispatch( |
|---|
| 227 |
data, getattr(self, '_dispatch', None) |
|---|
| 228 |
) |
|---|
| 229 |
except: # This should only happen if the module is buggy |
|---|
| 230 |
# internal error, report as HTTP server error |
|---|
| 231 |
self.send_response(500) |
|---|
| 232 |
self.end_headers() |
|---|
| 233 |
raise; |
|---|
| 234 |
else: |
|---|
| 235 |
# got a valid XML RPC response |
|---|
| 236 |
self.send_response(200) |
|---|
| 237 |
self.send_header("Content-type", "text/yaml") |
|---|
| 238 |
self.send_header("Content-length", str(len(response))) |
|---|
| 239 |
self.end_headers() |
|---|
| 240 |
self.wfile.write(response) |
|---|
| 241 |
|
|---|
| 242 |
# shut down the connection |
|---|
| 243 |
self.wfile.flush() |
|---|
| 244 |
self.connection.shutdown(1) |
|---|
| 245 |
|
|---|
| 246 |
class SimpleYAMLRPCServer(SocketServer.TCPServer, |
|---|
| 247 |
SimpleYAMLRPCDispatcher): |
|---|
| 248 |
"""Simple YAML-RPC server. |
|---|
| 249 |
|
|---|
| 250 |
Simple YAML-RPC server that allows functions and a single instance |
|---|
| 251 |
to be installed to handle requests. The default implementation |
|---|
| 252 |
attempts to dispatch YAML-RPC calls to the functions or instance |
|---|
| 253 |
installed in the server. Override the _dispatch method inhereted |
|---|
| 254 |
from SimpleYAMLRPCDispatcher to change this behavior. |
|---|
| 255 |
""" |
|---|
| 256 |
def __init__(self, addr, requestHandler=SimpleYAMLRPCRequestHandler, |
|---|
| 257 |
logRequests=1, safe_loader = True, json_compat = True): |
|---|
| 258 |
self.logRequests = logRequests |
|---|
| 259 |
|
|---|
| 260 |
SimpleYAMLRPCDispatcher.__init__(self, safe_loader, json_compat) |
|---|
| 261 |
SocketServer.TCPServer.__init__(self, addr, requestHandler) |
|---|
| 262 |
|
|---|
| 263 |
class CGIYAMLRPCRequestHandler(SimpleYAMLRPCDispatcher): |
|---|
| 264 |
"""Simple handler for YAML-RPC data passed through CGI.""" |
|---|
| 265 |
|
|---|
| 266 |
def __init__(self, safe_loader = True, json_compat = True): |
|---|
| 267 |
SimpleYAMLRPCDispatcher.__init__(safe_loader, json_compat) |
|---|
| 268 |
|
|---|
| 269 |
def handle_get(self): |
|---|
| 270 |
"""Handle a single HTTP GET request. |
|---|
| 271 |
|
|---|
| 272 |
Default implementation indicates an error because |
|---|
| 273 |
XML-RPC uses the POST method. |
|---|
| 274 |
""" |
|---|
| 275 |
|
|---|
| 276 |
code = 400 |
|---|
| 277 |
message, explain = \ |
|---|
| 278 |
BaseHTTPServer.BaseHTTPRequestHandler.responses[code] |
|---|
| 279 |
|
|---|
| 280 |
response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \ |
|---|
| 281 |
{ |
|---|
| 282 |
'code' : code, |
|---|
| 283 |
'message' : message, |
|---|
| 284 |
'explain' : explain |
|---|
| 285 |
} |
|---|
| 286 |
print 'Status: %d %s' % (code, message) |
|---|
| 287 |
print 'Content-Type: text/html' |
|---|
| 288 |
print 'Content-Length: %d' % len(response) |
|---|
| 289 |
print |
|---|
| 290 |
sys.stdout.write(response) |
|---|
| 291 |
|
|---|
| 292 |
def handle_request(self, request_text = None): |
|---|
| 293 |
"""Handle a single YAML-RPC request passed through a CGI post method. |
|---|
| 294 |
|
|---|
| 295 |
If no YAML data is given then it is read from stdin. The resulting |
|---|
| 296 |
YAML-RPC response is printed to stdout along with the correct HTTP |
|---|
| 297 |
headers. |
|---|
| 298 |
""" |
|---|
| 299 |
if request_text is None and \ |
|---|
| 300 |
os.environ.get('REQUEST_METHOD', None) == 'GET': |
|---|
| 301 |
self.handle_get() |
|---|
| 302 |
else: |
|---|
| 303 |
# POST data is normally available through stdin |
|---|
| 304 |
if request_text is None: |
|---|
| 305 |
request_text = sys.stdin.read() |
|---|
| 306 |
|
|---|
| 307 |
self.handle_yamlrpc(request_text) |
|---|
| 308 |
|
|---|
| 309 |
def handle_yamlrpc(self, request_text): |
|---|
| 310 |
"""Handle a single YAML-RPC request""" |
|---|
| 311 |
|
|---|
| 312 |
response = self._marshaled_dispatch(request_text) |
|---|
| 313 |
|
|---|
| 314 |
print 'Content-Type: text/yaml' |
|---|
| 315 |
print 'Content-Length: %d' % len(response) |
|---|
| 316 |
print |
|---|
| 317 |
sys.stdout.write(response) |
|---|
| 318 |
|
|---|
| 319 |
if __name__ == '__main__': |
|---|
| 320 |
server = SimpleYAMLRPCServer(("localhost", 8002), safe_loader = False, json_compat = True) |
|---|
| 321 |
server.register_function(pow) |
|---|
| 322 |
server.register_function(lambda x,y: x+y, 'add'); |
|---|
| 323 |
server.register_function(lambda x: x, u"echo"); |
|---|
| 324 |
server.register_introspection_functions(); |
|---|
| 325 |
server.serve_forever() |
|---|
| 326 |
|
|---|
| 327 |
|
|---|