2 # Originally taken from:
3 # http://code.activestate.com/recipes/552751/
4 # thanks to david decotigny
6 # Heavily based on the XML-RPC implementation in python.
7 # Based on the json-rpc specs: http://json-rpc.org/wiki/specification
8 # The main deviation is on the error treatment. The official spec
9 # would set the 'error' attribute to a string. This implementation
10 # sets it to a dictionary with keys: message/traceback/type
14 import SimpleAppHTTPServer
15 #import BaseHTTPServer
27 import SimpleXMLRPCServer
30 class SimpleJSONRPCRequestHandler(SimpleAppHTTPServer
.SimpleAppHTTPRequestHandler
):
31 """Simple JSONRPC request handler class and HTTP GET Server
33 Handles all HTTP POST requests and attempts to decode them as
36 Handles all HTTP GET requests and serves the content from the
41 # Class attribute listing the accessible path components;
42 # paths not on this list will result in a 404 error.
43 rpc_paths
= ('/', '/JSON')
49 def is_rpc_path_valid(self
):
52 return self
.path
in self
.rpc_paths
54 # If .rpc_paths is empty, just assume all paths are legal
57 def onPOST(self
, client
, *args
):
58 """Handles the HTTP POST request.
60 Attempts to interpret all HTTP POST requests as XML-RPC calls,
61 which are forwarded to the server's _dispatch method for handling.
63 print "onPost", client
, args
67 # Check that the path is legal
68 if not self
.is_rpc_path_valid():
72 print "about to read data"
74 # Get arguments by reading body of request.
75 # We read this in chunks to avoid straining
76 # socket.read(); around the 10 or 15Mb mark, some platforms
77 # begin to have problems (bug #792570).
78 max_chunk_size
= 10*1024*1024
79 size_remaining
= int(self
.hr
.headers
["content-length"])
81 print "size_remaining", size_remaining
83 chunk_size
= min(size_remaining
, max_chunk_size
)
84 data
= self
.hr
.rfile
.read(chunk_size
)
86 size_remaining
-= len(L
[-1])
89 # In previous versions of SimpleXMLRPCServer, _dispatch
90 # could be overridden in this class, instead of in
91 # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
92 # check to see if a subclass implements _dispatch and dispatch
93 # using that method if present.
94 response
= self
._marshaled
_dispatch
(
95 data
, getattr(self
, '_dispatch', None)
97 except: # This should only happen if the module is buggy
98 # internal error, report as HTTP server error
99 self
.hr
.send_response(500)
100 self
.hr
.add_cookies()
101 self
.hr
.end_headers()
103 # got a valid JSONRPC response
104 self
.hr
.send_response(200)
105 self
.hr
.send_header("Content-type", "text/x-json")
106 self
.hr
.send_header("Content-length", str(len(response
)))
107 self
.hr
.add_cookies()
108 self
.hr
.end_headers()
109 self
.hr
.wfile
.write(response
)
111 # shut down the connection
113 #self.connection.shutdown(1)
115 def report_404 (self
):
117 self
.hr
.send_response(404)
118 response
= 'No such page'
119 self
.hr
.send_header("Content-type", "text/plain")
120 self
.hr
.send_header("Content-length", str(len(response
)))
121 self
.hr
.add_cookies()
122 self
.hr
.end_headers()
123 self
.hr
.wfile
.write(response
)
124 # shut down the connection
125 self
.hr
.wfile
.flush()
126 self
.hr
.connection
.shutdown(1)
128 def register_function(self
, function
, name
= None):
129 """Registers a function to respond to XML-RPC requests.
131 The optional name argument can be used to set a Unicode name
136 name
= function
.__name
__
137 self
.funcs
[name
] = function
140 def _marshaled_dispatch(self
, data
, dispatch_method
= None):
143 req
= cjson
.decode(data
)
144 method
= req
['method']
145 params
= req
['params'] or ()
148 if dispatch_method
is not None:
149 result
= dispatch_method(method
, params
)
151 result
= self
._dispatch
(method
, params
)
152 response
= dict(id=id, result
=result
, error
=None)
154 extpe
, exv
, extrc
= sys
.exc_info()
155 err
= dict(type=str(extpe
),
157 traceback
=''.join(traceback
.format_tb(extrc
)))
158 response
= dict(id=id, result
=None, error
=err
)
160 return cjson
.encode(response
)
162 extpe
, exv
, extrc
= sys
.exc_info()
163 err
= dict(type=str(extpe
),
165 traceback
=''.join(traceback
.format_tb(extrc
)))
166 response
= dict(id=id, result
=None, error
=err
)
167 return cjson
.encode(response
)
169 def _dispatch(self
, method
, params
):
170 """Dispatches the XML-RPC method.
172 XML-RPC calls are forwarded to a registered function that
173 matches the called XML-RPC method name. If no such function
174 exists then the call is forwarded to the registered instance,
177 If the registered instance has a _dispatch method then that
178 method will be called with the name of the XML-RPC method and
179 its parameters as a tuple
180 e.g. instance._dispatch('add',(2,3))
182 If the registered instance does not have a _dispatch method
183 then the instance will be searched to find a matching method
184 and, if found, will be called.
186 Methods beginning with an '_' are considered private and will
190 func
= self
.funcs
.get(method
, None)
193 print "params", params
196 raise Exception('method "%s" is not supported' % method
)
199 #def log_request(self, code='-', size='-'):
200 # """Selectively log an accepted request."""
202 # if self.server.logRequests:
203 # BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
207 class SimpleJSONRPCServer
:
208 """Simple JSON-RPC server.
210 Simple JSON-RPC server that allows functions and a single instance
211 to be installed to handle requests. The default implementation
212 attempts to dispatch JSON-RPC calls to the functions or instance
213 installed in the server. Override the _dispatch method inhereted
214 from SimpleJSONRPCDispatcher to change this behavior.
217 allow_reuse_address
= True
219 def __init__(self
, addr
, requestHandler
=SimpleJSONRPCRequestHandler
,
221 self
.logRequests
= logRequests
223 # [Bug #1222790] If possible, set close-on-exec flag; if a
224 # method spawns a subprocess, the subprocess shouldn't have
225 # the listening socket open.
226 if fcntl
is not None and hasattr(fcntl
, 'FD_CLOEXEC'):
227 flags
= fcntl
.fcntl(self
.fileno(), fcntl
.F_GETFD
)
228 flags |
= fcntl
.FD_CLOEXEC
229 fcntl
.fcntl(self
.fileno(), fcntl
.F_SETFD
, flags
)
237 class ResponseError(xmlrpclib
.ResponseError
):
239 class Fault(xmlrpclib
.ResponseError
):
242 def _get_response(file, sock
):
246 response
= sock
.recv(1024)
248 response
= file.read(1024)
257 class Transport(xmlrpclib
.Transport
):
258 def _parse_response(self
, file, sock
):
259 return _get_response(file, sock
)
261 class SafeTransport(xmlrpclib
.SafeTransport
):
262 def _parse_response(self
, file, sock
):
263 return _get_response(file, sock
)
266 def __init__(self
, uri
, id=None, transport
=None, use_datetime
=0):
267 # establish a "logical" server connection
271 type, uri
= urllib
.splittype(uri
)
272 if type not in ("http", "https"):
273 raise IOError, "unsupported JSON-RPC protocol"
274 self
.__host
, self
.__handler
= urllib
.splithost(uri
)
275 if not self
.__handler
:
276 self
.__handler
= "/JSON"
278 if transport
is None:
280 transport
= SafeTransport(use_datetime
=use_datetime
)
282 transport
= Transport(use_datetime
=use_datetime
)
284 self
.__transport
= transport
287 def __request(self
, methodname
, params
):
288 # call a method on the remote server
290 request
= cjson
.encode(dict(id=self
.__id
, method
=methodname
,
293 data
= self
.__transport
.request(
300 response
= cjson
.decode(data
)
302 if response
["id"] != self
.__id
:
303 raise ResponseError("Invalid request id (is: %s, expected: %s)" \
304 % (response
["id"], self
.__id
))
305 if response
["error"] is not None:
306 raise Fault("JSON Error", response
["error"])
307 return response
["result"]
311 "<ServerProxy for %s%s>" %
312 (self
.__host
, self
.__handler
)
317 def __getattr__(self
, name
):
318 # magic method dispatcher
319 return xmlrpclib
._Method
(self
.__request
, name
)
322 def jsonremote(service
):
323 """Make JSONRPCService a decorator so that you can write :
325 chatservice = SimpleJSONRPCServer()
327 @jsonremote(chatservice, 'login')
328 def login(request, user_name):
332 if isinstance(service
, SimpleJSONRPCServer
):
333 service
.register_function(func
, func
.__name
__)
335 emsg
= 'Service "%s" not found' % str(service
.__name
__)
336 raise NotImplementedError, emsg
341 if __name__
== '__main__':
342 if not len(sys
.argv
) > 1:
344 print 'Running JSON-RPC server on port 8000'
345 server
= SimpleJSONRPCServer(("localhost", 8000))
346 server
.register_function(pow)
347 server
.register_function(lambda x
,y
: x
+y
, 'add')
348 server
.register_function(lambda x
: x
, 'echo')
349 server
.serve_forever()
351 remote
= ServerProxy(sys
.argv
[1])
352 print 'Using connection', remote
354 print repr(remote
.add(1, 2))
356 print repr(remote
.pow(2, 4))
362 print "Successful execution of invalid code"
369 print "Successful execution of invalid code"
374 # Invalid method name
375 print repr(remote
.powx(2, 4))
376 print "Successful execution of invalid code"