"""Module to implement the SimpleXMLRPCServer module using JSON-RPC.
This module uses SimpleXMLRPCServer as the base and only overrides those
portions that implement the XML-RPC protocol. These portions are rewritten
to use the JSON-RPC protocol instead.
When large portions of code need to be rewritten the original code and
comments are preserved. The intention here is to keep the amount of code
change to a minimum.
This module only depends on default Python modules, as well as jsonrpclib
which also uses only default modules. No third party code is required to
use this module.
"""
import fcntl
import json
import SimpleXMLRPCServer as _base
import SocketServer
import sys
import traceback
import jsonrpclib
try:
import gzip
except ImportError:
gzip = None
class SimpleJSONRPCRequestHandler(_base.SimpleXMLRPCRequestHandler):
"""Request handler class for received requests.
This class extends the functionality of SimpleXMLRPCRequestHandler and only
overrides the operations needed to change the protocol from XML-RPC to
JSON-RPC.
"""
def do_POST(self):
"""Handles the HTTP POST request.
Attempts to interpret all HTTP POST requests as JSON-RPC calls,
which are forwarded to the server's _dispatch method for handling.
"""
if not self.is_rpc_path_valid():
self.report_404()
return
try:
max_chunk_size = 10*1024*1024
size_remaining = int(self.headers['content-length'])
data = []
while size_remaining:
chunk_size = min(size_remaining, max_chunk_size)
chunk = self.rfile.read(chunk_size)
if not chunk:
break
data.append(chunk)
size_remaining -= len(data[-1])
data = ''.join(data)
data = self.decode_request_content(data)
if data is None:
return
response = self.server._marshaled_dispatch(
data, getattr(self, '_dispatch', None), self.path)
except Exception, e:
self.send_response(500)
if (hasattr(self.server, '_send_traceback_header') and
self.server._send_traceback_header):
self.send_header('X-exception', str(e))
self.send_header('X-traceback', traceback.format_exc())
self.send_header('Content-length', '0')
self.end_headers()
else:
self.send_response(200)
self.send_header('Content-type', 'application/json')
if self.encode_threshold is not None:
if len(response) > self.encode_threshold:
q = self.accept_encodings().get('gzip', 0)
if q:
try:
response = jsonrpclib.gzip_encode(response)
self.send_header('Content-Encoding', 'gzip')
except NotImplementedError:
pass
self.send_header('Content-length', str(len(response)))
self.end_headers()
self.wfile.write(response)
class SimpleJSONRPCDispatcher(_base.SimpleXMLRPCDispatcher):
"""Dispatcher for received JSON-RPC requests.
This class extends the functionality of SimpleXMLRPCDispatcher and only
overrides the operations needed to change the protocol from XML-RPC to
JSON-RPC.
"""
def _marshaled_dispatch(self, data, dispatch_method=None, path=None):
"""Dispatches an JSON-RPC method from marshalled (JSON) data.
JSON-RPC methods are dispatched from the marshalled (JSON) data
using the _dispatch method and the result is returned as
marshalled data. For backwards compatibility, a dispatch
function can be provided as an argument (see comment in
SimpleJSONRPCRequestHandler.do_POST) but overriding the
existing method through subclassing is the preferred means
of changing method dispatch behavior.
Returns:
The JSON-RPC string to return.
"""
method = ''
params = []
ident = ''
try:
request = json.loads(data)
print 'request:', request
jsonrpclib.ValidateRequest(request)
method = request['method']
params = request['params']
ident = request['id']
if dispatch_method is not None:
response = dispatch_method(method, params)
else:
response = self._dispatch(method, params)
response = jsonrpclib.CreateResponseString(response, ident)
except jsonrpclib.Fault as fault:
response = jsonrpclib.CreateResponseString(fault, ident)
except:
exc_type, exc_value, _ = sys.exc_info()
response = jsonrpclib.CreateResponseString(
jsonrpclib.Fault(1, '%s:%s' % (exc_type, exc_value)), ident)
print 'response:', response
return response
class SimpleJSONRPCServer(SocketServer.TCPServer,
SimpleJSONRPCDispatcher):
"""Simple JSON-RPC server.
This class mimics the functionality of SimpleXMLRPCServer and only
overrides the operations needed to change the protocol from XML-RPC to
JSON-RPC.
"""
allow_reuse_address = True
_send_traceback_header = False
def __init__(self, addr, requestHandler=SimpleJSONRPCRequestHandler,
logRequests=True, allow_none=False, encoding=None,
bind_and_activate=True):
self.logRequests = logRequests
SimpleJSONRPCDispatcher.__init__(self, allow_none, encoding)
SocketServer.TCPServer.__init__(self, addr, requestHandler,
bind_and_activate)
if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)