The JSON-RPC protocol
Example
The following example shows how to use the
JSONRPCProtocol class in a custom
application, without using any other components:
Server
from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
from tinyrpc import BadRequestError, RPCBatchRequest
rpc = JSONRPCProtocol()
# the code below is valid for all protocols, not just JSONRPC:
def handle_incoming_message(self, data):
try:
request = rpc.parse_request(data)
except BadRequestError as e:
# request was invalid, directly create response
response = e.error_respond(e)
else:
# we got a valid request
# the handle_request function is user-defined
# and returns some form of response
if hasattr(request, create_batch_response):
response = request.create_batch_response(
handle_request(req) for req in request
)
else:
response = handle_request(request)
# now send the response to the client
if response != None:
send_to_client(response.serialize())
def handle_request(request):
try:
# do magic with method, args, kwargs...
return request.respond(result)
except Exception as e:
# for example, a method wasn't found
return request.error_respond(e)
Client
from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
rpc = JSONRPCProtocol()
# again, code below is protocol-independent
# assuming you want to call method(*args, **kwargs)
request = rpc.create_request(method, args, kwargs)
reply = send_to_server_and_get_reply(request)
response = rpc.parse_reply(reply)
if hasattr(response, 'error'):
# error handling...
else:
# the return value is found in response.result
do_something_with(response.result)
Another example, this time using batch requests:
# or using batch requests:
requests = rpc.create_batch_request([
rpc.create_request(method_1, args_1, kwargs_1)
rpc.create_request(method_2, args_2, kwargs_2)
# ...
])
reply = send_to_server_and_get_reply(request)
responses = rpc.parse_reply(reply)
for responses in response:
if hasattr(reponse, 'error'):
# ...
Finally, one-way requests are requests where the client does not expect an answer:
request = rpc.create_request(method, args, kwargs, one_way=True)
send_to_server(request)
# done
Protocol implementation
API Reference
- class tinyrpc.protocols.jsonrpc.JSONRPCProtocol(*args, **kwargs)
Bases:
RPCBatchProtocolJSONRPC protocol implementation.
- JSON_RPC_VERSION = '2.0'
Currently, only version 2.0 is supported.
- request_factory()
Factory for request objects.
Allows derived classes to use requests derived from
JSONRPCRequest.- Return type:
- create_batch_request(requests=None)
Create a new
RPCBatchRequestobject.Called by the client when constructing a request.
- Parameters:
requests (
listofRPCRequest) – A list of requests.- Returns:
A new request instance.
- Return type:
RPCBatchRequest
- create_request(method, args=None, kwargs=None, one_way=False)
Creates a new
JSONRPCRequestobject.Called by the client when constructing a request. JSON RPC allows either the
argsorkwargsargument to be set.- Parameters:
- Returns:
A new request instance.
- Return type:
- Raises:
InvalidRequestError – when
argsandkwargsare both defined.
- parse_reply(data)
Deserializes and validates a response.
Called by the client to reconstruct the serialized
JSONRPCResponse.- Parameters:
data (bytes) – The data stream received by the transport layer containing the serialized request.
- Returns:
A reconstructed response.
- Return type:
- Raises:
InvalidReplyError – if the response is not valid JSON or does not conform to the standard.
- parse_request(data)
Deserializes and validates a request.
Called by the server to reconstruct the serialized
JSONRPCRequest.- Parameters:
data (bytes) – The data stream received by the transport layer containing the serialized request.
- Returns:
A reconstructed request.
- Return type:
- Raises:
JSONRPCParseError – if the
datacannot be parsed as valid JSON.JSONRPCInvalidRequestError – if the request does not comply with the standard.
- raise_error(error)
Recreates the exception.
Creates a
JSONRPCErrorinstance and raises it. This allows the error, message and data attributes of the original exception to propagate into the client code.The
raises_errorflag controls if the exception obejct is raised or returned.- Returns:
the exception object if it is not allowed to raise it.
- Raises:
JSONRPCError – when the exception can be raised. The exception object will contain
message,codeand optionally adataproperty.
- class tinyrpc.protocols.jsonrpc.JSONRPCRequest
Bases:
RPCRequest- one_way
Request or Notification.
- Type:
This flag indicates if the client expects to receive a reply (request:
one_way = False) or not (notification:one_way = True).Note that according to the specification it is possible for the server to return an error response. For example if the request becomes unreadable and the server is not able to determine that it is in fact a notification an error should be returned. However, once the server had verified that the request is a notification no reply (not even an error) should be returned.
- unique_id
Correlation ID used to match request and response.
Generated by the client, the server copies it from request to corresponding response.
- method
The name of the RPC function to be called.
- Type:
The
methodattribute uses the name of the function as it is known by the public. TheRPCDispatcherallows the use of public aliases in the@publicdecorators. These are the names used in themethodattribute.
- args
The positional arguments of the method call.
- Type:
The contents of this list are the positional parameters for the
methodcalled. It is eventually called asmethod(*args).
- kwargs
The keyword arguments of the method call.
- Type:
The contents of this dict are the keyword parameters for the
methodcalled. It is eventually called asmethod(**kwargs).
- error_respond(error)
Create an error response to this request.
When processing the request produces an error condition this method can be used to create the error response object.
- Parameters:
- Returns:
An error response object that can be serialized and sent to the client.
- Return type:
- respond(result)
Create a response to this request.
When processing the request completed successfully this method can be used to create a response object.
- Parameters:
result (Anything that can be encoded by JSON.) – The result of the invoked method.
- Returns:
A response object that can be serialized and sent to the client.
- Return type:
- class tinyrpc.protocols.jsonrpc.JSONRPCSuccessResponse
Bases:
RPCResponseCollects the attributes of a succesful response message.
Contains the fields of a normal (i.e. a non-error) response message.
- unique_id
Correlation ID to match request and response. A JSON RPC response must have a defined matching id attribute.
Noneis not a valid value for a successful response.
- result
Contains the result of the RPC call.
- Type:
Any type that can be serialized by the protocol.
- class tinyrpc.protocols.jsonrpc.JSONRPCErrorResponse
Bases:
RPCErrorResponseCollects the attributes of an error response message.
Contains the fields of an error response message.
- unique_id
Correlation ID to match request and response.
Noneis a valid ID when the error cannot be matched to a particular request.
- data
This field may contain any JSON encodable datum that the server may want to return the client.
It may contain additional information about the error condition, a partial result or whatever. Its presence and value are entirely optional.
- Type:
Any type that can be serialized by the protocol.
- _json_rpc_error
The numeric error code.
The value is usually predefined by one of the JSON protocol exceptions. It can be set by the developer when defining application specific exceptions. See
FixedErrorMessageMixinfor an example on how to do this.Note that the value of this field must comply with the defined values in the standard.
Batch protocol
API Reference
- class tinyrpc.protocols.jsonrpc.JSONRPCBatchRequest(iterable=(), /)
Bases:
RPCBatchRequest- create_batch_response()
Creates a response suitable for responding to this request.
This is an abstract method that must be overridden in a derived class.
- Returns:
An
RPCBatchResponseor None if no response is expected.- Return type:
RPCBatchResponseor None
- class tinyrpc.protocols.jsonrpc.JSONRPCBatchResponse(iterable=(), /)
Bases:
RPCBatchResponse
Errors and error handling
API Reference
- class tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin(*args, **kwargs)
Bases:
objectCombines JSON RPC exceptions with the generic RPC exceptions.
Constructs the exception using the provided parameters as well as properties of the JSON RPC Exception.
JSON RPC exceptions declare two attributes:
- jsonrpc_error_code
This is an error code conforming to the JSON RPC error codes convention.
- Type:
- Parameters:
FixedErrorMessageMixinis the basis for adding your own exceptions to the predefined ones. Here is a version of the reverse string example that dislikes palindromes:class PalindromeError(FixedErrorMessageMixin, Exception) jsonrpc_error_code = 99 message = "Ah, that's cheating" @public def reverse_string(s): r = s[::-1] if r == s: raise PalindromeError(data=s) return r
>>> client.reverse('rotator')
Will return an error object to the client looking like:
{ "jsonrpc": "2.0", "id": 1, "error": { "code": 99, "message": "Ah, that's cheating", "data": "rotator" } }
- error_respond()
Converts the error to an error response object.
- Returns:
An error response object ready to be serialized and sent to the client.
- Return type:
- class tinyrpc.protocols.jsonrpc.JSONRPCParseError(*args, **kwargs)
Bases:
FixedErrorMessageMixin,InvalidRequestErrorThe request cannot be decoded or is malformed.
- class tinyrpc.protocols.jsonrpc.JSONRPCInvalidRequestError(*args, **kwargs)
Bases:
FixedErrorMessageMixin,InvalidRequestErrorThe request contents are not valid for JSON RPC 2.0
- class tinyrpc.protocols.jsonrpc.JSONRPCMethodNotFoundError(*args, **kwargs)
Bases:
FixedErrorMessageMixin,MethodNotFoundErrorThe requested method name is not found in the registry.
- class tinyrpc.protocols.jsonrpc.JSONRPCInvalidParamsError(*args, **kwargs)
Bases:
FixedErrorMessageMixin,InvalidRequestErrorThe provided parameters are not appropriate for the function called.
- class tinyrpc.protocols.jsonrpc.JSONRPCInternalError(*args, **kwargs)
Bases:
FixedErrorMessageMixin,InvalidRequestErrorUnspecified error, not in the called function.
- class tinyrpc.protocols.jsonrpc.JSONRPCServerError(*args, **kwargs)
Bases:
FixedErrorMessageMixin,InvalidRequestErrorUnspecified error, this message originates from the called function.
- class tinyrpc.protocols.jsonrpc.JSONRPCError(error)
Bases:
FixedErrorMessageMixin,RPCErrorReconstructs (to some extend) the server-side exception.
The client creates this exception by providing it with the
errorattribute of the JSON error response object returned by the server.- Parameters:
error (dict) –
This dict contains the error specification:
code (int): the numeric error code.
message (str): the error description.
data (any): if present, the data attribute of the error
Adding custom exceptions
Note
As per the specification you should use error codes -32000 to -32099 when adding server specific error messages. Error codes outside the range -32768 to -32000 are available for application specific error codes.
To add custom errors you need to combine an Exception subclass
with the FixedErrorMessageMixin class to create your exception
object which you can raise.
So a version of the reverse string example that dislikes palindromes could look like:
from tinyrpc.protocols.jsonrpc import FixedErrorMessageMixin, JSONRPCProtocol
from tinyrpc.dispatch import RPCDispatcher
dispatcher = RPCDispatcher()
class PalindromeError(FixedErrorMessageMixin, Exception):
jsonrpc_error_code = 99
message = "Ah, that's cheating!"
@dispatcher.public
def reverse_string(s):
r = s[::-1]
if r == s:
raise PalindromeError()
return r
Error with data
The specification states that the error element of a reply may contain
an optional data property. This property is now available for your use.
There are two ways that you can use to pass additional data with an Exception.
It depends whether your application generates regular exceptions or exceptions derived
from FixedErrorMessageMixin.
When using ordinary exceptions you normally pass a single parameter (an error message)
to the Exception constructor.
By passing two parameters, the second parameter is assumed to be the data element.
@public
def fn():
raise Exception('error message', {'msg': 'structured data', 'lst': [1, 2, 3]})
This will produce the reply message:
{ "jsonrpc": "2.0",
"id": <some id>,
"error": {
"code": -32000,
"message": "error message",
"data": {"msg": "structured data", "lst": [1, 2, 3]}
}
}
When using FixedErrorMessageMixin based exceptions the data is passed using
a keyword parameter.
class MyException(FixedErrorMessageMixin, Exception):
jsonrcp_error_code = 99
message = 'standard message'
@public
def fn():
raise MyException(data={'msg': 'structured data', 'lst': [1, 2, 3]})
This will produce the reply message:
{ "jsonrpc": "2.0",
"id": <some id>,
"error": {
"code": 99,
"message": "standard message",
"data": {"msg": "structured data", "lst": [1, 2, 3]}
}
}