ghcjs-websockets: GHCJS interface for the Javascript Websocket API

[ deprecated, library, mit, web ] [ Propose Tags ]
Deprecated

Documentation online at http://mstksg.github.io/ghcjs-websockets/JavaScript-WebSockets.html

'ghcjs-websockets' aims to provide a clean, idiomatic, efficient, low-level, out-of-your-way, bare bones, concurrency-aware interface with minimal abstractions over the Javascript Websockets API http://www.w3.org/TR/websockets/, inspired by common Haskell idioms found in libraries like 'io-stream' http://hackage.haskell.org/package/io-streams and the server-side websockets http://hackage.haskell.org/package/websockets library, targeting compilation to Javascript with ghcjs.

The interface asbtracts websockets as simple IO/file handles, with additional access to the natively "typed" (text vs binary) nature of the Javascript Websockets API. There are also convenience functions to directly decode serialized data (serialized with binary http://hackage.haskell.org/package/binary) sent through channels.

The library is mostly intended to be a low-level FFI library, with the hopes that other, more advanced libraries maybe build on the low-level FFI bindings in order to provide more advanced and powerful abstractions. Most design decisions were made with the intent of keeping things as simple as possible in order for future libraries to abstract over it.

Most of the necessary functionality is in hopefully in JavaScript.WebSockets; more of the low-level API is exposed in JavaScript.WebSockets.Internal if you need it for library construction.

See the JavaScript.WebSockets module for detailed usage instructions and examples.

Some examples:

import Data.Text (unpack)

-- A simple echo client, echoing all incoming text data
main :: IO ()
main = withUrl "ws://my-server.com" $ \conn ->
    forever $ do
        t <- receiveText conn
        putStrLn (unpack t)
        sendText conn t
-- A simple client waiting for connections and outputting the running sum
main :: IO ()
main = withUrl "ws://my-server.com" (runningSum 0)

runningSum :: Int -> Connection -> IO ()
runningSum n conn = do
    i <- receiveData conn
    print (n + i)
    runningSum (n + i) conn
-- Act as a relay between two servers
main :: IO ()
main = do
    conn1 <- openConnection "ws://server-1.com"
    conn2 <- openConnection "ws://server-2.com"
    forever $ do
        msg <- receiveMessage conn1
        sendMessage conn2 msg
    closeConnection conn2
    closeConnection conn1

[Skip to Readme]

Flags

Automatic Flags
NameDescriptionDefault
ghcjs

Tell cabal we are using ghcjs (work around until hackage supports impl(ghcjs))

Enabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

Versions [RSS] 0.3.0.0, 0.3.0.1, 0.3.0.2, 0.3.0.3, 0.3.0.4, 0.3.0.5 (info)
Change log CHANGELOG.md
Dependencies base (>=4.7 && <5), base64-bytestring (>=1), binary (>=0.7), bytestring (>=0.10), ghcjs-base (>=0.1), text (>=1) [details]
License MIT
Copyright Copyright (c) Justin Le 2015
Author Justin Le <justin@jle.im>
Maintainer Justin Le <justin@jle.im>
Category Web
Home page http://github.com/mstksg/ghcjs-websockets
Source repo head: git clone https://github.com/mstksg/ghcjs-websockets
Uploaded by jle at 2015-06-18T06:14:08Z
Distributions NixOS:0.3.0.5
Reverse Dependencies 1 direct, 0 indirect [details]
Downloads 3518 total (22 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2015-06-18 [all 1 reports]

Readme for ghcjs-websockets-0.3.0.4

[back to package description]

ghcjs-websockets

Join the chat at https://gitter.im/mstksg/ghcjs-websockets

ghcjs-websockets aims to provide a clean, idiomatic, efficient, low-level, out-of-your-way, bare bones, concurrency-aware interface with minimal abstractions over the Javascript Websockets API, inspired by common Haskell idioms found in libraries like io-streams and the server-side websockets library, targeting compilation to Javascript with ghcjs.

The interface abstracts websockets as simple IO/file handles, with additional access to the natively "typed" (text vs binary) nature of the Javascript Websockets API. There are also convenience functions to directly decode serialized data (serialized with binary) sent through channels.

The library is mostly intended to be a low-level FFI library, with the hopes that other, more advanced libraries maybe build on the low-level FFI bindings in order to provide more advanced and powerful abstractions. Most design decisions were made with the intent of keeping things as simple as possible in order for future libraries to abstract over it.

Most of the necessary functionality is in hopefully in JavaScript.WebSockets; more of the low-level API is exposed in JavaScript.WebSockets.Internal if you need it for library construction.

Documenation is online on github pages.

Usage

import Data.Text (unpack)

-- A simple echo client, echoing all incoming text data
main :: IO ()
main = withUrl "ws://my-server.com" $ \conn ->
    forever $ do
        t <- receiveText conn
        putStrLn (unpack t)
        sendText conn t

The above code will attempt to interpret all incoming data as UTF8-encoded Text, and throw away data that does not.

conn is a Connection, which encapsulates a websocket channel.

You can also do the same thing to interpret all incoming data as any instance of Binary --- say, Ints:

-- A simple client waiting for connections and outputting the running sum
main :: IO ()
main = withUrl "ws://my-server.com" (runningSum 0)

runningSum :: Int -> Connection -> IO ()
runningSum n conn = do
    i <- receiveData conn
    print (n + i)
    runningSum (n + i) conn

receiveData will block until the Connection receives data that is decodable as whatever type you expect, and will throw away all nondecodable data (including Text data).

The receive function is provided as an over-indulgent layer of abstraction where you can receive both Text and instances of Binary with the same function using typeclass magic --- for the examples above, you could use receive in place of both receiveText and receiveData.

send works the same way for sendText and sendData.

If you want to, you can access the incoming data directly using the SocketMsg sum type, which exposes either a Text or a lazy ByteString:

import Data.Text (unpack, append)
import qualified Data.ByteString.Base64.Lazy as B64

main :: IO ()
main = withUrl "ws://my-server.com" $ \conn ->
    forever $ do
        msg <- receiveMessage
        putStrLn $ case msg of
            SocketMsgText t ->
                unpack $ append "Received text: " t
            SocketMsgData d ->
                "Received data: " ++ show (B64.encode d)

You can talk to multiple connections by nesting withUrl:

-- Act as a relay between two servers
main :: IO ()
main =  withUrl "ws://server-1.com" $ \conn1 ->
        withUrl "ws://server-2.com" $ \conn2 ->
            forever $ do
                msg <- receiveMessage conn1
                sendMessage conn2 msg

And also alternatively, you can manually open and close connections:

-- Act as a relay between two servers
main :: IO ()
main = do
    conn1 <- openConnection "ws://server-1.com"
    conn2 <- openConnection "ws://server-2.com"
    forever $ do
        msg <- receiveMessage conn1
        sendMessage conn2 msg
    closeConnection conn2
    closeConnection conn1

receiveMessage and its varieties will all throw an exception if the connection closes while they're waiting or if you attempt to receive on a closed connection. You can handle these with mechanisms from Control.Exception, or you can use their "maybe"-family counterparts, receiveMessageMaybe, etc., who will return results in Just on a success, or return a Nothing if the connection is closed or if receiving on a closed connection.

You can use also connectionClosed :: Connection -> IO Bool to check if the given Connection object is closed (or connectionCloseReason to see why).

When closing connections, there might be some messages that were received by the socket but never processed on the Haskell side with a receive method. These will normally be deleted; however, you can use closeConnectionLeftovers or withUrlLeftovers to grab a list of the raw SocketMsgs remaining after closing.

Issues and Roadmap

As of now, there are still some exceptions that might be thrown by the Javascript websockets API that are not explicitly handled by the library. For the most part, things are usable and asynchronous exceptions (in Haskell) should all be handled well at this point. There are also some small aspects of the websockets API that aren't yet accessible through the library.

Copyright (c) Justin Le 2015