JSON-RPC is a way for the user's browser, or other computer systems, to communicate with your web application without the need for a page refresh – a technique known as AJAX. While not the only way to implement AJAX, the JSON-RPC protocol is powerful, well-supported and easy to work with in Javascript. The specification is worth a read, but you won't need to be familiar with the inner workings to implement remote methods.
The Moya JSON-RPC library is built in to Moya and may be imported via its Python path as follows:
<import py="moya.libs.jsonrpc"/>
You can install Moya JSON-RPC with the following:
<install name="jsonrpc" lib="moya.jsonrpc" />
Note that this library doesn't provide any mountpoints or views, so doesn't strictly require being installed at all. The purpose of installing it is to serve the a JQuery plugin which can call the remote methods. If don't want to use this, then you can leave out the install line entirely.
This library uses the namespace http://moyaproject.com/jsonrpc
. Since we will be mixing rpc methods with tags in the default namespace it is advisable to add xmlns:rpc="http://moyaproject.com/jsonrpc
to the top level tag, which will enable the rpc:
prefix.
The Moya JSON-RPC library handles the job of decoding requests, invoking methods (macros), and encoding the response. It can also generate a self-documenting index page.
To serve remote methods, add an <interface> tag. An interface is a container for your remote methods, and functions just like a view. You can mount an rpc interface in the same way you would mount a view, i.e. with the view
parameter on a <url>.
Here's an example of the simplest possible interface:
<moya xmlns="http://moyaproject.com"xmlns:rpc="http://moyaproject.com/jsonrpc"><rpc:interface libname="jsonrpc.interface"><!-- methods go here --></rpc:interface></moya>
We can mount this as follows:
<url route="/jsonrpc/" methods="GET,POST" view='#jsonrpc.interface' name="jsonrpc" />
Now if you were to visit /jsonrpc/
you would see the interface documentation page. Although without any methods (see below), it will not be particularly useful.
A method is Moya code that may be invoked via JSON-RPC. You can define a method with the <method> tag, which documents the method and sets the parameters the method will take, as well as containing the code the method will run.
To add a method to a interface, insert a <method> inside the <interface>. Alternatively, if you prefer to organize your methods differently you can put the <method> elsewhere and link it to the interface with the interface
attribute. Here's an example of a trivial method that gets a string containing the current time:
<rpc:interface libname="jsonrpc.interface"><rpc:method name="time"><return-str>${.now}</return-str></rpc:method></rpc:interface>
You can associate text with each method that will be displayed in the auto-generate documentation page, with the dpc
tag. Documentation is useful, even if the rpc methods are for internal use, but essential if third parties will be using your remote methods. Here's an example of adding a doc
to a remote method:
<rpc:interface libname="jsonrpc.interface"><rpc:method name="time"><doc>Get the current time.</doc><return-str>${.now}</return-str></rpc:method></rpc:interface>
If a method has parameters, the <method> tag should contain a <parameter> for each parameter. These are used to document the method and to validate remote calls. For example, parameters may be set as requried (with the required
attribute). You can also set a requirement on the type of the value in a remote call with the type
attribute, which should be one of the following values:
True
or False
.
[1, 2, 3, 4]
.
3.14
.
{'foo': 'bar'}
.
"moya"
.
If the method call doesn't match the parameter requirements, then Moya JSON-RPC will return an appropriate response code.
The following example builds on the previous method to get the server time, and adds a format
parameter to select the format of the returned time:
<rpc:interface libname="jsonrpc.interface"><rpc:method name="time"><rpc:parameter name="format" type="string" default="medium" required="yes">Time format to return</rpc:parameter><return-str>${.now::format}</return-str></rpc:method></rpc:interface>
If can now call this method with format
set to a datetime format, such as 'YYYY mm dd'
You can return an error response with the <error> tag which sets the error message, an error code with the code
attribute, and optional message data with the data
attribute. An <error> acts like a return and immediately stops processing the method.
For example, lets say we want to restrict the format parameter on the gettime example to one of four standard formats: short
, medium
, long
, and full
. We could add the following to the method, before the call to <return-str>:
<rpc:error code="100" if="format not in ['short', 'medium', 'long', 'full']">Format parameter is not correct</rpc:error>
Numerical error codes can be hard to keep track of, especially when the number of methods grows. To manage error codes, you can use an enumeration to map the integer on to a label. Here's how to create an enumeration the previous error:
<enum libname="enum.jsonrpc.errors"><value id="100" label="bad_time_format">The format for the time method should be on of: 'short', 'medium', 'long', 'full'</value></enum>
Error enumerations also make it possible for the auto-generated docs to list the error codes in a table.
To use this enumeration, set it on the the <interface> with the errors
attribute. Then you can replace the error code 100
with its corresponding label, bad_time_format
. Here's the complete gettime
method:
<rpc:interface libname="jsonrpc.interface"><enum libname="enum.jsonrpc.errors"><value id="100" label="bad_time_format">The format for the time method should be one of: 'short', 'medium', 'long', 'full'</value></enum><rpc:method name="time"><rpc:parameter name="format" type="string" default="medium" required="yes">Time format to return</rpc:parameter><rpc:error code="bad_time_format" if="format not in ['short', 'medium', 'long', 'full']">Format parameter is not correct</rpc:error><return-str>${.now::format}</return-str></rpc:method></rpc:interface>
Moya JSON-RPC writes information about each call and it's return value to the logger, moya.jsonrpc
. See Logging for information on configuring the logging system.
The Moya JSON-RPC library comes with a JQuery plugin to call remote methods from Javascript.
To load the JS file you can add the following to your content:
<include-js from="moya.jsonrpc" path="js/jsonrpc.js" />
Alternatively, you can add the following to your template:
<script type="text/javascript" src="{% media 'js/jsonrpc.js' from 'moya.jsonrpc' %}"></script>
This makes available a class called JSONRPC
, which you can create as follows:
var rpc = new JSONRPC('/jsonrpc/');
The constructor takes the URL of the JSON-RPC server, and an optional object containing default callbacks.
function success(result){}
function error(response) {}
function failure(jqXHR, textStatus, errorThrown) {}
function complete() {}
You can make calls with the call
method, which takes the method name, followed by the parameters and a function that processes the result. Here's an example which gets the time with the remote gettime
method we created:
rpc.call('gettime',{'format': 'long'},function(result){alert("The time is: " + result);});
You can also add an optional callbacks object as the fourth parameter, which can have the same callback functions as the constructor. Any callbacks supplied to the call
method will override those set in the constructor.
An alternative to call
is the notify
method which sends notifications. Notifications are remote calls where you aren't interested in the result. This is reflected in the success
parameter which will be invoked with no parameters.
You can make batch calls (multiple calls in a single requests) by calling createBatch
method which returns a new JSONRPCBatch
object. You may then use the call
and notifiy
methods on the batch object to store a list of method calls, and send then to the server with JSONRPC.batch
. Here's an example that calls the gettime
method with two different parameters, in a single request.
/* make a batch object */var batch = rpc.createBatch();function log_time(result){console.log(result);}/* Add calls to batch */batch.call('gettime', {'format':'medium'}, log_time);batch.call('gettime', {'format':'short'}, log_time);/* send calls to server and invoke callbacks */rpc.batch(batch);