25 April 2014

N2O: Erlang Web-фреймворк на WebSockets

Website developmentOpen sourceErlang/OTP
N2O Erlang Framework

ВВЕДЕНИЕ


Данный пост подразумевает хорошее интро в N2O на русском.

Что такое Erlang/OTP Web Framework N2O и в чём его фишка для веб-разработки, можно узнать на странице в github и официальном сайте SynRC. Там всё как вы любите с графиками и презентациями.

А здесь рассмотрим принципы работы фреймворка и поговорим о вечном.

Рассматриваемая версия N2O: 1.1.0
Всегда интереснее видеть результат, нежели говорить о нём, предлагаю сначала установить N2O себе на компьютер а уже потом вникать в его внутренности. Так нагляднее.

Получить ответы на возникшие вопросы и увидеть рекомендации можно на официальном канале IRC #n2o на FreeNode.net.

УСТАНОВКА


Устанавливаем Erlang если он ещё не установлен. Установили.

Скачиваем N2O, компилируем и запускаем:

git clone git://github.com/5HT/n2o.git
cd n2o/samples
make && make console

Смотрим: 127.0.0.1:8000/

N2O: Erlang Web Framework in Safari

Открыв несколько окон можно початиться самому с собой.

МНОГО ТЕОРИИ


НИША

Предназначен в первую очередь для разработки горизонтально масштабируемых решений с низкой задержкой на обработку клиентских запросов.
Онлайн-игры, чаты, инстаграммы, твиторы — это не совсем то, для чего создавался Erlang, но то, где нужно воспользоваться N2O.

ПОД КАПОТОМ

N2O это переработанный проект Nitrogen который немного загрустил. В N2O мы имеем быстрейший вэб сервер Cowboy, работу через WebSockets, обмен бинарными данными везде где только возможно, HTML и DTL шаблоны, минимальное количество JavaScript без использования сторонних библиотек.

Сравнение веб-фреймворков на Erlang можно увидеть по ссылке.

Возможность использования:
  • NoSQL решений, таких как Mnesia, RIAK, KAI через простую абстрактную модель в виде приложения KVS.
  • Библиотеки авторизации AVZ (Facebook, Google, Twitter, Github, Microsoft).
  • Библиотеки MQ-очередей MQS для RabbitMQ.
  • Интерпретатора Erlang кода в JavaScript с возможностью проверки во время компиляции — Shen.
  • Любых других Erlang решений, просто добавив в приложение.

В N2O реализована компиляция исходников на лету, даже без порчи открытых сессий, на основе Sync. Разработка становится всё человечнее.
С Sync ты можешь кодить без дрочилова (перев.)

Обмен данными сервера с клиентами реализуется посредством WebSockets и имеет минимальный оверхед. Возможные форматы передаваемых данных:
  • BLOB (RAW Binary), идеально для изображений
  • Bert Encoded, для Erlang-термов
  • Pickled, закодированные данные в Base64, либо AES/RIPEMD160

Кластеризация и отказоустойчивость для Веба стала доступна как никогда прежде, с максимальной простотой разработки.
_5HT: Чтобы ты не е___cя. Твое дело клац-клац и в продакшен


МОДЕЛЬ ПОВЕДЕНИЯ

N2O построен как и сам Erlang на передаче сообщений, но только не от процесса к процессу, а от клиента к серверу. Назову это событийной моделью.

Опишем таймлайн работы встроенного в N2O примера n2o_sample.
  1. При запросе страницы /index браузеру передаётся HTML разметка, описанная в функции index:body().
  2. На странице выполняется JavaScript, инициализирующий подключение через WebSocket.
  3. Затем по WebSocket передаётся полезная нагрузка в виде JavaScript кода, и инициализации элементов страницы данными через index:event(init). Здесь одна из ролей JavaScript — например, создать для кнопки на клиенте событие в виде JS-функции, которая отправит на сервер информацию о нажатии.
  4. После клацанья по кнопке, на сервер приезжает Bert-Encoded сообщение под грибами Base64 и выполняется функция index:event(Term) где Term — это терм, описанный в поле рекорда кнопки: #button.postback. Например, кнопка #button{postback=sasay} после нажатия принудит выполниться функцию index:event(sasay).
  5. Сервер, в свою очередь, также в любое время может отсылать данные браузеру. Если данные шлются из другого процесса Х, то должны быть соблюдены условия: в index:event(init) главный процесс, обслуживающий клиента (вкладку браузера), должен быть зарегистрирован под неким именем вызовом wf:reg/1, а в процессе Х после окончания генерации кода для обновления страницы (wf:insert/2, wf:wire/1 и т.п.) должна быть вызвана функция wf:flush/1 для отправки кода обновления браузеру, где он затем будет выполнен JS-машиной.


Первое что приходит на ум — страница динамически изменяется не на основе вызовов функций из файла с JavaScript, а основываясь на полученном JavaScript от сервера в реальном времени.

И это нативный интерактивный режим для вэб-приложения без использования костылей вроде AJAX и LongPooling Comet. Будущее уже здесь, котята. Название ему — WebSockets.

Таблицу поддержки WS браузерами можно увидеть здесь, а проверить свой браузер здесь.

ПОДРОБНЕЕ О СТРУКТУРЕ ПРИЛОЖЕНИЙ


Если знакомство с Erlang произошло недавно или только что, скорее всего возникнут вопросы: что где лежит и в каком месте проявлять свой креатив.

image

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

n2o_sample

N2O уже содержит в себе пример пользовательского приложения — n2o_sample. n2o_sample — это отдельное Erlang приложение, которое работает на N2O. Как видно на картинке выше, оно находится в директории apps/ — это коллекция пользовательских приложений, можем довавлять туда свои, если потребуется разделить n2o_sample на два или более независимых приложения.

Также n2o_sample, конечно же, можно переименовать или заменить чем-нибудь другим. Но для начала я бы не советовал этим заниматься, а пользоваться имеющимся кодом как отправной точкой в разработке своего приложения.

Application list

При запуске n2o_sample, он, как и полагается Erlang приложению, запускает все приложения от которых зависит (зависимости из deps/, и пользовательские, при их наличии, из apps/ ). Этот код находится в n2o_sample/src/web_app.erl:

-module(web_app).
-behaviour(application).
-export([start/2, stop/1]).

start(_StartType, _StartArgs) -> 
    application:start(crypto),
    application:start(sasl),
    application:start(ranch),
    application:start(cowboy),
    application:start(gproc),
    application:start(mimetypes),
    application:start(syntax_tools),
    application:start(compiler),
    application:start(erlydtl),
    application:start(rest),
    application:start(n2o),
    web_sup:start_link().

stop(_State) -> ok.


Функция web_sup:start_link() запускает сам n2o_sample.

Но если мы захотим расширить этот список зависимостей — надо знать, что это не единственное место с перечислением приложений. Также надо пофиксить ещё два файа: reltool.config и .applist, последний создаётся после первой команды make в консоли. Держать в трёх местах одно и то же — временный костыль от @darkproger, но он обещал всё пофиксить (обещанного три года ждут).

sys.config

Сюда сохраняются параметры для всех приложений. В один файл. Удобно. Потом из кода звоним в application:get_env(App,Key) и всё.

vm.args

Здесь можно указать ключи для виртуальной машины а также переменные окружения (вместо $ export SOME_PARAM=value).

priv/static/

Директория, переданная веб-серверу Cowboy как файлопомойка “статики”. Здесь можно размещать JavaScript код, закидывать пикчи и хентай. n2o_sample/src/web_sup.erl:

dispatch_rules() ->
    cowboy_router:compile(
        [{'_', [
            {"/static/[...]", cowboy_static,
                {priv_dir, ?APP, <<"static">>,
                [{mimetypes,cow_mimetypes,all}]}},
                ****


СТРАНИЦЫ


Для создания динамических страниц N2O даёт возможность включения HTML файлов в проект как DTL шаблоны. В директории deps/erlydtl/ находится приложение ErlyDTL которое предназначено для компиляции DTL шаблонов в Erlang байт-код.

Сами шаблоны располагаются в директории n2o_sample/priv/templates/ и выглядят как HTML файлы с объявлениями внешних источников данных через слова, заключённых в двойные фигурные скобки {{ }}.

Таким образом мы можем “накидать” HTML макет со статической информацией, а динамическую вынести в Erlang код через подключение {{ }}.

Для примера рассмотрим тело функции login:main/0, которая отдаёт браузеру первоначальное состояние страницы при переходе по адресу http://127.0.0.1/login/:

#dtl{ file = "login",
      app=n2o_sample,
      bindings=[
                {title,title()},
                {body,body()}
                ]}.


  • Здесь login — это имя шаблона: /priv/static/template/index.html;
  • title и body — именованные включения в HTML шаблоне {{title}} и {{body}};
  • title() и body() — функции, результат которых будет подставлен в HTML шаблон.


СОБЫТИЯ


Обмен данными в N2O на клиенте реализован через JavaScript, с помощью подгружаемых на страницы файлов /deps/n2o_scripts/n2o/bullet.js (инициализация WebSocket подключения) и n2o.js (обработка полученных данных).

Со стороны сервера эндпоинтами служат: /n2o/src/endpoints/cowboy/bullet_handler.erl (инициализация WebSocket подключения) и n2o_bullet.erl (обмен данными).

API


Разберём основные API-функции, с которыми возникает больше всего вопросов при знакомстве с N2O.

wf:comet/1, wf:async/1, wf:async/2

Регистрируют процесс под уникальным именем (“comet” для comet/1 и async/1) в рамках ноды через global:register_name/2. Если уже зарегистрирован, возвращают его Pid.

wf:flush/1

Изымает через wf_context:actions/0 сохранённые в стейте текущего процесса изменения для страницы и отсылает их через wf:send/2 процессу (в рамках ноды), имя которого передано в параметре.

wf:reg/1, wf:reg/2 (?REGISTRATOR = n2o_mq)

Регистрируют процесс как Property под неуникальным именем в рамках ноды через GProc. Повторно зарегистрировать процесс через GProc под тем же именем не получится, состояние регистрации хранится в стейте процесса (get/1, put/1) и будет возвращён терм skip (n2o_mq.erl). Дополнительная инфа на русском по Gproc тут.

Регистратором по умолчанию является модуль n2o_mq но его можно переопределить, например для возможности регистрировать процессы в рамках кластера.

wf:send/2 (?REGISTRATOR = n2o_mq)

Отсылает сообщение, переданное в функцию вторым аргументом, процессу, зарегистрированному через wf:reg/1 или wf:reg/2.

wf:q/1

Извлекает данные, переданные от клиента через, так называемые, элементы обратной передачи, например:

body() ->
    [ #textbox{ id=message },
    #button{ postback={button_pressed}, source=[message] } ].
event({button_pressed}) ->
    wf:info("Message: ~p",[wf:q(message)]);

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

wf:qs/1

Извлекает параметры из HTTP форм, например:
wf:qs(<<"x">>) извлечет <<"ABC">> если URL был бы localhost:8000/index?x=ABC.

wf:wire/1

Может принимать аргументом как JavaScript текст, так и рекоды событий, объявленные в секции “Actions” файла /n2o/include/wf.hrl, например: wf:wire(#alert{text="Привет!"}). JavaScript будет также обёрнут в #wire{actions=JS}, что приведет к конструкции wf:wire(#wire{sctions=JS}).

wf:update/2, wf:insert_top/2, wf:remove/1 и другие

Это частные случаи wf:wire/1, включающие готовый JavaScript код для изменения DOM в браузере клиента.

wf:info, wf:warning, wf:error

Это функции, которыми рекомендуется пользоваться, вместо error_logger:info_msg/1 и остальных соответственно.

wf:f, wf:to_list, wf:to_binary, wf:html_encode, wf:url_encode, wf:hex_encode и другие

Распологаются в секции “Convert and Utils API” файла /n2o/src/wf.erl. Все они являются надстройками над стандартными функциями Erlang, но более умные и не такие деревянные. wf:f — аналог io_lib:format, далее по списку функции-конвертеры принимающие на вход любой терм, затем более специфичные для веба функции. Нет смысла их все подробно здесь описывать, была цель только показать, что они существуют.

wf:pickle/1, wf:depickle/1 (?PICKLER = n2o_pickle)

Кодер и декодер термов для передачи системных вызовов между клиентом и сервером. n2o_pickle кодирует в Base64, в то время как n2o_secret использует AES/RIPEMD160 шифрование с произвольным ключом.

На этом по API всё, дополнительную информацию можно почерпнуть из официальной доки по N2O API.

СТОИТ ОБРАТИТЬ ВНИМАНИЕ


Ниже перечислены свободные программные продукты SynRC, способные ускорить в десятки раз вывод в продакшн Вашего проекта на Erlang.

KVS

KVS — абстрактная модель K-V noSQL базы данных, умеющая в подмножества (feeds) таблицы через двусвязные списки и вторичные индексы (kvs:index/3). На текущий момент возможно использование с Mnesia, RIAK и KAI.

AVZ

AVZ — система авторизации через Twitter, Google, Facebook, Github и Microsoft.

Shen

Shen — интерпретатор Erlang кода в JavaScript. Позволяет использовать компилятор Erlang для проверки JavaScript.

MQS

MQS — MQ библиотека для RabbitMQ.

Feeds

Feeds — обработчик пула команд поддержания согласованности данных на всех нодах кластера, также кэш сервер.

SkyLine

SkyLine — пример интернет-магазина на N2O.

COUNTACH

Countach — социальная система и расширенный магазин приложений. Production-ready. Использует KVS, AVZ и Feeds. Основан на VOXOZ.

VOXOZ

VOXOZ — Открытая Erlang облачная платформа (PaaS). Использует Docker, ErlangOnXen. Больше информации на blog.docker.io.

ЗАКЛЮЧЕНИЕ


Чтобы информация усваивалась равномерно, на этом пока всё. Отдельное спасибо @mtreskin за советы в выборе архитектуры и наводкой на nitrogen и n2o; а также @5HT за бесконечные консультации 24/7 в IRC.
Tags:n2oerlangwebframeworksscalabilitynitrogenfunctional programming
Hubs: Website development Open source Erlang/OTP
+39
29.8k 185
Comments 24
Top of the last 24 hours