В статье речь пойдет о технологии WebSocket. Точнее не о самой технологии, а о том, как ее можно использовать. Я давно слежу за ней. Еще когда в 2011 году один мой коллега прислал мне ссылку на стандарт, пробежав глазами, я как-то расстроился. Выглядело настолько круто, и я думал, что в момент, когда это появится в популярных браузерах, я уже буду планировать, на что потратить свою пенсию. Но все оказалось не так, и как гласит caniuse.com WebSocket не поддерживается только в Opera Mini (надо бы провести голосование, как давно кто-либо видел Opera Mini).
Кто трогал WebSocketы руками, тот наверняка знает, что работать с API тяжело. В Javascript API достаточно низкоуровневый (принять сообщение — отправить сообщение), и придется разрабатывать алгоритм, как этими сообщениями обмениваться. Поэтому и была предпринята попытка упростить работу с вебсокетами.
Так и появился WSRPC. Для нетерпеливых вот простое демо.
Идея
Основная идея в том, чтобы дать разработчику простой API на Javascript вроде:
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();
И на 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) tornado.ioloop.IOLoop.instance().start()
Особенности
Поясню некоторые моменты того, как это работает.
JavaScript
Браузер инициализирует новый объект RPC, после этого мы вызываем методы, но WebSocket еще не соединился. Не беда, вызовы стали в очередь, которую мы разгребаем при удачном соединении, или отвергаем все обещания (promises), очищая очередь при следующем неудачном соединении. Библиотека все время пытается соединиться с сервером (на события соединения и отсоединения тоже можно подписаться RPC.addEventListener(«onconnect», func)). Но пока мы не запустили RPC.connect(), мы мирно складываем вызовы в очередь внутри RPC.
После соединения сериализуем в JSON наши параметры и отправляем на сервер сообщение вида:
{"serial":3,"call":"test","arguments": null}
На что сервер отвечает:
{"data": {}, "serial": 3, "type": "callback"}
где serial это номер вызова.
После получения ответа библиотка на JS разрешает обещание (resolve promise), и мы вызываем то, что за then. После этого делаем еще один вызов и так далее…
Замечу также, что между вызовом и ответом на него, может пройти сколько угодно времени.
Python
На Python регистрируются вызовы в объекте WebSocket. Атрибут класса (class-property) ROUTES это словарь (dict), который хранит ассоциацию того, как называется вызов, и какая функция или класс его обслуживает.
Если указана функция, она просто вызывается, и ее результат передается клиенту.
Когда мы указываем класс, и клиент хоть раз вызывает его, мы создаем экземпляр этого класса и храним его вместе с соединением до самого его разрыва. Это очень удобно, можно сделать statefull соединение с браузером.
Доступ к методам осуществляется через точку. Если метод называется с подчеркивания (_hidden), то доступ из Javascript к нему не получить.
Еще от клиента к серверу, и от сервера к клиенту пробрасываются исключения. Когда я это реализовал, а был просто ошарашен. Увидеть Javascript traceback в питонячих логах — гарантированный когнтивный диссонанс. Ну, а про питонячьи Exceptions в JS я молчу.
Итог
Использую этот модуль на нескольких проектах. Везде работает как надо, основные баги вычистил.
Вместо заключения
Спасибо моим коллегам и друзъям за то, что помогали находить ошибки и иногда присылали патчи. Ну, и тебе, читатель. Если ты это читаешь, с учетом сухости статьи, тогда тебе уж точно интересна эта тема.
ссылка на оригинал статьи http://habrahabr.ru/post/248507/
Добавить комментарий