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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
-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:
1 2 3 4 5 6 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
-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.
Very clean. Well done.
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.
That probably means that your browser doesn’t have websockets enabled.
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
Hi gilemon,
these are already covered in the DEV branch of misultin.
Cheers,
r.
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?
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.
Ok, it will be another example then.
Thanks for your good work :)