jsonrpc - JSON-RPC 2.0 client and server
Import with import "jsonrpc.j" as jsonrpc;. A JSON-RPC 2.0 client that calls remote methods over HTTP, and a transport-agnostic server handle that dispatches an incoming request to your program's methods by name. Built on json for the wire format and http for the client transport, so it needs the default jennifer binary.
params and a call's result are json.Values: you build params with the json write API and read the result with the json accessors. Every client-side failure - a JSON-RPC error reply, a transport error (connection refused / timeout / malformed HTTP), or a malformed reply - surfaces as a single catchable Error{kind: "jsonrpc"}, so one catch covers them all.
import "jsonrpc.j" as jsonrpc;
use json;
def c as jsonrpc.Client init jsonrpc.client("https://api.example.com/rpc");
# positional params: [2, 3]
def args as json.Value init json.list();
$args = json.append($args, "", 2);
$args = json.append($args, "", 3);
def sum as json.Value init jsonrpc.call($c, "add", $args); # -> 5Runnable: examples/modules/jsonrpc_demo.j.
Client
A Client is an endpoint plus optional headers; every call POSTs a request and reads the reply.
def struct jsonrpc.Client { endpoint as string, headers as map of string to string };| Call | Returns | |
|---|---|---|
jsonrpc.client(endpoint) | Client | bind to a JSON-RPC endpoint URL |
jsonrpc.clientWith(endpoint, headers) | Client | with extra request headers (e.g. Authorization) |
jsonrpc.call(client, method, params) | json.Value | call method and return its result; throws Error{kind: "jsonrpc"} on an error reply, a transport error, or a malformed reply (no result/error, or a mismatched id) |
jsonrpc.notify(client, method, params) | send a notification (no id, no reply); returns nothing, but a transport error still throws Error{kind: "jsonrpc"} (the request never reached the server) |
params is a json.Value: a json.list for positional arguments or a json.map for named ones (pass json.list() for a method that takes none). An https:// endpoint uses TLS automatically.
# named params, and reading a structured result
def p as json.Value init json.set(json.map(), "/city", "berlin");
def w as json.Value init jsonrpc.call($c, "weather", $p);
io.printf("%d C\n", json.asInt($w, "/tempC"));Server
jsonrpc.handle(requestBody) turns a raw request into a reply body - it does the whole JSON-RPC protocol (single request, notification, batch, and every reserved error code) and leaves the transport to you: read the body off httpd or net, pass it to handle, write the returned string back.
Each request's method names a top-level method func NAME(params as json.Value) in the program that imported the module (dispatched by name via meta.callMain - the method name arrives on the wire, so it is resolved as a runtime string, not a func value). The handler is called with the request's params and returns a json.Value or a scalar (int / float / string / bool / null) as its result.
| Call | Returns | |
|---|---|---|
jsonrpc.handle(requestBody) | string | the reply JSON, or "" when none is owed (a notification, or an all-notification batch) |
# in the program that serves RPC:
func add(params as json.Value) {
return json.asInt($params, "/0") + json.asInt($params, "/1");
}
# ... in your accept loop, given the request body:
def reply as string init jsonrpc.handle($body);
if (len($reply) > 0) {
httpd.respond($req, 200, $reply); # a notification returns "" - send 204
}A missing method is a -32601 (method not found) reply; a handler that throws becomes a generic -32603 (internal error) reply - the thrown message stays server-side and is not put on the wire, so you can raise detailed errors without leaking internals. An unparseable body is -32700 (parse error). A handler must take exactly one json.Value parameter.
Security - the whole top-level namespace is exposed.
handleresolves a request'smethodagainst every top-levelfuncin the program (viameta.callMain); any one that takes a singlejson.Valueargument is remotely callable. There is no route allow-list likeweb's. So:
- Name RPC handlers deliberately - a shared prefix, or a dedicated dispatch file - and do not co-locate a
handle-served program with privileged one-argument helpers.- Authenticate at the transport: check a header / token before calling
handle; the module does no authentication of its own.- A batch is processed entry by entry with no size limit of its own (the transport's body cap bounds it); cap the request size upstream if untrusted.
Error codes
Constants for the reserved codes (JSON-RPC 2.0 section 5.1); application errors use the -32000..-32099 server-error range or any code outside the reserved block.
| Constant | Code | |
|---|---|---|
jsonrpc.PARSE_ERROR | -32700 | invalid JSON received |
jsonrpc.INVALID_REQUEST | -32600 | not a valid request object |
jsonrpc.METHOD_NOT_FOUND | -32601 | no such method |
jsonrpc.INVALID_PARAMS | -32602 | invalid method parameters |
jsonrpc.INTERNAL_ERROR | -32603 | internal JSON-RPC error |
Scope
- HTTP transport. The client speaks JSON-RPC over HTTP POST (the common case);
handleis transport-agnostic, so a raw-TCP / newline-framed server is a matter of wiring it tonet. - Client is single-call.
call/notifysend one request; the serverhandleaccepts a batch (a JSON array) on the receiving side. A client-side batch builder is a possible later add. - Handler result shape. A handler returns a
json.Valueor a scalar; a raw Jenniferlist/map/ struct is not coerced - build ajson.Valueresult explicitly. A custom per-handler error code (beyond the generic-32603) is a later add; today a thrown error maps to internal-error with a fixed message. - Client correlation.
callsends a fixed requestidand rejects a reply whoseiddoes not echo it. It is a synchronous single-call client, so there is no in-flight pipelining to correlate across.
See also
- json.md - the value type params and results are built from and read with.
- http.md - the client transport (
https://via TLS). - rest.md / web.md - the REST client and web framework, the other
http-backed request/response modules. - modules/index.md - the module catalog and import rules.