Эрланг для веб-разработки (1) -> Знакомство;

от автора


Я начинаю публиковать серию статей о веб-разработке на Эрланге. Многие хотят попробовать Эрланг, но сталкиваются с проблемой, что вводные курсы в основном касаются Эрланга как функционального языка и далеки от реальных проектов (Learn You Some Erlang for great good! — хорошая и подробная книга). С другой стороны все обучающие материалы по веб-разработке подразумевают, что читатель уже хорошо знает Эрланг.

Эта серия статей рассчитана для разработчиков, у которых есть опыт в веб-разработке (PHP, Ruby, Java), но не имеют опыта разработки на Эрланге.

Задачей будет сделать блог. Код из статей https://github.com/denys-potapov/n2o-blog-example, готовый проект можно посмотреть по адресу http://46.101.118.21:8001/. Особенности проекта:

  • обновление комментариев в реальном времени;
  • авторизация через фейсбук;
  • данные храним в mnesia.

В основе проекта феймворк n2o. Выбор довольно субъективен, но из живых Эрланг фреймворков, n2o мне показался наиболее «эрлангоподобным», в тоже время ChicagoBoss больше похож на MVC фреймворки в других языках.

Настраиваем окружение

Я буду настраивать окружение в Ubuntu, но схожим образом должно работать и в других ОС. Скачиваем и устанавливаем актуальную версию эрланга www.erlang-solutions.com/resources/download.html.

Менеджер зависимостей

Стандартный менеджер зависимостей в Эрланге — rebar. Но, в данной статье мы будем использовать mad от создателей n2o, который совместим с rebar конфигурацией, работает быстрее и позволяет отслеживать изменения в шаблонах.

curl -fsSL https://raw.github.com/synrc/mad/master/mad > mad  chmod +x mad  sudo cp mad /usr/local/bin/ 

Для отслеживание изменений файлов mad требует установки inotify-tools:

sudo apt-get install inotify-tools 

Генерируем костяк приложения и запускаем его:

    mad app "blog"     cd blog     mad deps compile plan repl 

По адресу http://localhost:8001/ открывается чат, который обновляется по вебсокету в реальном времени, и можно переписываться самому с собой из разных окон.

Параметры mad отвечают за получение зависимостей и запуск приложения:

  • deps — получить зависимости;
  • compile — скомпилировать приложение;
  • plan — создать план запуска;
  • repl — запустить консоль.

Структура проекта

Структура файлов нашего проекта стандартная для Эрланг приложений:

 ├── apps     ├── rebar.config     └── sample         ├── ebin         │   ├── ...         ├── priv         │   ├── static         │   │   ...         │   └── templates         │       └── index.html         ├── rebar.config         └── src             ├── index.erl             ├── routes.erl             ├── sample.app.src             └── sample.erl ├── deps ├── rebar.config └── sys.config 

Подробно о структуре можно почитать в официальной документации.
Позже мы познакомимся практически со всеми файлами и папками, а пока нам надо знать, что Эрланг приложение обычно состоит из нескольких приложений, которые лежат в папке apps. У нас там одно приложение sample, в котором:

  • src — исходный код;
  • ebin — скомпилированные файлы;
  • priv — остальные файлы проекта, в данном случае шаблоны и статика;
  • index.erl — заглавная страница.

Первый код

Удалим ненужные файлы:

rm -r apps/sample/priv/static/ 

Для шаблонов мы используем ErlyDTL, реализацию Django Template Language на эрланге. Поэтому синтаксис будет понятен тем, кто знаком с Django-подобными шаблонизаторами (Django, Twig, Mustache).

apps/sample/priv/templates/base.html

<!DOCTYPE html> <html lang="en">   <head>     <meta charset="utf-8">     <meta http-equiv="X-UA-Compatible" content="IE=edge">     <meta name="viewport" content="width=device-width, initial-scale=1">     <title>{% block title %}Erlang blog example{% endblock %}</title>      <!-- Bootstrap -->     <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">      <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->     <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->     <!--[if lt IE 9]>       <script src="//oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>       <script src="//oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>     <![endif]-->     <style>       .container {           max-width: 40em;       }     </style>   </head>   <body>       <div class="container">         {% block content %}{% endblock %}       </div>      <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>     <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>   </body> </html> 

apps/sample/priv/templates/index.html

{% extends "base.html" %} {% block title %}Latest posts{% endblock %} {% block content %} <h1>Latest posts</h1> {{ posts }} {% endblock %} 

Теперь откроем index.erl и заменим код на такой:

-module(index). -compile(export_all). -include_lib("n2o/include/wf.hrl"). -include_lib("nitro/include/nitro.hrl").  main() -> #dtl{file="index"}. 

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

Функция main/1 вызывается при открытии главной страницы. Функции могут возвращать или сразу HTML, или DSL Эрланг записи, о которых мы поговорим позже. Пока мы просто возвращаем отрендеренный шаблон index. В документации к Эрлангу функции всегда пишутся как название/кратность, где кратность — количество аргументов.

Знакомимся с синтаксисом

Сейчас самое время ознакомиться с основами синтаксиса, это быстрее всего сделать на www.tryerlang.org. Мы выведем на главной странице все посты. Пока не будем использовать БД, а будем хранить посты прямо в коде.

В заголовочном файле /apps/sample/include/records.hrl опишем запись для хранения постов:

-record(post, {id, title, text, author}). 

Создадим модуль /apps/sample/src/posts.erl для хранения постов. Модуль экспортирует две функции: get/0 — возвращает все посты, а get/1 — возвращает пост по Id:

-module(posts). -export([get/0, get/1]). -include("records.hrl").  get() -> [     #post{id=1, title="first post", text="interesting text"},     #post{id=2, title="second post", text="not interesting text"},     #post{id=3, title="third post", text="very interesting text"} ].  get(Id) -> lists:keyfind(Id, #post.id, ?MODULE:get()). 

Записи в Эрланге — это синтаксический сахар, компилятор заменит записи на кортежи, а поля на индексы. Например #post.id будет заменен на 0.

DSL

Выше я писал, что функции могут возвращать Эрланг записи, которые преобразуются в HTML. Изменим наш index.erl, чтобы на странице выводился список всех постов:

-module(index). -compile(export_all). -include_lib("n2o/include/wf.hrl"). -include_lib("nitro/include/nitro.hrl"). -include_lib("records.hrl").  posts() -> [     #panel{body=[         #h2{body = #link{body = P#post.title, url = "/post?id=" ++ wf:to_list(P#post.id)}},         #p{body = P#post.text}       ]} || P <- posts:get()].  main() -> #dtl{file="index", bindings=[{posts, posts()}]}. 

Для создания страницы поста, мы в /apps/sample/src/routes.erl указываем, какой модуль будет обрабатывать наш путь:

route(<<"post">>)        -> post; 

Модуль apps/sample/src/post.erl просто выводит шаблон с данными поста:
модуль

-module(post). -compile(export_all). -include_lib("n2o/include/wf.hrl"). -include_lib("records.hrl").  main() ->     {Id, _} = string:to_integer(binary_to_list(wf:q(<<"id">>))),     Post = posts:get(Id),     #dtl{file="post", bindings=[{title, Post#post.title}, {text, Post#post.text}]}. 

Шаблон:

{% extends "base.html" %} {% block title %}{{ title }}{% endblock %} {% block content %} <h1>{{ title }}<br /> <small>by {{ author }}</small> <p>{{ text }}</p> <h3>Comments</h3> {{ comments }} {% endblock %} 

Вебсокеты

Теперь мы подошли к самому интересному, а именно связи браузера с сервером по вебсокету. Мы сделаем комментарии к посту, которые будут обновляться в реальном времени. Для этого в базовый шаблон добавим библиотеки инициализации n2o:

    <script>{{script}}</script>     <script src='/n2o/protocols/bert.js'></script>     <script src='/n2o/protocols/client.js'></script>     <script src='/n2o/protocols/nitrogen.js'></script>     <script src='/n2o/validation.js'></script>     <script src='/n2o/bullet.js'></script>     <script src='/n2o/utf8.js'></script>     <script src='/n2o/template.js'></script>     <script src='/n2o/n2o.js'></script>     <script>protos = [ $bert, $client ]; N2O_start();</script> 

А в модуле post.erl добавим обработчик события и код для вывода комментариев:

main() ->     Id = wf:to_integer(wf:q(<<"id">>)),     Post = posts:get(Id),     #dtl{file="post", bindings=[{title, Post#post.title}, {text, Post#post.text}, {comments, comments()}]}.  comments() ->     [#textarea{id=comment, class=["form-control"], rows=3},       #button{id=send, class=["btn", "btn-default"], body="Post comment",postback=comment,source=[comment]} ].  event(comment) ->     wf:insert_bottom(comments, #blockquote{body = #p{body = wf:html_encode(wf:q(comment))}}). 

При выводе кнопки, мы указываем, какое событие будет вызвано (postback) и какие параметры надо передать на сервер (source). В функции event(comment) мы отправляем клиенту код, чтобы добавить комментарий внизу списка. Пока этот комментарий не попадает к другим клиентам, но сейчас мы это исправим:

event(init) ->     wf:reg({post, post_id()});  event(comment) ->     wf:send({post, post_id()}, {client, wf:q(comment)});  event({client, Text}) ->     wf:insert_bottom(comments, #blockquote{body = #p{body = wf:html_encode(Text)}}). 

Событие init, вызывается в момент загрузки страницы, и мы регистрируем наш процесс, что он будет получать сообщения из пула {post, post_id()}.

Вместо вывода комментария в событии event(comment), мы посылаем сообщение с новым комментарием в пул. А вывод комментария делаем в обработчике event({client, Text}). Теперь мы можем весело переписываться в чате под постом, и почти повторили код, который сгенерировал mad как костяк приложения.

В следующей статье мы будем хранить посты и комментарии в БД, и добавим авторизацию через фейсбук.

ссылка на оригинал статьи http://habrahabr.ru/post/273979/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *