Building a Tic Tac Toe game with Cowboy & websockets (Part 1)

Tic-tac-toe (or noughts and crosses) is a good example project, as the game itself is pretty simple and won’t become a distraction. I’m going to try and build a version using Erlang, and more specifically Cowboy: an http server/framework.

I’m going to assume you know the basics of setting up a new project, and skip straight to the more interesting bits. The goal is to create a multi-player, websockets & html based version of tic-tac-toe. As it’s turn-based, we avoid a lot of the really thorny problems of “real-time” multi-player games.

My first step was to create a session as soon as the socket is opened:

socket.onopen = function () {

The backend then needs to handle this frame. I decided to add a session manager as a gen_server:

-record(state, {sessions=#{}}).

handle_call(new_session, _From, State) ->
    SessionId = uuid:uuid_to_string(uuid:get_v4(), binary_standard),
    NewState = update_session(SessionId, State),
    {reply, {ok, SessionId}, NewState};

update_session(SessionId, State) ->
    Expiry = half_an_hour_from_now(),
    prune_expired_sessions(State#state{sessions=maps:put(SessionId, Expiry, State#state.sessions)}).

half_an_hour_from_now() ->
    Now = calendar:universal_time(),
    calendar:gregorian_seconds_to_datetime(calendar:datetime_to_gregorian_seconds(Now) + (30 * 60)).

prune_expired_sessions(State) ->
    SessionIds = maps:keys(State#state.sessions),
    {_, ExpiredSessions} = lists:partition(fun(S) -> session_valid(S, State#state.sessions) end, SessionIds),
    State#state{sessions=maps:without(ExpiredSessions, State#state.sessions)}.

session_valid(SessionId, Sessions) ->
    case maps:find(SessionId, Sessions) of
        error -> false;
        {ok, Expires} ->
            Expires > calendar:universal_time()

In a more realistic app, you would probably want to perform some authentication here, and use a datastore of some kind; but we’ll just create a new session and return the id. We also need to add the new server to the supervision tree:

init([]) ->
    Procs = [ 
        {t3_session_manager, {t3_session_manager, start_link, []}, permanent, 5000, worker, [t3_session_manager]}
    {ok, {{one_for_one, 1, 5}, Procs}}.

We can then call the server from the ws handler:

websocket_handle({text, <<"new_session">>}, Req, State) ->
    Resp = start_new_session(),
    {reply, make_frame(Resp), Req, State};

start_new_session() ->
    {ok, SessionId} = gen_server:call(t3_session_manager, new_session),
    #{type => <<"new_session">>, sessionId => SessionId}.

make_frame(Msg) ->
    Json = jiffy:encode(Msg),
    {text, Json}.

We can call the server using it’s module name, rather than a pid, because it registered itself when starting:

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

(Watch out though, this effectively makes it a singleton, which could be a choke-point in a real app). We need to handle the response on the client:

socket.onmessage = function(ev) {
    console.log('Received data: ' +;
    var msg = JSON.parse(;

    if (msg.type === 'new_session') {
        sessionId = msg.sessionId;
        newGameBtn.disabled = false;

And we’ll probably also need to be able to validate a session at some point:

handle_call({validate_session, SessionId}, _From, State) ->
    case session_valid(SessionId, State#state.sessions) of
        false ->
            {reply, {error, invalid_session}, State};
        true ->
            %% sliding expiry window
            NewState = update_session(SessionId, State),
            {reply, ok, NewState}

You can find the full source code here. In Part 2, we’ll look at starting a new game.

Hello world with Cowboy and Websockets

Cowboy is a web server/framework written in Erlang, that provides pretty seamless websocket support.

The “getting started” guide is very good, and I’m going to assume you’ve read it, and followed it up to the “cowboy setup” section.

We’re going to add a ws handler, rather than an http one, using the template:

make new t=cowboy_ws n=ws_handler

And wire it up in the app:

start(_Type, _Args) ->
    Dispatch = cowboy_router:compile([{'_', [
        {"/connect", ws_handler, []} 
    Port = 8080,
    cowboy:start_http(my_http_listener, 100, [{port, Port}], [{env, [{dispatch, Dispatch}]}]),

You could now build and run the release, and connect to a websocket by calling http://localhost:8080/connect. But it wouldn’t be very interesting… yet.

The next job is to add a web page that will call the server, in priv/static/index.html:



var socket = new WebSocket('ws://localhost:8080/connect');
socket.onopen = function() {

and add a static handler to the dispatcher:

    Dispatch = cowboy_router:compile([{'_', [
        {"/", cowboy_static, {priv_file, ping_pong, "static/index.html"}},
        {"/connect", ws_handler, []} 

Still not hugely exciting, but if you look in the js console in your browser you should be connected to a websocket! We can now send a message to the server:

socket.onopen = function() {

function send(data) {
    console.log('Sending data: ' + data);

and return the standard response:

websocket_handle({text, <<"ping">>}, Req, State) ->
    Reply = {text, <<"pong">>},
    {reply, Reply, Req, State};

which we can listen for on the client:

socket.onmessage = function(ev) {
    console.log('Received data: ' +;

If all that went well, then we can liven things up by adding some json into the mix. First, add jiffy as a dependency (other json libraries are available!), then update the js to send some json instead:

socket.onopen = function() {
    var msg = {type: 'ping', count: 1};

and handle that on the server:

websocket_handle({text, Json}, Req, State) ->
    Map = jiffy:decode(Json, [return_maps]),
    Count = maps:get(<<"count">>, Map),
    Reply = #{type => <<"pong">>, count => Count + 1},
    {reply, {text, jiffy:encode(Reply)}, Req, State};

Just remember that the socket receives the json as text, you need to parse it!

socket.onmessage = function(ev) {
    console.log('Received data: ' +;
    var msg = JSON.parse(;

An alternative to parsing the json is to use pattern matching:

websocket_handle({text, <<"{\"type":\"ping\",\"count\":", Count:1/binary, "}">>}, Req, State) ->
    Reply = #{type => <<"pong">>, count => list_to_integer(binary_to_list(Count)) + 1}, 
    {reply, {text, jiffy:encode(Reply)}, Req, State};

but in this case, it has the disadvantage that the length of Count has to be specified.

You can find the source code for these examples here, and there’s another example in the Cowboy repo.

Using the gen_server template

I can never remember how to do this when the time comes, so here’s a note to future self:

make new t=gen_server n=foo_server

The generated file will be in src/foo_server.erl. You can also list all the available templates using:

make list-templates

Available templates: cowboy_http cowboy_loop cowboy_rest cowboy_ws gen_server ranch_protocol supervisor

Or just look at the source.

Json, in a jiffy!

Working with json in Erlang has vastly improved since the last time I looked at it, particularly with the addition of maps to the language (in R17).


-export([allowed_methods/2, content_types_accepted/2]).

init(_Transport, _Req, []) ->
    {upgrade, protocol, cowboy_rest}.

allowed_methods(Req, State) ->
    {[<<"POST">>], Req, State}.

content_types_accepted(Req, State) ->
        {<<"application/json">>, foo}
    ], Req, State}.

foo(Req, State) ->
    {ok, Body, Req1} = cowboy_req:body(Req),
    Json = jiffy:decode(Body, [return_maps]),
    Bar = maps:get(<<"bar">>, Json),
    {ok, Res} = do_something(Bar),
    Body = jiffy:encode(#{result => Res}),
    {true, cowboy_req:set_resp_body(Body, Req1), State}.

Application metadata file exists but is malformed

I was playing around with Cowboy recently. I had followed the Getting Started guide, but was hoping to avoid having to get into releases. Sadly, the easiest way to serve static files is from an application priv_dir.

I created a relx.config file, but when I ran make I got a cryptic error:

===> Starting relx build process ...
===> Resolving OTP Applications from directories:
===> Application metadata file exists but is malformed: /vagrant/ebin/
===> Resolving available OTP Releases from directories:
Failed to solve release:
 Dependency my_app is specified as a dependency but is not reachable by the system.

I was pretty sure my app metadata file wasn’t malformed, but a quick dig through the sauce led me back to an ominous warning in the cowboy docs:

the modules line will be replaced with the list of modules during compilation; make sure to leave this line even if you do not use it directly

Adding an empty modules list was enough to build a release successfully:

{application, my_app, [
    {description, "My App"},
    {modules, []},
===> Starting relx build process ...
===> Resolving OTP Applications from directories:
===> Resolving available OTP Releases from directories:
===> Resolved my_app-1
===> Including Erts from /usr/local/lib/erlang
===> release successfully created!

Faking a response to a gen_server call

I’ve been trying to write a unit test for an FSM, that makes a call to a gen_server:

Result = gen_server:call(Pid, foo).

I wanted to fake the response to the call, without needing to spin up a gen_server instance. My first attempt didn’t really get me anywhere:

Result = ok,
Pid = spawn(fun() ->
        _ -> Result

But some digging through the OTP source revealed the solution:

Result = ok,
Pid = spawn(fun() ->
        {_, {Caller, Ref}, _} -> Caller ! {Ref, Result}

Starting mnesia with a webmachine app

I have a webmachine app that uses Mnesia as an in-memory DB. To begin with, I was just calling mnesia:start() in the erlang terminal provided after running But, after forgetting to start it a couple of times, I looked for a better solution.

My first stop, the internet, didn’t provide me with any answers (either it should be obvious, or I was using the wrong keywords!). So I decided to try adding it to the dependency list in my application resource file (.app.src).

{application, restbucks,
  {description, "restbucks"},
  {vsn, "1"},
  {modules, []},
  {registered, []},
  {applications, [
                  ... snip ...
  {mod, { restbucks_app, []}},
  {env, []}

Unfortunately, that didn’t seem to have any effect whatsoever.

Luckily, I’d discovered through my adventures with eunit, that erl can take a number of arguments (-s) that will be executed when starting. So I modified my script appropriately:

exec erl -pa $PWD/ebin $PWD/deps/*/ebin -boot start_sasl -s mnesia start -s reloader -s restbucks

Whether this is a good idea remains to be seen, but it seems to work!