
WebSocket RPC or how to write a live WEB browser application

The article will focus on WebSocket technology. More precisely, not about the technology itself, but about how it can be used. I have been following her for a long time. Even when in 2011, one of my colleagues sent me a link to the standard , having run my eyes, I was somehow upset. It looked so cool, and I thought that at the moment when it appears in popular browsers, I will already plan what to spend my pension on. But everything turned out to be wrong, and as caniuse.com says, WebSocket is not supported only in Opera Mini (it would be necessary to vote, how long had anyone seen Opera Mini).
Those who touch WebSockets with their hands probably know that working with the API is hard. The Javascript API is quite low-level (accept a message - send a message), and you will have to develop an algorithm for how to exchange these messages. Therefore, an attempt was made to simplify the work with web sockets.
So WSRPC appeared . For the impatient, here is a simple demo .
Idea
The basic idea is to give the developer a simple Javascript API like:
var url = (window.location.protocol==="https:"?"wss://":"ws://") + window.location.host + '/ws/';
RPC = WSRPC(url, 5000);
// Инициализируем объект
RPC.call('test').then(function (data) {
// посылаем аргументы как *args
RPC.call('test.serverSideFunction', [1,2,3]).then(function (data) {
console.log("Server return", data)
});
// Объект как аргументы **kwargs
RPC.call('test.serverSideFunction', {size: 1, id: 2, lolwat: 3}).then(function (data) {
console.log("Server return", data)
});
});
// Если с сервера придет вызов 'whoAreYou', вызовем следующую функцию
// ответим на сервер то, что после return
RPC.addRoute('whoAreYou', function (data) {
return window.navigator.userAgent;
});
RPC.connect();
And in python:
import tornado.web
import tornado.httpserver
import tornado.ioloop
import time
from wsrpc import WebSocketRoute, WebSocket, wsrpc_static
class ExampleClassBasedRoute(WebSocketRoute):
def init(self, **kwargs):
return self.socket.call('whoAreYou', callback=self._handle_user_agent)
def _handle_user_agent(self, ua):
print ua
def serverSideFunction(self, *args, **kwargs):
return args, kwargs
WebSocket.ROUTES['test'] = ExampleClassBasedRoute
WebSocket.ROUTES['getTime'] = lambda: time.time()
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(tornado.web.Application((
# Генерирует url со статикой q.min.js и wsrpc.min.js
# (подключать в том же порядке)
wsrpc_static(r'/js/(.*)'),
(r"/ws/", WebSocket),
(r'/(.*)', tornado.web.StaticFileHandler, {
'path': os.path.join(project_root, 'static'),
'default_filename': 'index.html'
}),
))
http_server.listen(options.port, address=options.listen)
WebSocket.cleapup_worker()
tornado.ioloop.IOLoop.instance().start()
Features
I’ll explain some points about how this works.
Javascript
The browser initializes a new RPC object, after which we call the methods, but WebSocket has not yet connected. It doesn’t matter, the calls are in the queue that we rake at a successful connection, or we reject all promises, clearing the queue at the next failed connection. The library always tries to connect to the server (you can also subscribe to RPC.addEventListener (“onconnect”, func) events of connection and disconnection. But until we run RPC.connect (), we peacefully queue calls inside RPC.
After the connection, we serialize our parameters in JSON and send a message of the form to the server:
{"serial":3,"call":"test","arguments": null}
What the server answers:
{"data": {}, "serial": 3, "type": "callback"}
where serial is the call number.
After receiving the response, the JS library resolves the promise, and we call what then. After that, we make another call, and so on ...
I also note that any time can elapse between the call and the answer to it.
Python
In Python, calls are recorded in a WebSocket object. The class attribute (class-property) ROUTES is a dictionary (dict) that stores an association of what the call is called and what function or class it serves.
If a function is specified, it is simply called, and its result is passed to the client.
When we specify a class, and the client calls it at least once, we create an instance of this class and store it together with the connection until it breaks. It is very convenient, you can make a statefull connection with the browser.
Access to methods is through a point. If the method is called with an underscore (_hidden), then access from Javascript cannot be obtained.
Exceptions are thrown from client to server, and from server to client. When I realized this, I was just stunned. Seeing Javascript traceback in feed logs is guaranteed cognitive dissonance. Well, I’m silent about the python Exceptions in JS.
Total
I use this module on several projects. Everywhere it works as it should, I cleaned the main bugs.
Instead of a conclusion
Thanks to my colleagues and friends for helping me find bugs and sometimes sending patches. Well, and you, reader. If you read this, given the dryness of the article, then you are certainly interested in this topic.
upd 1: Added WebSocket.cleapup_worker () to the examples.