JSON-RPC или когда REST неудобен

Видео моего рассказа про JSON-RPC.

17 минут доклад, 30 минут холивар 🙂

Слайды в PDF – PyNSK-6_JSON-RPC.pdf

PyNSK #6. Слайды доклада про JSON-RPC

Версия в PDF – PyNSK-6_JSON-RPC.pdf

Обработка RPC-ответов

Главное отличие асинхронного RPC от синхронного в том, что ответ от сервера может прийти не сразу, может прийти не в том порядке, в котором были запросы. Это сильно влияет на структуру кода обработчика.

Однако, раз уж сделан красивый вызов RPC, то хочется и красиво обрабатывать ответы.

Самый очевидный способ – это писать обработчики, стилизованные под синхронные. Т.е. код обработки ответа должен идти в файле сразу после вызова RPC.

Пример вызова клиентом сервера:

Когда происходит вызов метода объекта server, в пул добавляется запись, указывающая библиотеке: когда придёт ответ от сервера с id таким-то, то нужно вызвать функцию success у такого-то объекта result.

Это в случае успешного ответа (проверяется по наличию поля result в приходящем от сервера json’е). Аналогично делается обработка ответа, когда сервер сообщает об ошибке. Бонусом библиотека позволяет сделать обработчик, который вызовется вне зависимости пришла ли ошибка или нет. Вот полный пример:

Знатоки js говорят, что правильно такие вещи делать через Promise. Возможно, в будущем библиотека будет поддерживать такой способ. Пока только так. В любом случае, результат был достигнут – в коде идёт вызов RPC, ниже идёт обработчик. Код легко читается. Да и пишется 🙂

С Python’ом на сервере вышло немного сложнее. Дело в том, что просто создать блок кода, который бы выполнялся не сразу, а по требованию нельзя. Точнее можно 🙂 Такой блок – это функция.

В этом примере сервер запустит функцию test когда клиент к нему обратится. В функции происходит вызов клиентского метода js_func. Когда клиент ответит серверу хотелось бы вызвать обработчик ответа – в данном случае функцию client_response. Но для этого библиотека как-то должна узнать, что именно вызвать при ответе клиента. Всё осложняется тем, что функция client_response не видна из вне. Она доступна только внутри функции test.

После долгих попыток и проб разных решений был найден достаточно красивый способ:

Декоратор позволяет связать запрос и обработчик ответа через переменную, добавляя в пул запись. По этой записи библиотека знает какую функцию вызвать когда придёт ответ от клиента.

Но для полноты картины не хватает возможности делать обработчики ошибок jsonrpc. Решилось это не сразу, после перебора разных хитрых способов. И… хитрые способы не помогли, а простое решение оказалось вполне красивым:

В этом примере после ответа клиента вызовется функция client_response с параметром data, а второй параметр – success будет подставлен декоратором. Декоратор “внутри себя” проверит ответ jsonrpc, определит есть ли там поле error и запустит client_respnse с нужным параметром success.

В примере функция делется на три блока как и пример на js, но использован другой механизм. В случае успешного ответа выполнится код после

В случае ошибки выполниться блок после

А уже затем выполниться код не зависящий от результата ответа. В примере на js этот блок был в функции finally.

 

Красивый вызов RPC

Реализовал в библиотеке такой механизм:

Вызов серверной функции с клиента:

Вызов клиентской функции с сервера:

Таким образом и клиент и сервер могут вызывать api-методы друг друга, например, клиент посылает запрос на сервер, когда пользователь нажал кнопку, а сервер посылает запрос клиенту, когда для пользователя появилось новое сообщение.

Теперь подробнее 🙂

Клиентская часть.

Чтобы клиент мог обратиться к методу объекта server, у объекта уже должны быть объявлены нужные методы. По-другому js не умеет. Создать методы к объекту в js просто, достаточно создать словарь server, в именованные элементы которого поместить функции. Но всё равно нужно заранее иметь список серверных методов. Обойти это не удалось, потому сервер должен их передать в js.

И так в цикле для всех серверных методов.

Серверная часть.

Реализовать вызов несуществующего метода на Python оказалось проще, чем на js.

В Python при обращении к несуществующему атрибуту объекта происходит вызов функции __getattr__, если она определена (иначе выдастся ошибка).

Например, объявлен класс:

После объявление переменной

и обращения к атрибуту x:

будет выведено

При обращении к любому другому атрибуту, например

будет выведено

Однако, если при этом попробовать вызвать метод

а не просто атрибут, то произойдёт ошибка

Исправить это достаточно просто: нужно, чтобы функция __getattr__ вернула другую функцию 🙂

Теперь при обращении

будет возвращён объект функции, а при

будет выполнена функция.

Помимо красивого вызова api реализована обработка ответов сервера и клиента. Но об этом следующий раз.

Изоморфный JavaScript

Послушал доклад про js-фреймворк Slot на мероприятии DevDay. Помимо самого фреймворка докладчик говорил про изоморфный js и про то, как это хорошо и вообще за этим будущее.

Изоморфный JavaScript – концепция, при которой клиент и сервер написаны на js, что позволяет писать гораздо меньше повторяющегося кода.

И всё тут вроде бы хорошо – для веб-проекта используется один язык, кода меньше, модули между собой взаимодействуют лучше за счёт общего языка и т.д. Но ощущение, что что-то не так присутствует 🙂 Настораживает ли то, что на сервере используются изначально клиентский (даже браузерный) язык js? Нет, ни грамма. Есть ли сложности в том, что фронтэндщики, писавшие на js для браузера, начинают писать серверную часть? В этом тоже проблем нет. Раз нравится язык и его возможностей хватает для написания требуемой программы, то почему бы и нет. Проблема находится с другой стороны: из-за подхода изоморфности, серверным разработчикам приходится писать именно на js, т.к. браузер ничего другого не умеет, а значит сервер вынужден подстраиваться под такие ограничения.

Фанаты JavaScript скажут, что в этом нет ничего  такого, что js – хороший современный язык с быстрым интерпретатором. Когда же возникают вопросы про ограничения языка, то ответы обычно вида “Вы просто не умеете на нём писать” или “В следующей версии языка всё это исправится”. Стоит отметить, что в таком стиле говорят и фанаты всех остальных языков 🙂

Так в чём же проблема писать серверную часть на js и подходит ли этот язык для сервера? Язык подходит, на нём пишут, его используются и вполне успешно. Проблема в навязывании этого языка.

Кто же его навязывает? Фанаты? Нет! Фанаты любого языка хвалят и рекомендуют другим тот язык, который они сами считают удобным. Навязыванием занимаются разработчики браузеров. Специально ли они это делают или просто так получается – не известно.

Может возникнуть мысль, что это ограничение появилось потому, что браузер и веб – это платфора, а там правило – использовать JavaScript, и остальные платформы также накладывают ограничения в используемых средствах. Однако это не так, и этому множество подтверждений. Такие платформы как Linux, Windows, MacOS не имеют запрета на используемые языки. Это касается как их серверных, так и десктопных реализаций. Писать можно на чём угодно. Даже такие “закрытые” и контролируемые корпорациями экосистемы как Android и IOS позволяют писать не только на флагманских для этих платформ языках (Java и Objective-C), но и на других: JavaScript, C#, C++, Python, Scala, Swift. Даже на Delphi и ActionScript (Flash) можно писать (как минимум под Android).

Может быть браузер – это уникальная платформа и там в принципе невозможна поддержка нескольких языков? Нет, это не так и разработчики браузеров пытались встроить такую поддержку. Например, Internet Explorer поддерживает язык VBScript, а для Chrome можно писать на Dart.

Почему же возможность писать на разных языках не стала популярна? Может быть никому из программистов это не нужно и всех устраивает JavaScript в браузере? JavaScript не устраивает, и подтверждение тому множество языков, на которых уже сейчас можно писать. Самые популярные из них: CoffeeScript и TypeScript. Но они вынуждены преобразовывать свой код в js, чтобы быть понятым браузером.

Итак, востребованность в других языках есть, браузеры на практике уже показывали, что могут поддерживать не только js. Почему же многоязычность не взлетает? На мой взгляд, не хватает всего лишь одной вещи – лёгкой интеграции любого стороннего языка в браузер. Вот как это могло бы выглядеть: разработчики браузера описывают, каким требованиям должен удовлетворять модуль внешнего языка; если модуль проходит проверку, то его помещают в специальный каталог (репозиторий) – некий аналог Google Play; когда пользователь заходит на страницу, которая требует определённого языка, браузер подгружает модуль из репозитория. Для пользователя это будет абсолютно прозрачно и незаметно.

В конце хотелось бы упомянуть мнение некоторых разработчиков на проблему противостояния одного “стандартного” языка и многоязычности. Некоторые полагают, что многоязычность ухудшит ситуацию, веб разделится на множество языков, под каждый язык придётся по-новому писать все необходимые библиотеки, тогда как сейчас есть тысячи готовых модулей для JavaScript. Уже нельзя будет просто посмотреть код странички, т.к. она может использовать язык, неизвестный разработчику. Я считаю эти опасения несколько надумаными, ведь на десктопах используют разные языки и катастрофы не случилось. Поскольку в качестве десктопа я использую Linux, то приведу пример из этой платформы: для написания оконных и консольных приложений активно используется целый зоопарк технологий, каждый выбирает то, что хочет, что лучше подходит. При этом для пользователя нет особой разницы на чём написана та или иная программа. Множество программ написано на C/C++, есть оконные приложения на Python, Java. Встречаются даже программы написанные на C# – языка изначально разработанного для работы в Windows. Применяются скрипты на Lua. Кто-то пишет на JavaScript с использованием библиотек для отрисовки окон. Есть ли преимущества у такого зоопарка? Несомненно. Языки и их библиотеки конкурируют между собой, берут друг у друга лучшие концепции, соревнуются в скорости выполнения кода, в скорости компиляции. Одни позволяют написать код, который будет выдерживать огромные нагрузки, другие же – написать простую программу здесь и сейчас буквально парой строчек кода. От такого разнообразия выигрывают все, и, я полагаю, что браузеры, внедрив у себя такую возможность точно не прогадают.

Прогресс по библиотеке

Задумка с декоратором удалась. Получилось сделать библиотеку ws_nado_rpc (точнее наработки по библиотеке 🙂 ) удобными в использовании.

Простой пример:

Server:

Client: (js)

В итоге на клиент вернётся строка:

Это результат работы декорированной функции test1.

Впечатления от написании библиотеки:

Сперва код был простой, каждая строчка делала полезную работу. Однако для того, чтобы сделать код “устойчивым” и обрабатывать ситуации, когда клиент присылает что-то не то, пришлось писать обработчики, которые раздувают код.

Нельзя просто получить от клиента json, взять оттуда название метода и выполнить его с параметрами. Кроме проверки на корректность самого json’а (вдруг это просто какая-то строка, а не json?), нужно проверить и наличие параметра method:

И так по всем параметром из стандарта jsonrpc.

Дальнейшие планы по серверу:

Для полноценного двухстороннего rpc нужен механизм вызова функции клиента с сервера.

Необходимо реализовать частоиспользуемые функции, напрямую не связанные с простым rpc, например, проверку авторизации пользователя.

Планы по клиенту:

А точнее, по js-клиенту. Сейчас клиент должен отправлять на сервер готовую строку в формате jsonrpc:

Хотелось бы, чтобы оно выглядело, как вызов простой клиентской функции. Например, чтобы для вызова серверной функции test1, на клиенте необходимо было бы выполнить javascript-код:

Однако, при попытке реализовать такую задумку возникли сложности. Если в том же Python‘е можно у любого объекта выполнить функцию, которой у объекта нет (на лету понятно, что функции нет, узнать что хочется и сделать это), то в JavaScript такой роскоши нет.

Поискав несколько js-rpc библиотек и посмотрев их способ вызова функций, стало понятно, что их авторы за красотой не гнались (а может в мире js принято вызывать функции не как функции?).

Вот пример использования плагина JsonRpcClient для JQuery, реализующего jsonrpc:

Но есть и исключения 🙂 Например, в библиотеке angular-jsonrpc rpc-функции вызываются “красиво”:

Правда перед тем как эта магия заработает, нужно руками всё сконфигурировать:

WebSockets, Tornado, JSONRPC

Вебсокеты дают много плюшек, однако найти подходящие средства разработки оказалось не просто…

Теперь по порядку.

При разработке веб-приложений используются API – это понятно. Давно и успешно применяю jsonrpc. Стандарт простой, библиотек много, платформонезависимый, и отлично читается при отладке. В том же (той же?) Django, чтобы превратить функцию в api-метод для jsonrpc достаточно обернуть её декоратором jsonrpc_method из библиотеки django-json-rpc:

Но Django не поддерживает вебсокеты. Тут на помощь приходит Tornado, где создать простое приложение, отправляющее данные по вебсокетам, очень легко. К тому же, пользователи Торнадо заявляют, что такой сервер выдержит огромное количество соединений. Самое то для интерактивного веб-приложения, если бы не одно “но”: все библиотеки для Торнадо, реализующие jsonrpc, работают только с обычными http-запросами. Не удалось найти ни одной библиотеки с отправкой команд по вебсокетам.

А это значит, что пора написать такую библиотеку 🙂

Без неё код приложения с API будет выглядеть как жуткая лапша из if’ов.

Продолжение следует…

Выбор движка для блога

Сперва посмотрел на популярные ныне средства для генерации статичных блогов вроде Octopress.

Плюсы:

  • Низкая нагрузка на хостинг.
  • Хранение данных в репозитории.
  • Гиковость 🙂

Минусы:

  • Необходимость перегенерировать сайт локально – придётся ставить локальный софт.
  • Сложнее отредактировать пост на лету в браузере. Даже для мелкой правки нужно изменить локальный файл, залить в репозиторий, запустить генератор, залить на хостинг.

Из “традиционных” движков в первую очередь вспоминается WordPress.

Его плюсы:

  • Легко развернуть на хостинге, т.к. WebFaction поддерживает установку в пару кликов из админки.
  • Легко редактировать посты.
  • Много статей в интернете по настройке WordPress – легко настроить стартовый вариант даже если раньше никогда не доводилось использовать WordPress.

В конце концов всегда можно сменить движок в будущем, а сейчас не тратить много времени на выбор платформы. Ведь главное не движок, а контент 🙂