Moya JSON-RPC

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.

Installation

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.

Namespace

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.

Interfaces

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.

Methods

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>

Documenting Methods

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>

Parameters

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:

anything
No particular requirements for the parameter.
bool
The parameter should be a boolean, e.g. True or False.
list
The parameter should be a list, e.g [1, 2, 3, 4].
number
The parameter should be a number, e.g 3.14.
object
The parameter should be an object (dict in Moya), e.g. {'foo': 'bar'}.
string
The parameter should be a string, e.g. "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'

Errors

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>

Logging

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.

Javascript

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.

"success"
function success(result){}
Called when the remote method executes successfully.
"error"
function error(response) {}
Called when the remote method returns an error code.
"failure"
function failure(jqXHR, textStatus, errorThrown) {}
Called when there is a error making the remote call (and no remote method was called).
"complete"
function complete() {}
Called when the the call completes (regardless of whether it was successful), prior to other callbacks. This may be used to disable a throbber, for example.

Calls

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.

Notifications

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.

Batch Calls

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);