Overview
This tutorial demonstrates how Mongoose Library can be used to implement JSON-RPC functionality over Websocket:
- Start a server that hosts a simple web page
- A web page starts a Websocket connection to the server
- On that websocket connection, a web page and a backend exchange JSON-RPC frames
- A web page receives a real-time notifications (RPC frame without
id
) - A web page can send RPC requests to a call RPC functions on the backend
A full source code for this tutorial is at https://github.com/cesanta/mongoose/tree/master/examples/json-rpc-over-websocket
Why JSON-RPC
JSON-RPC is a remote procedure call protocol encoded in JSON. Here is an example request and response:
{"id": 12, "method": "Sum", "params": [2,3]} // Request
{"id": 12, "result": 5} // Response
One might ask, why do we need it? Isn't RESTful capabilities not enough? RESTful is doing the same thing, request/response and it can also wrap data into JSON, right? Right. But there are few differences, some of them can be crucial and be a decision factor.
- Notifications. With RESTful, there is no straight way to send
notifications from server to client. With JSON-RPC, any side at any point can
send requests. JSON-RPC requests without
id
do not require responses, thus they can be treated as notifications. - Performance. A single Websocket connection, once established, can be used to send multiple requests in either direction. With RESTful, requests are either pipelined or require multiple connections.
In this tutorial, we present a simple JavaScript JSON-RPC client implementation that can send requests to the server and receive asynchronous real-time notifications as well.
Build and test
Assuming we're on a Mac or Linux workstation, start a new terminal and execute the following to build and start the example:
$ git clone https://github.com/cesanta/mongoose
$ cd mongoose/examples/json-rpc-over-websocket
$ make
cc main.c mjson.c ../../mongoose.c -I../.. -W -Wall -DMG_ENABLE_LINES=1 -o example
./example
Starting WS listener on ws://localhost:8000/websocket
Start your browser on URL http://localhost:8000 - and you should see a simple web page:

Click on "connect" button. A JavaScript code on the web page starts a JSON-RPC instance, which makes a Websocket connection to the server. Wait for a couple of seconds, see a notification from the server:

Now click on the buttons. Each click trigger a JSON-RPC request. In the event
log, you can see a response frame. Notice that for each request, a new id
is generated. This allows client to differentiate responses, cause they can
come out of order.

Notice log message on the terminal window that runs the server:
2021-11-23 10:05:06 2 main.c:52:fn [{"id":0,"method":"sum","params":[1,2]}] -> [{"id":0,"result":3}
]
2021-11-23 10:05:09 2 main.c:52:fn [{"id":1,"method":"mul","params":[2,3]}] -> [{"id":1,"result":6}
]
Client side
First, let's see what's on the client side. A JavaScript code in the browser is simple and straightforward. We get get elements that present on the page. On a "connect" button click, start a RPC instance:
The JSON-RPC client code is located in a different file:
The jsonrpc()
call accepts a Websocket URL, and 3 callbacks: when a Websocket
connection opens, when it closes, and when a JSON-RPC notification arrives.
It return an object with two methods, close()
and call()
. The close()
method simply closes Websocket connection - we need that if user clicks on
a "disconnect" button. The call()
method is triggered when user presses on
a "calculation" buttons. Note that call parameters are hardcoded just to
make an example simpler:
Server side
On the server side, we use mjson library
which adds mjson.h
and mjson.c
files to the source code. A server
initialisation is as usual, with two extra additions - we register JSON-RPC
instance using mjson API.
In the JSON RPC instance, we register two functions
sum()
and mul()
to add and multiply numbers, here are their callbacks:
Another thing we do is we register a timer which kicks in every 5 seconds and sends JSON-RPC notifications to all connected clients:
At lastly, the event handler. It is quite simple:
If a connection is still HTTP, we upgrade it to a Websocket if the URI
i /webscket
- unless, we serve static content from a web_root
directory.
When a Websocket connection gets established, we set a mark in c->label
to
let timer function know who should be notified. When a Websocket frame is
received from a client, we call jsonrpc_process()
to create a response,
and send it back.