Tuesday, 5 January 2010

Bidirectional Web Sockets

Thanks to some very helpful comments on the last post and some example code I was able to see what I was doing wrong with bidirectional communication.

I'd tried to use hGetContents and just read off one message at a time. This is doomed to failure because laziness and hanging around on a network socket that's in a semi-closed state just isn't going to work.

In this version of the code we enter a loop which terminates once the client sends the "quit" message (rather than an infinite loop).

(Update: Thanks to helpful comments edited the code to use Control.Monad.when)

listenLoop :: Handle -> IO ()
listenLoop h = do
sendFrame h "hi, remember you can stop this at anytime by pressing quit!"
msg <- readFrame h
putStrLn msg
when (msg /= "quit") (listenLoop h)

readFrame :: Handle -> IO String
readFrame h = readUntil h ""
readUntil h str = do
new <- hGetChar h
if (new == chr 0)
then readUntil h ""
else if new == chr 255
then return str
else readUntil h (str ++ [new])

On a side note - the indentation for the if/then/else is a little strange, but "if within do" at least explains why.

All that remains is a quick bit of HTML to allow you to have a conversation. Replace the body in the previous example with something like this (and also make sure that the ws object has the appropriate scope!

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

<div id="output">

<div id="connectionStatus">

<textarea rows="2" cols="80" id="message">
Type whatever you want here, but type quit to close the connection

<br />

<button id="clickMe" onClick="ws.send($('#message').val());">
Click me!

You should get something where you can have a rather boring conversation with Haskell. It repeats the same text over and over again until you press quit.