Date: | 2007-05-06 |
---|---|
Web site: | http://89.106.94.126/jsonrpc/ |
Author: | Roland Koebler (r.koebler at yahoo dot de) |
"JSON-RPC is a lightweight remote procedure call protocol. It's designed to be simple!" [http://json-rpc.org/wiki/specification]
That's good.
But unfortunately, some useful things are missing in JSON-RPC 1.0, especially named parameters and maybe some definitions about error-messages. The JSON-RPC 1.1 Working Draft on the other side somehow overshoots the mark and makes things much more complicated. This already led to several discussions on the JSON-RPC Yahoo! Group.
The goal of this document is to propose a JSON-RPC-specification which enhances JSON-RPC 1.0, adds reasonable features from the 1.1WD, but still stays simple. This currently is only a working draft.
In my option, RPC consists of several independent parts:
Unfortunately, these parts are often not treated as independent, which results in unnecessary complex results [1]. A RPC-specification should only define point 1 ("data structure"), and tell the user which serialization to use [2].
[1] | (Have you ever tried to run i.e. XML-RPC over unix domain sockets? this does not work, because XML-RPC always uses http, although this would not be necessary.) |
[2] | Although requiring a specific serialization would not be absolutely necessary: It would also be possible to serialize XML-RPC-data-structures in JSON, or JSON-RPC-data-structures in XML. But I don't think that things like this are really used. |
The following sections mostly use the same headlines (and often same numbers) as the 1.1WD, if possible.
see also the may TODO tags all over this document.
The most important differences are:
The most important differences are:
Transport:
1.1WD requires HTTP.
This specification does not rely on any specific transport, so you can use i.e. unix domain sockets, tcp/ip, http, or even letters or avian carriers. This removes much of the complexity of the 1.1WD.
HTTP:
Nevertheless, if you want to use http, an appendix recommends how to use JSON-RPC over HTTP: simply tunnel JSON-RPC through HTTP.
HTTP then doesn't need to know anything about JSON-RPC. And I don't think that it's reasonable to i.e. convert JSON-RPC-errors to HTTP errors.
HTTP GET is discouraged. And if you really want to use HTTP GET, you should use a small wrapper, which converts HTTP GET calls to json-rpc.
The "User-Agent" header is not explicitly required anymore, because it does not provide any information for JSON-RPC.
Named and Positional Parameters, Null parameters:
1.1WD allows mixed named and positional parameters, which requires much more complicated servers and clients and adds some ambiguity. It also says that "Null ... as the value of any parameter ... MUST be taken to mean that the parameter is not being supplied for the call".
This specification allows either named or positional parameters, and it also optionally allows but discourages the use of both in one call. It also uses a separate objects for named and positional parameters.
"Null" is no special value, and a parameter-value of "Null" simply means that the parameter should be "Null".
Member Sequence:
1.1WD: the members of the JSON-RPC-object SHOULD be in a specific order, and allows the server to refuse requests which do not stick to this order.
This is removed in this specification.
Procedure Call Parity, Call Approximation:
Removed in this specification. A Service must be called exactly as specified.
(Things like "Call Approximation" and other magic are considered harmful, and may result in unexpected results. It's much better to require the client to say exactly what he wants than having a server which guesses.)
Error Object:
The "name" member was removed, because it does not provide any information. The error-code is no longer limited to 100-999, so that i.e. the xmlrpc-error-codes can be used.
Services, Service Description, Parameter and Return Type Strings:
service.description is now optional. Additional optional system-services were added (mainly those from xmlrpc).
The "Parameter and Return Type Strings" now SHOULD be used (instead of "MAY"), and it is not allowed to supply the Null value for all parameters.
Service, Procedure and Parameter Names:
Added underscore ("_") to, and removed hypen ("-") from the recommended characters. Additionally, added period (".") for methods, and everything is defined as case-sensitive.
Object Extensions:
This was removed in this specification.
Multicall:
This was added in this specification.
A remote procedure call is made by sending a request to a remote service. The request is expressed as a JSON Object, with the following members:
REQUIRED. A String containing the name of the procedure to be invoked.
Procedure names that begin with the word "system" followed by a period character (U+002E or ASCII 46) are reserved. In other words, a procedure named "system.foobar" is considered to have reserved semantics.
Unless a call is for notification purposes only, bearing one-way semantics, it MUST be replied to with a response.
Parameters for a procedure call can be identified by their name or position. The name and position of a parameter is defined by the formal argument list of the target procedure.
A client can specify parameters:
by-position: "params" contains the parameters in the right order, "kwparams" is omitted. This is like JSON-RPC 1.0.
Every JSON-RPC implementation MUST support this.
by-name: "kwparams" contains the parameter-names and its values, "params" is omitted. The names MUST match exactly (including in case) the names defined by the formal arguments.
Every JSON-RPC implementation SHOULD support this.
mixed: The usage of both "params" and "kwparams" in one call is discouraged. Nevertheless, a JSON-RPC implementation MAY support this. Then, if a parameter is both given as named and positional parameter, an error MUST be returned.
If the implementation does not support both in one call, it MUST return an error if called with both.
TODO: | specify which errors should be returned |
---|
Calls that identify the parameters by-name are RECOMMENDED over positional parameters in most cases, for the following reasons:
In small or simple applications however, positional parameters may sometimes be more appropriate.
--> data sent to service
<-- data coming from service
Suppose a remote procedure called sum that defines three formal arguments called a, b and c. Suppose further that a comes first in the argument list, b second and c third, as in "sum(a,b,c)". The following examples show how this procedure can be called.
--> { "version" : "1.1", "method" : "sum", "kwparams" : { "a" : 12, "b" : 34, "c" : 56 } }
Since parameters are identified by their name, the order is insignificant. The same MAY be expressed with parameters appearing in a different order than which is defined by the formal argument list:
--> {
"version" : "1.1",
"method" : "sum",
"kwparams" : { "b" : 34, "c" : 56, "a" : 12 }
}
If a client chooses to send parameters by their position, then it must use the params member of the procedure call object:
--> {"version" : "1.1", "method" : "sum", "params" : [ 12, 34, 56 ] }
Although this is discouraged, here is an example:
--> { "version": "1.1", "method": "sum", "params": [12,34], "kwparams":{"c": 56} }
When a remote procedure call is made, the service MUST reply with a response whether the invocation was successful or not.
TODO: | how about notifications ? |
---|
The response MUST be a JSON Object that carries the following properties or members:
REQUIRED. A String specifying the version of the JSON-RPC protocol to which the client conforms. An implementation conforming to this specification MUST use the exact String value of "1.1" for this member.
The absence of this member can effectively be taken to mean that the remote server implements version 1.0 of the JSON-RPC protocol.
Exactly one of "result" or "error" MUST be specified. It's not allowed to specify both or none.
When a remote procedure call fails, the Procedure Return object MUST contain the error member whose value is a JSON Object with the following members:
The following table lists the error codes defined by this specification:
code | message | Meaning |
---|---|---|
Server error | General error on the server, prior to procedure invocation. | |
Parse error | Invalid JSON / An error occured on the server while parsing the JSON text. | |
Bad call | The procedure call is not valid. | |
Service error | The call is valid, but a general error occurred during the procedure invocation. | |
Procedure not found | The call is valid but the procedure identified by the call could not be located on the service. |
TODO: | specify error-codes; maybe use the same error-codes as XML-RPC? |
---|
Suppose the example from Call Examples. The response to all these calls on success would be:
<-- { "version" : "1.1", "result" : 102 }
And here's a possible response on error:
<-- {
"version" : "1.1",
"error" : {
"code" : 123,
"message" : "An error occurred parsing the request object.",
"error" : {
"message" : "Bad array",
"at" : 42,
"text" : "{\"id\":1,\"method\":\"sum\",\"params\":[1,2,3,4,5}"
}
}
}
TODO: | use the defined errors/error-codes as soon as they are defined. |
---|
Procedure names that begin with "system." are reserved (see Procedure Call (Request)).
A server MAY implement the following system-services. It SHOULD at least implement "system.listMethods" and "system.methodHelp".
This method takes one parameter, the name of a method implemented by the XML-RPC server. It returns an array of possible signatures for this method. A signature is an array of types. The first of these types is the return type of the method, the rest are parameters.
Because multiple signatures (i.e. overloading) is permitted, this method returns a list of signatures rather than a singleton.
Signatures themselves are restricted to the top level parameters expected by a method. For instance if a method expects one Array of Objects as a parameter, and it returns a String, its signature is simply "str, arr". If it expects three Numbers and returns a String, its signature is "str, num, num, num".
If no signature is defined for the method, Null is returned.
This method takes one parameter, the name of a method implemented by the RPC server. It returns a documentation string describing the use of that method. If no such string is available, an empty string is returned.
The documentation string may contain markup.
TODO: | Maybe it would be reasonable to define a simplified version of "describe". |
---|---|
TODO: | describe/define these services in detail. |
The 3 services "listMethods", "methodSignature" and "methodHelp" are nearly the same as in XML-RPC (see http://xml-rpc.org or http://docs.python.org/lib/serverproxy-objects.html).
The type member of the Procedure Parameter Description SHOULD be one of the following String values:
TODO: | SHOULD or MUST? |
---|
If another String value is found then it MUST be treated the same as "any".
The "nil" string MUST NOT be used to describe the type of a procedure's formal argument. Rather, it is strictly reserved to denote the return type of a procedure that is not expected to produce a result. In other words, the result member of the Procedure Return object resulting from a call to such procedure is not interesting because it will always be the Null value.
In JSON-RPC, service, procedure, parameters identifiable by name. The names of all these artifacts SHOULD only include:
All other characters in a name, although not technically excluded here, could severely limit the reach of the service and its procedures given certain environments, scenarios and especially programming languages.
All names etc. are case-sensitive. Conforming implementations therefore MUST treat all names as being case-sensitive such the names "bar" and "BAR" would be seen as two distinct entities.
All extensions are OPTIONAL.
There is a special method called "system.multicall", like in XMLRPC. This method takes the single calls as positional parameters (in "params"). The result is a list of the single-call results.
Call Example:
--> { "version":"1.1", "method":"system.multicall", "params":
[{ "version":"1.1", "method":"sum", "params":{"a":1,"b":1} },
{ "version":"1.1", "method":"sum", "params":{"a":2,"b":2} },
{ "version":"1.1", "method":"sum", "params":{"a":3,"b":3} }]
}
Return Example on success:
<-- { "version":"1.1", "result":
[{ "version":"1.1", "result":2 },
{ "version":"1.1", "result":4 },
{ "version":"1.1", "result":6 }]
}
Return Example with some errors:
<-- {"version":"1.1", "result":
[{"version":"1.1", "error":{"code":..., "message":"..."}},
{"version":"1.1", "result":4},
{"version":"1.1", "error":{"code":..., "message":"..."}}]
}
Return Example if the multicall itself fails:
<-- {"version":"1.1", "error":{"code":..., "message":"Parse error"}}
JSON-RPC does not depend on any specific transport (see also Differences From 1.1WD). Any transport should be possible.
Nevertheless, here are some recommendations how to use JSON-RPC over some commonly-used transports.
This is straight-forward: open a socket and send/receive the data.
This is straight-forward: open a socket and send/receive the data.
If you want to use HTTP, simply tunnel JSON-RPC through HTTP with Content-Type "application/json".
TODO: | should "application/json" be changed to "application/jsonrpc"? |
---|
The HTTP request message MUST specify the following headers:
The Request itself is carried in the body of the HTTP message.
Example:
POST /myservice HTTP/1.1
User-Agent: Wget/1.6
Host: www.example.com
Content-Type: application/json
Content-Length: ...
Accept: application/json
{
"version" : "1.1",
"method" : "sum",
"kwparams" : { "b" : 34, "c" : 56, "a" : 12 }
}
The use of JSON-RPC directly over HTTP GET is NOT RECOMMENDED, since this would mean to encode the JSON-string of the Request in the URL, which would be ugly, cause several problems, and is not what most people would expect when thinking of HTTP GET.
But if you need to call JSON-RPC-services via HTTP GET, use a wrapper which accepts a HTTP GET request, decides if a JSON-RPC-service should be called and then converts it to JSON-RPC-request-syntax. It could additionally mangle the JSON-RPC-response before returning the result (via HTTP).
But note that (according to HTTP Section 9.1, Safe and Idempotent Methods) only procedures that are considered safe and idempotent MAY be invoked using HTTP GET.
The HTTP return message MUST specify the following headers:
The status code MUST be 200, except for HTTP errors.
The Response (both on success and error) is carried in the HTTP body.
Example on Success:
HTTP/1.1 200 OK
Connection: close
Content-Length: ...
Content-Type: application/json
Date: Sat, 08 Jul 2006 12:04:08 GMT
{
"version" : "1.1",
"result" : 102
}
Example on Error:
HTTP/1.1 200 OK
Connection: close
Content-Length: ...
Content-Type: application/json
Date: Sat, 08 Jul 2006 12:04:08 GMT
{
"version" : "1.1",
"error" : {
"code" : 123,
"message" : "An error occurred parsing the request object.",
"error" : {
"message" : "Bad array",
"at" : 42,
"text" : "{\"id\":1,\"method\":\"sum\",\"params\":[1,2,3,4,5}"}
}
}
}
TODO: | add correct Content-Length |
---|