Monday 4 January 2010

Web Sockets and Haskell

Web Sockets are a new technology which allows continuous bi-directional communication between the browser and the server. It's not supported by many browsers at the moment, but recently Google announced support for it in Chrome.

The API isn't very difficult to understand, as there's only three callback functions! At the client level you simply create a socket connection with callbacks for open connection, close connection and message received. As a consumer of web sockets that's all you need to know.

The server side is a little more complicated (but not much). There's a new protocol (web socket protocol). To explore this, I built a quick Haskell application to send data from Haskell to the browser. I haven't quite got how to send data back again (DOM Exception 11 is just *so* helpful for debugging).

Before your web sockets code will do anything, it must be hosted on a server and not served from the file system. To configure Apache to do this on Ubuntu, you'll need to edit /etc/apache2/sites-available/default and make it point to the appropriate place (or copy it and create your own configuration). Apache can be restarted with apache2ctl restart.

I used the Network package and a simple main function which accepts a connection, spawns a new thread to handle it and repeats the process.

The most complicated part of the code was getting the server handshake correct. The below is hardcoded for communication on localhost with port 9876. Get the handshake wrong and you'll get all sorts of baffling errors. I found that tcpdump was invaluable in troubleshooting these kinds of errors as I was able to capture the packets and compare them to a web sockets example in Erlang.


import Network
import System.IO
import Control.Concurrent
import Char

serverHandshake =
"HTTP/1.1 101 Web Socket Protocol Handshake\r\n\
\Upgrade: WebSocket\r\n\
\Connection: Upgrade\r\n\
\WebSocket-Origin: http://localhost\r\n\
\WebSocket-Location: ws://localhost:9876/\r\n\
\WebSocket-Protocol: sample\r\n\r\n"

acceptLoop socket = forever $ do
(h,_,_) <- accept socket
hPutStr h serverHandshake
hSetBuffering h NoBuffering
forkIO (listenLoop h)
where
forever a = do a; forever a

main = withSocketsDo $ do
socket <- listenOn (PortNumber 9876)
acceptLoop socket
sClose socket
return ()

listenLoop :: Handle -> IO ()
listenLoop h = do
sendFrame h "hello from haskell"
threadDelay (3 * 1000000)
sendFrame h "it works!"
return ()

sendFrame :: Handle -> String -> IO ()
sendFrame h s = do
hPutChar h (chr 0)
hPutStr h s
hPutChar h (chr 255)


forkIO makes handling the socket on a separate thread trivial, it spawns off a new lightweight thread (lightweight in the sense of being very cheap to create) to handle the IO processing.

The browser side isn't too bad either. I used Google's CDN to pull in JQuery and just hooked up some handlers to append various bits and pieces as events occurred.


<html>
<head>
<title>Web Sockets</title>

<script src="http://www.google.com/jsapi"></script>
<script>
google.load('jquery','1.3.2');
</script>

<script>
$(document).ready(function() {

if ("WebSocket" in window) {
var ws = new WebSocket("ws://localhost:9876/");
ws.onopen = function() {
$('#connectionStatus').text('Connection opened');
};
ws.onmessage = function(evt) {
$('#output').append('<p>' + evt.data);
};
ws.onclose = function() {
$('#connectionStatus').text('Connection closed');
};
}
else {
$('#connectionStatus').append('<p>Your browser does not support web sockets</p>');
}
});
</script>

</head>
<body>
<h1>I'm doing something</h1>

<div id="output">
</div>

<div id="connectionStatus">
</div>

</body>
</html>


I guess the next task is to do something exciting with web sockets!

(Update: After the extremely helpful comments I was able to get Bidirectional Web Sockets working

(Update 2: Realized that the server hand shake should not have a trailing \0 so remove it in the code example).