Introduction
JSONsvc defines a simple format for data to be sent between a Websocket server and its clients. It allows browser clients to view the connection as a type of "proxy-peer-to-peer" network. i.e. any client can send data to another client as if they were directly connected - the websocket server does the routing.
The protocol also allows multiple clients to automagically receive data they are interested in whenever anyone else sends it - again routed by the websocket server.
The intended interest here is in the use of general-purpose protocol with a server, rather than some ad-hoc scheme which means you would have to change client and server code to add or change any functionality.
Background
This is a companion article to "Using Sec-Websocket-Protocol" which discussed the use of different protocols with websockets. This article describes a 'JSONsvc' protocol which could be used by any websocket server or client written in any language. The example implementation of the server happens to be in PHP with client code in HTML/javascript. For more information the use of Sec-Websocket-Protocol and implementation of the server, see the other article.
The JSONsvc protocol
JSONsvc allows clients to register for named 'services' (i.e. bits of data) and to be a 'service provider' if needed. Whenever a 'service provider' sends data for a 'service', the JSON2svc sends that data to all clients who are registered for it. The data is sent via a websocket opened with protocol: "JSONsvc"
The protocol handles data like this:
{ header_json }
or { header_json }>SERVICEDATA i.e. a header '>' and some service data
the header can contain the following key:value pairs:
"to":name = name of destination client or 'JSONsvc'
"from":name = name of the sender - required
"provides":service_name = service provided
"requests":service_name = service client wants sent automatically
"get":service_name = request a service as soon as possible
"put":service_name = deliver a service (could have >SERVICEDATA attached)
note:"provides","requests" and "get" can also accept an array of service names
e.g. "provides":["this_service","that_service"]
So a client could send:
{ "from":"Bob", "requests":["info","updates"], "get":"info", "provides":"junk" }
which identifies the client as 'Bob'
and that he wants to receive automatic updates whenever something provides 'info'
or 'updates'
. Since Bob has just started up, he also wants to "get":"info"
as soon as possible, he also registers as a provider of 'junk'
.
If another client, 'Alice'
, has registered as a provider of 'info'
, she would be automatically sent:
{ "from":"Bob", "to":"Alice" "get":"info" }
to which she is expected to reply: (to the websocket server, of course)
{ "from":"Alice", "to":"Bob" "put":"info" }>{.... whatever the info is ....}
and the server automatically forwards the whole lot to 'Bob'.
If there are multiple clients with the same name or have requested the same info, then they all get a copy.
Note that the embedded '>'
character is to allow the server to extract the header without knowing anything about the data (which may or may not actually be in JSON format). This is fairly important when the data is actually an umpteen megabyte file.
Since Bob
has sent "requests":["info","updates"]
, he will get those data services automatically whenever another client provides them. In the mean time he could make ad-hoc requests of any data service by issuing a "get":"servicename"
Service providers
In our example above, Bob
has also registered as a provider of "junk"
, so if any other client requested that service he could receive...
{ "from":"some_user", "to":"Bob" "get":"junk" }
...to which he is expected to reply with whatever 'junk' is.
Actually, Bob may not be the only provider of 'junk' connected to the server - any request is simply sent to the first provider the server finds in its list of 'junk providers'. In my application this is intended for delivering stuff from two or more client apps which share data (using another service)- if one becomes unavailable, the other can satisfy the 'junk' request.
"put" data format
It was originally intended for the 'JSONDATA' following the header to actually be in JSON format, however, since websockets are quite capable of sending arbitrary binary data it has been useful to leave the format of that data to the 'service provider'.
Currently you can do something like this:
{"from":"fred", "put":"fileservice", "filename":"somefile.xyz" }>... binary data...
If you were generalizing the protocol, you'd probably want to strictly define some more keywords like the "filename"
above. (You may wonder what a javascript client is going to do with a binary file - but remember that the websocket client could be anything: a cgi script, some desktop application or another type of websocket server
For sending data to be used by javascript, you'd probably stick to JSON, just separate that data from the websocket frame using something like (keeping it simple here):
host="ws://some_server/";
socket= new WebSocket( host, "JSONsvc");
socket.onopen = function(msg){
socket.send( '{"from":"me", "requests":["alert","UserList"]}' );
}
socket.onmessage = function (msg) {
var separatorIndex=msg.data.indexOf('>');
if( separatorIndex > 0 )
header= JSON.parse( msg.data.slice(0,separatorIndex) );
data= JSON.parse( msg.data.slice(separatorIndex+1) );
}else{
header= JSON.parse( msg.data );
data= someDefaultThing;
}
if( header.put == "alertService" ) alert( data.msg );
else if( header.put =="UserList" ) updateUserList( data );
else ...
}
The example websocket2.html demonstrates JSONsvc in a simplistic fashion by implementing a 'chat' server. Using the example; start JSONsvc protocol and type a message - the resulting data is shown. If you then click 'Log decoded' and open several browsers to talk to yourself (!), each client is auto-updated with messages and current client list.
In websocket2.html the JSONsvc request is formed by:
request= {from:"Anonymous", requests:["text","JSONsvc_ClientList"], put:"text" };
if( username != "" )
request.from= username_from_textbox;
msg= JSON.stringify( request ) + ">" + message_from_textbox;
sending the request results in:
Send: {"from":"Bob","requests":["text","JSONsvc_ClientList"],"put":"text"}>Hello there
Recv: {"from":"Bob","put":"text","to":"Bob"}>Hello there
Recv: {"from":"JSONsvc","put":"JSONsvc_ClientList","to":"Bob"}>["Alice","Bob","Carol"]
here we received two messages, the first is an echo of what we sent (because we provided and requested the "text"
service), the second because we requested "JSONsvc_ClientList"
which sends the list of connected clients whenever it changes. If another user sends a message we may get:
Recv: {"from":"Carol","put":"text","to":"Bob"}>hi from Carol
A more 'restful' example
A project which needs real-time communication between users who control a group of Emergency Response volunteers is looking at using Websockets with JSONsvc to serve status information pages. In this case we can do things like:
{ "from","viewpage", "requests":["alerts","pageItems"], "get":["ItemNames","pageItems"]}
{ "from","JSONsvc", "to","viewpage", "put":"ItemNames"}>{"name":40,"status":100}
{ "from","JSONsvc", "to","viewpage", "put":"pageItems"}>
{"name":"Fred Bloggs", "status","off duty"}
The javascript uses the "pageItemNames"
to build a page view for that list of items and whenever it receives "pageItems"
it fills in the data. In this way the javascript does not need to know anything about the content of the central database and will refresh the page whenever something sends an updated version of "pageItems"
. This example also registers for "alerts"
which may be used for urgent 'popup' messages. (in reality there's a few more tags added to identify pages, provide screen legends, tag editable items and so on)
JSONsvc2.php
This is the example implementation of the "JSONsvc" protocol for a websocket server.
The code happens to be written in PHP, as is the example server, but I think it's pretty straightforward and doesn't take long to port to C, java/script or anything else. (e.g. it took a couple of evenings to re-implement this in C and embed it with a Civetweb server - well... it's nearly working
Finally
Most of the websocket examples I've seen so far simply sends strings or some arbitrary JSON encoded stuff from
point-to-point rather than considering the pseudo
peer-to-peer possibilities. So I hope this has given you some ideas on how to design and implement your own flexible data transport over websockets.
Yours,
TonyWilk
History
Example version is 1v2
Native browser support for websockets v13 at 1st Jan 2014:
PC: Internet Explorer 11, Chrome 32, Firefox 26.0 and Android: Chrome 31, Opera 18
on windows pc/laptop, Samsung S3/4, Amazon Fire and a Nexus 7