N2O
is Fast and Furious ~ 15K
Web Framework for Erlang
over Async WebSockets
with Nitrogen DSL and Templates
supports
Zepto or
jQuery
REST
included
Composability
N2O consists of
set of layers
you could easy tune or replace
Tuned Layers
static and dynamic Routing Path
cleaned Query Parser
Session Cookies stored in ETS
cached DTL Templates
optimized Nitrogen DSL rendering
HTML Elements binaries
JavaScript Actions deferred
Speed Disclaimer
requests pages/sec latency (ms)
wrk 15K 13628.86 18.88
ab 10K 5464.63 190
httperf 10K 3623.50 200
siege 1K 884.51 430
On same machine
raw webserver performance
measured with wrk:
NGNIX -- 60K
Latency
Deliver HTML ASAP
Deffered JavaScript delivery
after
WebSocket
connection established
<script>
TransitionProcess = '<0.7780.5>'
</script>
socket.send(["N2O",TransitionProcess]).
Templates
Plug your own fast Template Engine
#'EEX' { file = "template",
bindings = [ { title, Title },
{ body, Body } ] }
Routes
Redefine you own fast Routing
route(<<"/">>) -> {index, []};
route(<<"/index">>) -> {index, []};
route(<<"/hello">>) -> {hello, []};
REST
Develop your REST apps fast
init() -> ets:new(users, [named_table,{keypos,1}]),
ets:insert(users, ?USERS).
get([]) -> ets:foldl(fun(C,Acc) -> [C|Acc] end,[],users);
get(Id) -> ets:lookup(users,Id).
delete(Id) -> ets:delete(users,Id).
put(User) -> ets:insert(users,User).
exists(Id) -> ets:member(users,Id).
Mix DSL and DTL
Render both HTML and JavaScript with DSL :
#button { postback = login, source = [ user, pass ] }
Or render only JavaScript ..
#button { render = script,
id = ElementId,
postback = login,
source = [ user, pass ] }
.. when you develop with Templates approach
Actions
Client Updates are sent as an Actions
which are a JavaScript Strings for eval
render_action(Record) ->
wf:f("alert(\"~s\");", [wf:js_escape(Record#alert.text)]).
wf:wire(#alert{text="Hello, World!"}).
Element render
could create Actions
Action could include rendered Elements
Triggered Action may send Events
Elements
Plug your JavaScript controls
body() -> [ #textboxlist { },
#grid { },
and talk with them on Erlang
through WebSockets
Sample Login
main
() ->
Body =
wf
:
render
(
body
()
)
,
[
#dtl
{
file =
"login"
,
bindings
= [ {
body
, Body }
]
} ].
body
() ->
[
#span {
text
= "Login: " },
#textbox
{
id
=
user
},
#span
{
text
=
"Pass: "
},
#password
{
id
=
pass
},
#button
{
text
= "Login"
,
postback =
login
, source =
[
user
,
pass
]
} ].
event
(
login
) ->
wf
:
user
(
wf
:
q
(
user
)
),
wf
:
redirect
(
).
Render
<input value="Login" id=
"temp563149"
type="button"/>
$(
'#temp563149'
).bind(
'click'
,
function anonymous(event)
ws.send(Bert.encodebuf({
source: Bert.binary(
'temp563149'
),
pickle: Bert.binary('g2gCaINoAmgEZAA...'),
linked: [ Bert.tuple(Bert.atom(
'user'
),
utf8.toByteArray($(
'#user'
).val())),
Bert.tuple(Bert.atom(
'pass'
),
utf8.toByteArray($(
'#pass'
).val())) ]
}));
});
Events
From JavaScript
Everything is sent as an Event
Postback event/1
Control control_event/2
API api_event/3
Handle events in Erlang...
Events Nature
Events are enveloped by BERT
as #ev{}
-record
( ev,
{ name :: api_event | control_event | event | atom () ,
payload
,
trigger
,
module
::
atom
() } ).
You can define your own entry points
Postback Events
Create Page Logic with
Postback Events event/1
main() -> [ #button { postback = login } ] .
event(login) -> wf:info("Logged User: ~p", [wf:user()]).
Control Events
Handle Data from Controls with
Control Events control_event/2
render_element( E = #myelement { } ) -> do_render( E ).
control_event(Trigger, Data) ->
wf:info("Received Data ~p from JavaScript Control ~p",
[ Data, Trigger ] ).
... when you cteate your own Elements
API Events
Wire Erlang and JavaScript with
API Events api_event/3
main() -> wf:wire(
#api { name = notify, tag = api3
} ), [ ].
api_event(Event, Args, Req) ->
wf:info( "Received from JavaScript: ~p", [ Args ] ).
document.notify('Hello from JavaScript').
Resources
Sources
HTML and TeX Handbook
Twitter