⬆️ ⬇️

WebSocket RPC or how to write a live web browser application





The article focuses on technology WebSocket. More precisely, not about the technology itself, but about how it can be used. I've been watching 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 will appear in popular browsers, I will already plan on what to spend my retirement. But everything turned out to be wrong, and as caniuse.com WebSocket says, it is not supported only in Opera Mini (it would be necessary to vote how long someone has seen Opera Mini).



Who touched the WebSocket with his hands, he probably knows that working with the API is hard. The Javascript API is quite low-level (to accept a message — to send a message), and it will be necessary to develop an algorithm for how to exchange these messages. Therefore, an attempt was made to simplify the work with web-based software.

')

This is how 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() 




Special features



I will explain some moments of how it works.



Javascript


The browser initializes a new RPC object, after which we call methods, but WebSocket has not yet connected. It does not matter, the calls are in the queue, which we rake at a successful connection, or reject all promises (promises), clearing the queue at the next failed connection. The library tries to connect to the server all the time (you can also subscribe to connection and disconnect events RPC.addEventListener ("onconnect", func)). But until we run RPC.connect (), we peacefully add calls to the queue inside the RPC.



After connection, we serialize our parameters into JSON and send a message to the server like this:



 {"serial":3,"call":"test","arguments": null} 


To which the server responds:



 {"data": {}, "serial": 3, "type": "callback"} 


where serial is the call number.



After receiving the answer, the JS library resolves the promise (resolve promise), and we call what is then. After this, we make another challenge and so on ...



I also note that between the challenge and the answer to it, it may take some time.



Python


In Python, calls are logged in the WebSocket object. Class attribute (class-property) ROUTES is a dictionary (dict) that stores the association of the name of the call, and which function or class serves it.



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 its very end. This is very convenient, you can make a statefull connection to the browser.



Access to methods is via point. If the method is called with an underscore (_hidden), then it cannot be accessed from Javascript.



More from the client to the server, and from the server to the client, exceptions are thrown. When I implemented it, I was just stunned. See the Javascript traceback in the python logs - guaranteed cognitive dissonance. Well, about the Python Exceptions in JS, I am silent.



Total



I use this module on several projects. Everywhere it works as it should, the main bugs are cleaned.



Instead of conclusion



Thanks to my colleagues and friends for helping me find bugs and sometimes sending patches. Well, and you, the reader. If you read this, given the dryness of the article, then you certainly are interested in this topic.



upd 1: Added WebSocket.cleapup_worker () to examples.

Source: https://habr.com/ru/post/248507/



All Articles