.. post:: 2015-01-06 :tags: Erlang, N2O :author: Rudi Grinberg Omegle in N2O ============= Recently I've been messing around with a new and exciting web framework in erlang called N2O. This framework appeals to me for a few reasons. These include: - It's lean. Clocking in around 4.5k lines, it's small enough for me to read the source whenever I don't understand something, and modify it to my liking if I ever find it inadequate. Just imagine doing any of that with something like Rails. - It's powerful. Quite a bit can be accomplished with comparatively little code. The rest of my post will provide some evidence for that. - It's "realtime" (in the web 2.0 sense). Bidrectional streaming is a core tenant of the framework and Web Sockets are exceptionally easy to use. - It's full stack. I'm not interested in a so called framework that just lets you dump some static html and leaves you to the JS sharks. Unless I'm making an API. N2O has an erlang to js compiler (shen), nice integration with a templating engine (erlydtl), and lots of other utilities for making front end work much easier. To demonstrate all of the properties above, I've implemented a tiny omegle clone in N2O. The aim being to showcase how n2o gets out of your way and lets you solve such problems in a concise and natural manner. Implementation -------------- The full implementation is available at my `github repo `__. Which also contains instructions how to run the project. It should serve as a good project template as well, since there isn't an official one for rebar. Most of the code is one file - omegle.erl. There's certainly other code but it's mostly scaffolding for setting up an n2o app on top of cowboy. The heart of the code is short enough to include here: .. code-block:: erlang -module(omegle). -compile(export_all). -include_lib("n2o/include/wf.hrl"). %% The main function is the entrance point to your n2o handler main() -> #dtl{ file="index", app=nitroshell, bindings=[{body, body()}] }. body() -> %% spawn a background process responsible for pairing users {ok, Pid} = wf:async("matcher", fun () -> matcher() end), [ #span{ id=status, body="Welcome"}, #br{}, #span{ id=ui, body=ui(seek, Pid) } ]. %% helper functions for constructing the dom ui(seek, Pid) -> [ #button{ id=seekButton, body="Seek", postback={seek, Pid}} ]; ui(chat, Pid) -> [ #textbox{ id=message }, #button{ id=sendButton, body="Send", postback={chat, Pid}, source=[message]}, #span{ id=history } ]. message_ui(From, Msg) -> #pre{ body=(From ++ ": " ++ Msg) }. set_status(S) -> wf:update(status, #span{ id=status, body=S}). append_history(Msg) -> wf:insert_bottom(history, Msg). %% Event handler function called on dom/chat events event({seek, Pid}) -> %% user starts to seek Pid ! {seek, self()}, set_status("Seeking..."), wf:update(ui, #span{ id=ui }); event({chat, Pid}) -> %% user sends chat message Message = wf:q(message), append_history(message_ui("You", Message)), Pid ! {direct, {inbox, Message}}; %% incoming message from user event({inbox, Message}) -> append_history(message_ui("Anonymous", Message)); %% signal a user that he's been paired up event({connected, To}) -> wf:update(ui, ui(chat, To)), set_status("Connected. Say Hi."). %% Pairing up users has 2 responsibilities, hence another process is needed matcher() -> {ok, Q} = ebqueue:start_link(), _Pid = spawn_link(fun () -> matcherQ(Q) end), matcherRcv(Q). %% first responsibility is to add willing users to the queue matcherRcv(Q) -> receive {seek, Pid} -> ebqueue:in(Pid, Q) end, matcherRcv(Q). %% second is to consume the queue 2 items a time and pair up the users matcherQ(Q) -> {ok, Pid1} = ebqueue:out(Q), {ok, Pid2} = ebqueue:out(Q), Pid1 ! {direct, {connected, Pid2}}, Pid2 ! {direct, {connected, Pid1}}, matcherQ(Q). I do have to confess that I've cheated a little bit. I needed a very simple implementation of a blocking queue. I've made a quick and dirty version and called it `ebqueue `__. Hopefully the code above inspires you to checkout n2o and mess around with it. Especially if you're coming from the world of big heavy frameworks, in erlang or otherwise.