"""A simple JSON-RPC-like server.
|
|
|
|
The server will read and write lines of JSON-encoded method calls and
|
|
responses.
|
|
|
|
See the documentation of the JSONRPCServer class for further details.
|
|
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
import traceback
|
|
|
|
|
|
class JSONRPCServer(object):
|
|
"""Simple JSON-RPC-like server.
|
|
|
|
This class will read single-line JSON expressions from stdin,
|
|
decode them, and pass them to a handler. Return values from the
|
|
handler will be JSON-encoded and written to stdout.
|
|
|
|
To implement a handler, you need to subclass this class and add
|
|
methods starting with "rpc_". Methods then will be found.
|
|
|
|
Method calls should be encoded like this:
|
|
|
|
{"id": 23, "method": "method_name", "params": ["foo", "bar"]}
|
|
|
|
This will call self.rpc_method("foo", "bar").
|
|
|
|
Responses will be encoded like this:
|
|
|
|
{"id": 23, "result": "foo"}
|
|
|
|
Errors will be encoded like this:
|
|
|
|
{"id": 23, "error": "Simple error message"}
|
|
|
|
See http://www.jsonrpc.org/ for the inspiration of the protocol.
|
|
|
|
"""
|
|
|
|
def __init__(self, stdin=None, stdout=None):
|
|
"""Return a new JSON-RPC server object.
|
|
|
|
It will read lines of JSON data from stdin, and write the
|
|
responses to stdout.
|
|
|
|
"""
|
|
if stdin is None:
|
|
self.stdin = sys.stdin
|
|
else:
|
|
self.stdin = stdin
|
|
if stdout is None:
|
|
self.stdout = sys.stdout
|
|
else:
|
|
self.stdout = stdout
|
|
|
|
def read_json(self):
|
|
"""Read a single line and decode it as JSON.
|
|
|
|
Can raise an EOFError() when the input source was closed.
|
|
|
|
"""
|
|
line = self.stdin.readline()
|
|
if line == '':
|
|
raise EOFError()
|
|
return json.loads(line)
|
|
|
|
def write_json(self, **kwargs):
|
|
"""Write an JSON object on a single line.
|
|
|
|
The keyword arguments are interpreted as a single JSON object.
|
|
It's not possible with this method to write non-objects.
|
|
|
|
"""
|
|
self.stdout.write(json.dumps(kwargs) + "\n")
|
|
self.stdout.flush()
|
|
|
|
def handle_request(self):
|
|
"""Handle a single JSON-RPC request.
|
|
|
|
Read a request, call the appropriate handler method, and
|
|
return the encoded result. Errors in the handler method are
|
|
caught and encoded as error objects. Errors in the decoding
|
|
phase are not caught, as we can not respond with an error
|
|
response to them.
|
|
|
|
"""
|
|
request = self.read_json()
|
|
if 'method' not in request:
|
|
raise ValueError("Received a bad request: {0}"
|
|
.format(request))
|
|
method_name = request['method']
|
|
request_id = request.get('id', None)
|
|
params = request.get('params') or []
|
|
try:
|
|
method = getattr(self, "rpc_" + method_name, None)
|
|
if method is not None:
|
|
result = method(*params)
|
|
else:
|
|
result = self.handle(method_name, params)
|
|
if request_id is not None:
|
|
self.write_json(result=result,
|
|
id=request_id)
|
|
except Fault as fault:
|
|
error = {"message": fault.message,
|
|
"code": fault.code}
|
|
if fault.data is not None:
|
|
error["data"] = fault.data
|
|
self.write_json(error=error, id=request_id)
|
|
except Exception as e:
|
|
error = {"message": str(e),
|
|
"code": 500,
|
|
"data": {"traceback": traceback.format_exc()}}
|
|
self.write_json(error=error, id=request_id)
|
|
|
|
def handle(self, method_name, args):
|
|
"""Handle the call to method_name.
|
|
|
|
You should overwrite this method in a subclass.
|
|
"""
|
|
raise Fault("Unknown method {0}".format(method_name))
|
|
|
|
def serve_forever(self):
|
|
"""Serve requests forever.
|
|
|
|
Errors are not caught, so this is a slight misnomer.
|
|
|
|
"""
|
|
while True:
|
|
try:
|
|
self.handle_request()
|
|
except (KeyboardInterrupt, EOFError, SystemExit):
|
|
break
|
|
|
|
|
|
class Fault(Exception):
|
|
"""RPC Fault instances.
|
|
|
|
code defines the severity of the warning.
|
|
|
|
2xx: Normal behavior lead to end of operation, i.e. a warning
|
|
4xx: An expected error occurred
|
|
5xx: An unexpected error occurred (usually includes a traceback)
|
|
"""
|
|
def __init__(self, message, code=500, data=None):
|
|
super(Fault, self).__init__(message)
|
|
self.message = message
|
|
self.code = code
|
|
self.data = data
|