Information for Nitrogen users:
- All actions are triggered through single endpoint
- Works heavy coupled within Cowboy processes, but still simple_bridge compatible
- No unnecessary process spawns
- Dependency on Bert / jQuery / Bullet only
- no JSON encoding for client/server data transfer, no use of urlencode and Nitrogen.js
- Enough compatibility with original Nitrogen to convert Nitrogen sites to N2O
- Clean codebase
- Proper id and class attributes
- 2x faster that original Nitrogen
- Page construction from Erlang binaries
- Custom template engines as elements: DTL, SGTE, ET
- Advanced element collection: Tabs, Grid, Viz.js, Mandala
- XHR fallback through Bullet
- Rapid REST apps prototyping
- GProc process registry instead of nprocreg
N2O was started as the first Erlang Web Framework that fully relies on WebSocket transport for client / server communication. Compatibility with Nitrogen was mostly retained and many new improvements were made. Such as binary page construction, binary data transfer, events trigger over WebSocket channel, minumum process spawns, use of Cowboy processes to run N2O html rendering. N2O page rendering is several times faster than with the original Nitrogen.
N2O does not use JSON to encode data transfer between client / server. Instead, all data communication is encoded with native Erlang External Term Format. For that Berg.js library is used. Is it as simple as calling Bert.decode( msg ) and this allows to avoid complexity on the server-side. Please refer to http://bert-rpc.org for more information.
Original Nitrogen was tested in production under high-load and we decided to drop nprocreg process registry along with action_comet process creation. N2O creates only a single process for async websocket handler, all async operations are handled within Cowboy processes.
We have measured the performance of most of the existing modern web frameworks created with functional languages and Cowboy is still the king. You can see raw HTTP performance of functional and C-like languages with concurrent primitives (Go, D and Rust) on VAIO Z notebook with i7640M processor:
N2O greatly outperforms Nitrogen stack and is only ~2X slower than the raw HTTP Cowboy performance thus beating rendering performance of any other functional web framework several times over. It is certanly faster than raw HTTP node.js performance!
N2O does that. However, we agree that in some cases it is better to send only domain specific data over network (in JSON or Binary format) as you would do with Chaplin / CoffeScript and Meteor / JavaScript. However, Meteor is Node-based. And with “everything on the client” model it would not be possible to prototype services easily with your Erlang infrastructure. You should always prototype a server side protocol along with the client.
So, in cases when your system is built around Erlang infrastructure, N2O is the best choice that you could find for fast prototyping, simpicity of use, codebase maintanance, etc. Despite HTML tranfer over the wire, you will still have access to all your erlang services directly.
We liked Nitrogen for simple and elegant way of typed HTML page construction similar to Scala Lift, OCaml Ocsigen and Haskell Happstack. Template-based approach pushes programmers to deal with raw HTML, like Yesod, ASP, PHP, JSP, Rails, Yaws, ChicagoBoss. N2O goes further than that and optimizes HTML rendering from binary iolists instead of slower Erlang lists as in Nitrogen.
Main N2O attraction is the fast prototyping. We also use it in large scale projects. Here is the complete Web Chat example working with WebSockets:
-module(chat).
-compile(export_all).
-include_lib("n2o/include/wf.hrl").
main() -> #template { file= code:priv_dir(web) ++ "/templates/index.html" }.
title() -> <<"N2O">>.
body() -> %% area of http handler
{ok,Pid} = wf:comet(fun() -> chat_loop() end),
[ #span { text= <<"Your chatroom name: ">> },
#textbox { id=userName, text= <<"Anonymous">> },
#panel { id=chatHistory, class=chat_history },
#textbox { id=message },
#button { id=sendButton, text= <<"Send">>, postback={chat,Pid}, source=[userName,message] },
#panel { id=status } ].
event({chat,Pid}) -> %% area of websocket handler
Username = wf:q(userName),
Message = wf:q(message),
Terms = [ #span { text="Message sent" }, #br{} ],
wf:insert_bottom(chatHistory, Terms),
wf:wire("$('#message').focus(); $('#message').select(); "),
wf:reg(room),
Pid ! {message, Username, Message};
event(Event) -> error_logger:info_msg("Event: ~p", [Event]).
chat_loop() -> %% background worker ala comet
receive
{message, Username, Message} ->
Terms = [ #span { text=Username }, ": ",
#span { text=Message }, #br{} ],
wf:insert_bottom(chatHistory, Terms),
wf:wire("$('#chatHistory').scrollTop = $('#chatHistory').scrollHeight;"),
wf:flush(room); %% we flush to websocket process by key
Unknown -> error_logger:info_msg("Unknown Looper Message ~p",[Unknown])
end,
chat_loop().
And try to compare how this functionality would be implemented with your favourite language / framework.
We feel free to brake some of the compatability with the original Nitrogen project, mostly because we want to have a clean codebase. However, it is still possible to easily port Nitrogen web sites to N2O. E.g. N2O returns id and class semantics of HTML and not html_id. We simplify rendering by not using html_encode which should be handled by the application layer. Nitrogen.js that was originally created by Rusty Klophaus for XHR, has been removed due to pure WebSocket nature of N2O and native jQuery handling. There are plans to add XHR support by using some of the ideas from Bullet.js - such as fallback from websockets to XHR.
<input id="sendButton" type="button" class="sendButton button" value="Send"/>
<script>$('#sendButton').bind('click',function anonymous(event) {
ws.send(Bert.encodebuf({source: Bert.binary('sendButton'),
pickle: Bert.binary('R2LH0INQAAAAWXicy2DKYEt...'),
xforms: Bert.binary('undefined')}));});</script>
To run N2O sites you need Erlang R14 or higher and basho rebar installed. N2O works on Windows, Mac and Linux.
To try N2O you just need to clone a N2O repo from Github and build. We don’t use fancy scripts so building process is OTP compatible: bootstrap site is bundled as Erlang release.
$ cd samples
$ rebar get-deps
$ rebar compile
$ cd rels/web && rebar -f generate
$ node/bin/node console
Now you can try: http://localhost:8000.
If you want dependency on the raw N2O core you should define N2O http and websocket cowboy handlers and cowboy static handler as Cowboy dispatch parameter:
cowboy:start_http(http, 100, [{port, 8000}],
[{env, [{dispatch, dispatch_rules()}]}]),
dispatch_rules() ->
cowboy_router:compile(
[{'_', [
{["/static/[...]"], cowboy_static, [{directory, {priv_dir, ?APP, [<<"static">>]}},
{mimetypes, {fun mimetypes:path_to_mimes/2, default}}]},
{["/websocket/[...]"], n2o_websocket, []},
{'_', n2o_cowboy, []}
]}]).
And put minimal index.erl page:
-module(index).
-compile(export_all).
-include_lib("n2o/include/wf.hrl").
main() -> #span{text=<<"Hello">>}.
For developing we use some scripts which are needed for linking source directories with release lib directories and also links to BERT, N2O and jQuery javascript. After making release you should run:
$ ./nitrogen_static.sh
$ ./release_sync.sh
Now you can edit site sources and sync will automaticaly recompile and reload modules in release.
- Maxim Sokhatsky
- Andrii Zadorozhnii
- Roman Shestakov
- Jesse Gumm
- Rusty Klophaus
OM A HUM