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
.end_headers()
102 # got a valid JSONRPC response
103 self
.hr
.send_response(200)
104 self
.hr
.send_header("Content-type", "text/x-json")
105 self
.hr
.send_header("Content-length", str(len(response
)))
106 self
.hr
.end_headers()
107 self
.hr
.wfile
.write(response
)
109 # shut down the connection
111 #self.connection.shutdown(1)
113 def report_404 (self
):
115 self
.hr
.send_response(404)
116 response
= 'No such page'
117 self
.hr
.send_header("Content-type", "text/plain")
118 self
.hr
.send_header("Content-length", str(len(response
)))
119 self
.hr
.end_headers()
120 self
.hr
.wfile
.write(response
)
121 # shut down the connection
122 self
.hr
.wfile
.flush()
123 self
.hr
.connection
.shutdown(1)
125 def register_function(self
, function
, name
= None):
126 """Registers a function to respond to XML-RPC requests.
128 The optional name argument can be used to set a Unicode name
133 name
= function
.__name
__
134 self
.funcs
[name
] = function
137 def _marshaled_dispatch(self
, data
, dispatch_method
= None):
140 req
= cjson
.decode(data
)
141 method
= req
['method']
142 params
= req
['params']
145 if dispatch_method
is not None:
146 result
= dispatch_method(method
, params
)
148 result
= self
._dispatch
(method
, params
)
149 response
= dict(id=id, result
=result
, error
=None)
151 extpe
, exv
, extrc
= sys
.exc_info()
152 err
= dict(type=str(extpe
),
154 traceback
=''.join(traceback
.format_tb(extrc
)))
155 response
= dict(id=id, result
=None, error
=err
)
157 return cjson
.encode(response
)
159 extpe
, exv
, extrc
= sys
.exc_info()
160 err
= dict(type=str(extpe
),
162 traceback
=''.join(traceback
.format_tb(extrc
)))
163 response
= dict(id=id, result
=None, error
=err
)
164 return cjson
.encode(response
)
166 def _dispatch(self
, method
, params
):
167 """Dispatches the XML-RPC method.
169 XML-RPC calls are forwarded to a registered function that
170 matches the called XML-RPC method name. If no such function
171 exists then the call is forwarded to the registered instance,
174 If the registered instance has a _dispatch method then that
175 method will be called with the name of the XML-RPC method and
176 its parameters as a tuple
177 e.g. instance._dispatch('add',(2,3))
179 If the registered instance does not have a _dispatch method
180 then the instance will be searched to find a matching method
181 and, if found, will be called.
183 Methods beginning with an '_' are considered private and will
187 func
= self
.funcs
.get(method
, None)
190 print "params", params
193 raise Exception('method "%s" is not supported' % method
)
196 #def log_request(self, code='-', size='-'):
197 # """Selectively log an accepted request."""
199 # if self.server.logRequests:
200 # BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
204 class SimpleJSONRPCServer
:
205 """Simple JSON-RPC server.
207 Simple JSON-RPC server that allows functions and a single instance
208 to be installed to handle requests. The default implementation
209 attempts to dispatch JSON-RPC calls to the functions or instance
210 installed in the server. Override the _dispatch method inhereted
211 from SimpleJSONRPCDispatcher to change this behavior.
214 allow_reuse_address
= True
216 def __init__(self
, addr
, requestHandler
=SimpleJSONRPCRequestHandler
,
218 self
.logRequests
= logRequests
220 # [Bug #1222790] If possible, set close-on-exec flag; if a
221 # method spawns a subprocess, the subprocess shouldn't have
222 # the listening socket open.
223 if fcntl
is not None and hasattr(fcntl
, 'FD_CLOEXEC'):
224 flags
= fcntl
.fcntl(self
.fileno(), fcntl
.F_GETFD
)
225 flags |
= fcntl
.FD_CLOEXEC
226 fcntl
.fcntl(self
.fileno(), fcntl
.F_SETFD
, flags
)
234 class ResponseError(xmlrpclib
.ResponseError
):
236 class Fault(xmlrpclib
.ResponseError
):
239 def _get_response(file, sock
):
243 response
= sock
.recv(1024)
245 response
= file.read(1024)
254 class Transport(xmlrpclib
.Transport
):
255 def _parse_response(self
, file, sock
):
256 return _get_response(file, sock
)
258 class SafeTransport(xmlrpclib
.SafeTransport
):
259 def _parse_response(self
, file, sock
):
260 return _get_response(file, sock
)
263 def __init__(self
, uri
, id=None, transport
=None, use_datetime
=0):
264 # establish a "logical" server connection
268 type, uri
= urllib
.splittype(uri
)
269 if type not in ("http", "https"):
270 raise IOError, "unsupported JSON-RPC protocol"
271 self
.__host
, self
.__handler
= urllib
.splithost(uri
)
272 if not self
.__handler
:
273 self
.__handler
= "/JSON"
275 if transport
is None:
277 transport
= SafeTransport(use_datetime
=use_datetime
)
279 transport
= Transport(use_datetime
=use_datetime
)
281 self
.__transport
= transport
284 def __request(self
, methodname
, params
):
285 # call a method on the remote server
287 request
= cjson
.encode(dict(id=self
.__id
, method
=methodname
,
290 data
= self
.__transport
.request(
297 response
= cjson
.decode(data
)
299 if response
["id"] != self
.__id
:
300 raise ResponseError("Invalid request id (is: %s, expected: %s)" \
301 % (response
["id"], self
.__id
))
302 if response
["error"] is not None:
303 raise Fault("JSON Error", response
["error"])
304 return response
["result"]
308 "<ServerProxy for %s%s>" %
309 (self
.__host
, self
.__handler
)
314 def __getattr__(self
, name
):
315 # magic method dispatcher
316 return xmlrpclib
._Method
(self
.__request
, name
)
319 def jsonremote(service
):
320 """Make JSONRPCService a decorator so that you can write :
322 chatservice = SimpleJSONRPCServer()
324 @jsonremote(chatservice, 'login')
325 def login(request, user_name):
329 if isinstance(service
, SimpleJSONRPCServer
):
330 service
.register_function(func
, func
.__name
__)
332 emsg
= 'Service "%s" not found' % str(service
.__name
__)
333 raise NotImplementedError, emsg
338 if __name__
== '__main__':
339 if not len(sys
.argv
) > 1:
341 print 'Running JSON-RPC server on port 8000'
342 server
= SimpleJSONRPCServer(("localhost", 8000))
343 server
.register_function(pow)
344 server
.register_function(lambda x
,y
: x
+y
, 'add')
345 server
.register_function(lambda x
: x
, 'echo')
346 server
.serve_forever()
348 remote
= ServerProxy(sys
.argv
[1])
349 print 'Using connection', remote
351 print repr(remote
.add(1, 2))
353 print repr(remote
.pow(2, 4))
359 print "Successful execution of invalid code"
366 print "Successful execution of invalid code"
371 # Invalid method name
372 print repr(remote
.powx(2, 4))
373 print "Successful execution of invalid code"