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:

-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.

Comments

comments powered by Disqus