Erlang и n2o
web-разработка без
JavaScript
DevelCamp 2014
Татауров Евгений, Noda
Erlang
• Функциональный
• Динамически типизирован
• Лямбды
• Pattern matching
lists:map(fun(X) -> X*2 end, [4,8,15,16,23,42]).
double([H|T]) -> [2*H|double(T)];
double([]) -> [].
Erlang
• Процессы-акторы
• Message passing
• Распределенность из коробки
Erlang
• Let it crash
• OTP
• Supervision tree
n2o
• Cowboy (HTTP & WebSockets)
• Генерация HTML и обработчиков
• Scala Lift
• deferred javascript
n2o Elements
#panel{id=Panel, body=#button{id=myButton, body=
<<"OK">>, postback={ok, <<"INFO">>}}}
n2o Elements
#panel{id=Panel, body=#button{id=myButton, body=
<<"OK">>, postback={ok, <<"INFO">>}}}
<div id="myPanel"><button id="myButton"
type="button">OK</button></div>
n2o Elements
#panel{id=Panel, body=#button{id=myButton, body=
<<"OK">>, postback={ok, <<"INFO">>}}}
<div id="myPanel"><button id="myButton"
type="button">OK</button></div>
document.getElementById('myButton').addEventListener(
'click',function (event){
ws.send(enc(tuple(atom('pickle'),bin('myButton'),bin('
g2gCaAVkAAJldmQABWluZGV4aAJkAAJva20AAAAESU5GT2sACG15Qn
V0dG9uZAAFZXZlbnRoA2IAAAWFYgAKp5tiAACpXA=='),
[tuple(tuple(utf8_toByteArray('myButton'),
bin('detail')), event.detail)])));
}
)
Мини-приложение
+
|
+--------------+-+ |
+----------------+ | web_app | instagram_app
+-+--------------+ | | |
WS | index | | | |
+----+ | | | | Instagram
| | | | |
+----+ | | | |
| | | | |
+--------> +---+ | +---------------------+ HTTP POST
| +^ +-+ +--------------+-+ | | |
+----------------+ +----------------+ | | | <----------+
|| +++--------------+ | | | | |
|-------| loop | | | | | <----------+
+-------+> | | | | | |
| | | | | | <---------+
| | | | | | |
| <+-----------+ |
| +-----------+ |
| +-+ | | |
+----------------+ | | |
| +---------------------+
|
index.erl
main() ->
#dtl{file="index", bindings=[{body, body()}]}.
body() ->
[#panel{id=butPanel, body=[
#button{id=catsButton, class= <<"btn">>,
body= <<"show #cats”>>,
postback={tag,<<"cats">>}}
]},
#panel{id=updater}].
index.erl
event({tag, Tag}) ->
wf:update(butPanel,
#h1{body= << <<"#">>/binary, Tag/binary>>}),
{ok, _Pid} = wf:async(Tag,
fun() -> loop(updater, Tag) end),
manager:subscribe(Tag),
wf:reg(Tag).
index.erl
loop(Elem, Tag) ->
receive
{gproc_ps_event, {tag, Tag}, Text} ->
insert_image(Elem, Text),
wf:flush(Tag);
timer:sleep(500),
loop(Elem, Tag)
end.
insert_image(Elem, Img) ->
wf:insert_top(Elem,
#panel{body=#panel{body=[
#link{href=maps:get(<<"link">>, Img),
body=#image{src=maps:get(<<"url">>,
maps:get(<<"low_resolution">>,
maps:get(<<"images">>, Img))),
}}]}}).
Демо
Вариант на Python
• asyncio (aiohttp + websockets)
• акторы, общение через очередь
• код на JavaScript
@asyncio.coroutine
def handler(websocket, _path):
new_image_receiver = ProcessQueue()
SubscriptionManager.subscribe(TAG, new_image_receiver)
while True:
message = yield from new_image_receiver.receive()
if not websocket.open:
SubscriptionManager.unsubscribe(TAG, new_image_receiver)
break
yield from websocket.send(json.dumps(message))
var socket = new WebSocket("ws://127.0.0.1:8765");
socket.onmessage = function (event) {
var msg = JSON.parse(event.data);
var img = document.createElement('img');
img.src = msg.images.low_resolution.url;
var pics = document.getElementById('pics');
pics.insertBefore(img, pics.firstChild);
}
Производительность
Req/sec Latency
n2o 12450 21
aiohttp 530 117
tornado 1500 580
nginx 17600 49
wrk -t4 -c1000 -d30s http://127.0.0.1:8080
Выводы
• n2o быстр, как в плане работы, так и в плане
разработки
• Можно не писать HTML и JavaScript
• Гибкость. Толстый клиент и REST endpoint, либо
умный сервер и простой клиент.
• Все плюсы Erlang
Выводы
• Свой DSL
• Нет нормальной документации API (но есть
Nitrogen API)
• Сыроват, меняется, нет тестов.
Ресурсы
• http://synrc.com/apps/n2o/doc/web
• http://nitrogenproject.com
• Learn You Some Erlang for Great Good!
• Programming Erlang
• Erlang плагин для IDEA http://ignatov.github.io/intellij-
erlang/
• https://github.com/etataurov/instastream

Erlang и n2o. Web-разработка без JavaScript