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


Я починаю публікувати серію статей про веб-розробку на Эрланге. Багато хто хоче спробувати Ерланген, але стикаються з проблемою, що ввідні курси в основному стосуються Ерланга як функціонального мови і далекі від реальних проектів (Learn You Some Erlang great for 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 Language Template на эрланге. Тому синтаксис буде зрозумілий тим, хто знайомий з 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 and shim 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 як кістяк програми.

У наступній статті ми будемо зберігати пости і коментарі в БД, і додамо авторизацію через фейсбук.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.