Misultin: erlang and websockets
Jan 2010

Inspired by Joe Armstrong’s post, I’ve recently added websocket support to misultin v0.4, my Erlang library for building fast lightweight HTTP servers.

Basically, websockets allow a two-way asynchronous communication between browser and servers, filling the gap that some technologies such as ajax and comet have tried to fulfill in these recent years. If you want to try this out yourself, you will first need to grab a browser which implements websockets, such as Google Chrome.

The typical html page with javascript code to use websockets is as follows:

<html>
   <head>
     <script type="text/javascript">
         function addStatus(text){
            var date = new Date();
            document.getElementById('status').innerHTML = document.getElementById('status').innerHTML
               + date + ": " + text + "<br>";
         }
         function ready(){
            if ("WebSocket" in window) {
               // browser supports websockets
               var ws = new WebSocket("ws://localhost:8080/service");
               ws.onopen = function() {
                  // websocket is connected
                  addStatus("websocket connected!");
                  // send hello data to server.
                  ws.send("hello server!");
                  addStatus("sent message to server: 'hello server'!");
               };
               ws.onmessage = function (evt) {
                  var receivedMsg = evt.data;
                  addStatus("server sent the following: '" + receivedMsg + "'");
               };
               ws.onclose = function() {
                  // websocket was closed
                  addStatus("websocket was closed");
               };
            } else {
               // browser does not support websockets
               addStatus("sorry, your browser does not support websockets.");
            }
         }
      </script>
   </head>
   <body onload="ready();">
      <div id="status"></div>
   </body>
</html>

Here’s the code to use misultin to handle the requests of this script:

-module(misultin_websocket_example).
-export([start/1, stop/0]).

% start misultin http server
start(Port) ->
	misultin:start_link([{port, Port}, {loop, fun(Req) -> handle_http(Req, Port) end},
	{ws_loop, fun(Ws) -> handle_websocket(Ws) end}]).

% stop misultin
stop() ->
	misultin:stop().

% callback on request received
handle_http(Req, Port) ->
	% output
	Req:ok([]).

% callback on received websockets data
handle_websocket(Ws) ->
	receive
		{browser, Data} ->
			Ws:send(["received '", Data, "'"]),
			handle_websocket(Ws);
		_Ignore ->
			handle_websocket(Ws)
	after 5000 ->
		Ws:send("pushing!"),
		handle_websocket(Ws)
	end.

handle_websocket/1 is spawned by misultin to handle the connected websockets. Data coming from a browser will be sent to this process and will have the message format {browser, Data}, where Data is a string(). If you need to send data to the browser, you may do so by using the parametrized function Ws:send(Data), Data being a string() or an iolist().

Compile and run the example here above with misultin_websocket_example:start(8080). Then, open up your Chrome (or other websocket compliant browser) and point it to an .html file containing the above code.

You should normally see this being gradually printed on your browser:

Wed Jan 20 2010 15:18:52 GMT+0100 (CET): websocket connected!
Wed Jan 20 2010 15:18:52 GMT+0100 (CET): sent message to server: 'hello server'!
Wed Jan 20 2010 15:18:52 GMT+0100 (CET): server sent the following: 'received 'hello server!''
Wed Jan 20 2010 15:18:57 GMT+0100 (CET): server sent the following: 'pushing!'
Wed Jan 20 2010 15:19:02 GMT+0100 (CET): server sent the following: 'pushing!'
Wed Jan 20 2010 15:19:07 GMT+0100 (CET): server sent the following: 'pushing!'

In normal environments you may consider serving the .html page from misultin directly. You may do so with the following and complete misultin module:

-module(misultin_websocket_example).
-export([start/1, stop/0]).

% start misultin http server
start(Port) ->
   misultin:start_link([{port, Port}, {loop, fun(Req) -> handle_http(Req, Port) end}, {ws_loop, fun(Ws) -> handle_websocket(Ws) end}]).

% stop misultin
stop() ->
   misultin:stop().

% callback on request received
handle_http(Req, Port) ->
   % output
   Req:ok([{"Content-Type", "text/html"}],
   ["
   <html>
      <head>
         <script type=\"text/javascript\">
            function addStatus(text){
               var date = new Date();
               document.getElementById('status').innerHTML = document.getElementById('status').innerHTML
                  + date + \": \" + text + \"<br>\";
            }
            function ready(){
               if (\"WebSocket\" in window) {
                  // browser supports websockets
                  var ws = new WebSocket(\"ws://localhost:", integer_to_list(Port) ,"/service\");
                  ws.onopen = function() {
                     // websocket is connected
                     addStatus(\"websocket connected!\");
                     // send hello data to server.
                     ws.send(\"hello server!\");
                     addStatus(\"sent message to server: 'hello server'!\");
                  };
                  ws.onmessage = function (evt) {
                     var receivedMsg = evt.data;
                     addStatus(\"server sent the following: '\" + receivedMsg + \"'\");
                  };
                  ws.onclose = function() {
                     // websocket was closed
                     addStatus(\"websocket was closed\");
                  };
               } else {
                  // browser does not support websockets
                  addStatus(\"sorry, your browser does not support websockets.\");
               }
            }
         </script>
      </head>
      <body onload=\"ready();\">
         <div id=\"status\"></div>
      </body>
   </html>"]).

% callback on received websockets data
handle_websocket(Ws) ->
   receive
      {browser, Data} ->
         Ws:send(["received '", Data, "'"]),
         handle_websocket(Ws);
      _Ignore ->
         handle_websocket(Ws)
   after 5000 ->
      Ws:send("pushing!"),
      handle_websocket(Ws)
   end.

Please note that the Websocket Protocol still is draft. use with caution.

Comments (9)

Very clean. Well done.

1. Posted by David Semeria — March 21, 2011 @ 6:38 pm

Thanks for the post.
I have run the code using the example misultin_websocket_example under example directory, and it works in chrome, but failed in my iPad iOS 4.2, when i open the URL in my ipad, i just got websocket closed, any idea? thanks.

2. Posted by ccokme — April 20, 2011 @ 6:45 am

That probably means that your browser doesn’t have websockets enabled.

3. Posted by Jonathan Langdale — August 7, 2011 @ 5:54 pm

Thanks for this.
For other people hitting this, websocket protocol has changed, new browsers (ie: Firefox 7) use the new version, it is now using this specification: draft-hybi

http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5.6

4. Posted by gilemon — October 21, 2011 @ 3:24 pm

Hi gilemon,

these are already covered in the DEV branch of misultin.

Cheers,

r.

5. Posted by Roberto Ostinelli — October 23, 2011 @ 12:37 am

In my fork of mochiweb I have implemented a dispatcher that is starting a new erlang process matching an html page. If you browse to fobar.html fobar.erl is spawned and implements the websocket functions for that page.

Is that something you already have in misultin?

6. Posted by Stina Kvamme — December 4, 2011 @ 6:20 pm

Hi Stina,

misultin implements just a static directory, but no, it does not automatically convert REST to filenames.

it would be a nice addition on top of it, but not as core.

7. Posted by Roberto Ostinelli — December 4, 2011 @ 8:35 pm

Ok, it will be another example then.
Thanks for your good work :)

8. Posted by Stina Kvamme — December 4, 2011 @ 8:47 pm

[...] some help. CoffeeScript + Processing.js == Crazy Delicious gave me the idea to try CoffeeScript and Misultin: erlang and websockets gave the shell for the Erlang HTTP [...]

Leave a comment