ghcjs-websockets-0.2.0.0: GHCJS interface for the Javascript Websocket API

Copyright(c) Justin Le 2015
LicenseMIT
Maintainerjustin@jle.im
Stabilityunstable
Portabilityghcjs
Safe HaskellNone
LanguageHaskell2010

JavaScript.WebSockets

Contents

Description

Contains functions and operations for working with Javascript Websocket connections, which are encapsulated in the Connection object.

It includes operations for opening, closing, inspecting connections and operations for sending and receiving text and serializable data (instances of Binary) through them.

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.

Synopsis

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 a 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.

data Connection

Encapsulates a (reference to a) Javascript Websocket connection. Can be created/accessed with either openConnection or (preferably) withUrl.

Care must be taken to close the connection once you are done if using openConnection, or unprocessed messages and callbacks will continue to queue up.

class WSSendable s

A typeclass offering a gratuitous abstraction over what can be sent through a Connection. Allows you to wrap things in a SocketMsg automatically. The only instances that should really ever exist are Text and instances of Binary.

Minimal complete definition

wrapSendable

Instances

class WSReceivable s

A typeclass offering a gratuitous abstraction over what can be received through a Connection. Allows you to unwrap things in a SocketMsg automatically. The only instances that should really ever exist are Text and instances of Binary.

Minimal complete definition

unwrapReceivable

data SocketMsg

Sum type over the data that can be sent or received through a JavaScript websocket.

What an incoming message is classified as depends on the Javascript Websockets API http://www.w3.org/TR/websockets/, which provides a "typed" input channel of either text or binary blob.

There are several convenience functions to help you never have to deal with this explicitly; its main purpose is if you want to explicitly branch on a receiveMessage depending on what kind of message you receive and do separate things. receiveText and receiveData will both allow you to "filter" incoming messages by their type.

data ConnClosing

Data type containing information on Connection closes.

  • ManualClose: Closed by the Haskell WebSockets interface, using closeConnection or variants.
  • JSClose: Closed on the Javascript end, either by a connection error or server request, or what have you. Contains information from the Javascript Websockets API http://www.w3.org/TR/websockets/#event-definitions.

    The first field is whether or not it was a clean close; the second field is the closing reason code; the third field is a Text with the reason given by the Websockets API.

  • OpenInterupptedClose: There was an unexpected error encountered when attempting to open the connection.
  • UnexpectedClose: Otherwise uncategorized closed status, with a Text field offering a reason.

data ConnectionException

An exception that may be thrown when using the various Connection operations. Right now, only includes ConnectionClosed, which is thrown when using an "unsafe" receive on a closed Connection, or if a Connection closes while an unsafe receive is waiting.

Constructors

ConnectionClosed 

Fields

_socketOrigin :: Text
 

Opening, closing, and working with connections

withUrl

Arguments

:: Text

Websocket address to connect to

-> (Connection -> IO a)

Process to run on connection

-> IO a 

Performs the given Connection -> IO a process attached to the given server url. Handles opening and closing the Connection for you (and clearing the message queue afterwards), and cleans up on errors.

If any messages were received by the socket but never processed/received on the Haskell end, this will delete and drop them. Use withUrlLeftovers to get a hold of them.

withUrlLeftovers

Arguments

:: Text

Websocket address to connect to

-> (Connection -> IO a)

Process to run on connection

-> IO (a, [SocketMsg])

Result of process, with leftovers

Like withUrl, except returns also the "leftover messages" that were received by the socket but never processed on the Haskell end with receive.

openConnection :: Text -> IO Connection

Opens a websocket connection to the given url, and returns the Connection after connection is completed and opened. Care should be taken to ensure that the Connection is later closed with closeConnection.

Consider using withUrl, which handles closing with bracketing and error handling so you don't have to worry about closing the connection yourself.

Blocks until the connection has been established and opened.

If an async exception happens while this is waiting, the socket will be closed as the exception bubbles up.

closeConnection :: Connection -> IO ()

Manually closes the given Connection. Will un-block all threads currently waiting on the Connection for messages (releasing their callbacks) and disable sending and receiving in the future.

All leftover messages that were never processed on the Haskell end will be deleted; use dumpConnectionQueue to manually fetch them before closing, or closeConnectionLeftovers to recover them while closing.

closeConnectionLeftovers :: Connection -> IO [SocketMsg]

Manually closes the given Connection. It un-blocks all threads currently waiting on the connection and disables all sending and receiving in the future.

The result is a list of all messages received by the connection but not yet retrieved by receive, etc. on the Haskell end.

To close and ignore leftovers, use closeConnection.

clearConnectionQueue :: Connection -> IO ()

Clears the message queue (messages waiting to be received) on the given Connection. Is essentially a no-op on closed connections.

dumpConnectionQueue :: Connection -> IO [SocketMsg]

Returns all incoming messages received by the socket and queued for retrieval using receive functions. Empties the queue.

connectionClosed :: Connection -> IO Bool

Check if the given Connection is closed. Returns a Bool. To check *why* it was closed, see connectionCloseReason.

connectionCloseReason :: Connection -> IO (Maybe ConnClosing)

Returns Nothing if the given Connection is still open, or Just closing containing a ConnClosing with information on why the connection was closed.

For just a Bool saying whether or not the connection is closed, try connectionClosed.

connectionOrigin :: Connection -> Text

Returns the origin url of the given Connection.

Sending data

sendData :: Binary a => Connection -> a -> IO Bool

Send the given serializable (instance of Binary) data on the given connection.

Returns True if the connection is open, and False if it is closed. In the future will return more feedback about whether or not the send was completed succesfully.

sendData_ :: Binary a => Connection -> a -> IO ()

Send the given serializable (instance of Binary) data on the given connection.

Fails silently if the connection is closed or otherwise was not succesful. Use sendData to get feedback on the result of the send.

sendText :: Connection -> Text -> IO Bool

Send the given (strict) Text on the given connection.

Returns True if the connection is open, and False if it is closed. In the future will return more feedback about whether or not the send was completed succesfully.

sendText_ :: Connection -> Text -> IO ()

Send the given (strict) Text on the given connection.

Fails silently if the connection is closed or otherwise was not succesful. Use sendText to get feedback on the result of the send.

sendMessage :: Connection -> SocketMsg -> IO Bool

Sends the given SocketMsg through the given Connection. A SocketMsg is a sum type of either 'SocketMsgText t', containing (strict) Text, or 'SocketMsgData d', containing a (lazy) ByteString.

Returns True if the connection is open, and False if it is closed. In the future will return more feedback about whether or not the send was completed succesfully.

sendMessage_ :: Connection -> SocketMsg -> IO ()

Sends the given SocketMsg through the given Connection. A SocketMsg is a sum type of either 'SocketMsgText t', containing (strict) Text, or 'SocketMsgData d', containing a (lazy) ByteString.

Fails silently if the connection is closed or otherwise was not succesful. Use sendMessage to get feedback on the result of the send.

send :: WSSendable a => Connection -> a -> IO Bool

Send the given item through the given Connection.

You can send either (strict) Text or any instance of Binary, due to over-indulgent typeclass magic; this is basically a function that works everywhere you would use sendText or sendData.

Returns True if the connection is open, and False if it is closed. In the future will return more feedback about whether or not the send was completed succesfully.

send_ :: WSSendable a => Connection -> a -> IO ()

Send the given item through the given Connection.

You can send_ either (strict) Text or any instance of Binary, due to over-indulgent typeclass magic; this is basically a function that works everywhere you would use sendText_ or sendData_.

Fails silently if the connection is closed or otherwise was not succesful. Use send to get feedback on the result of the send.

Receiving data

receive :: WSReceivable a => Connection -> IO a

Block and wait until either something decodable as the desired type is received (returning it), or the Connection closes (throwing a ConnectionException). Throws the exception immediately if the Connection is already closed and there are no queued messages left.

This is polymorphic on its return type, so remember to let the type inference system know what you want at some point or just give an explicit type signature --- receiveData conn :: IO (Maybe Int), for example.

All non-decodable or non-matching data that comes along is discarded.

You can receive either (strict) Text or any instance of Binary, due to over-indulgent typeclass magic; this is basically a function that works everywhere you would use receiveText or receiveData.

To handle closed sockets with Maybe, use receiveMaybe.

receiveMaybe :: WSReceivable a => Connection -> IO (Maybe a)

Block and wait until either something decodable as the desired type is received (returning Just x), or the Connection closes (returning Nothing). Returns Nothing immediately if the Connection is already closed and there are no queued messages left.

This is polymorphic on its return type, so remember to let the type inference system know what you want at some point or just give an explicit type signature --- receiveData conn :: IO (Maybe Int), for example.

All non-decodable or non-matching data that comes along is discarded.

You can receive either (strict) Text or any instance of Binary, due to over-indulgent typeclass magic; this is basically a function that works everywhere you would use receiveText or receiveData.

receiveText :: Connection -> IO Text

Block and wait until the Connection receives a "typed" Text. This is determined by Javascript's own "typed" Websockets API http://www.w3.org/TR/websockets/, which receives data typed either as text or as a binary blob. Returns the first encountered text. Throws a ConnectionException if the Connection closes first, and throws one immediately if the connection is already closed and there are no queued messages left.

All "binary blobs" encountered are discarded.

To handle closed sockets with Maybe, use receiveTextMaybe.

receiveTextMaybe :: Connection -> IO (Maybe Text)

Block and wait until the Connection receives a "typed" Text. This is determined by Javascript's own "typed" Websockets API http://www.w3.org/TR/websockets/, which receives data typed either as text or as a binary blob. Returns Just t on the first encountered text. Returns Nothing if the Connection closes while it is waiting, or immediately if the connection is already closed and there are no queued messages left.

All "binary blobs" encountered are discarded.

receiveData :: Binary a => Connection -> IO a

Block and wait until the Connection receives a "binary blob" decodable as the desired instance of Binary. Returns the first succesfully decoded data, and throws a ConnectionException if the Connection closes first. Throws the exception immediately if the Connection is already closed and there are no queued messages left.

This is polymorphic on its return type, so remember to let the type inference system know what you want at some point or just give an explicit type signature --- receiveData conn :: IO (Maybe Int), for example.

All incoming messages received that cannot be decoded as the data type (or are text) will be discarded.

To handle closed sockets with Maybe, use receiveDataMaybe.

receiveDataMaybe :: Binary a => Connection -> IO (Maybe a)

Block and wait until the Connection receives a "binary blob" decodable as the desired instance of Binary. Returns Just x as soon as it is able to decode a blob, and Nothing if the Connection closes while it is waiting. Returns Nothing immediately if the Connection is already closed and there are no queued messages left.

All incoming messages received that cannot be decoded as the data type (or are text) will be discarded.

This is polymorphic on its return type, so remember to let the type inference system know what you want at some point or just give an explicit type signature --- receiveData conn :: IO (Maybe Int), for example.

receiveMessage :: Connection -> IO SocketMsg

Block and wait until the Connection receives any message, and returns the message wrapped in a SocketMsg. A SocketMsg is a sum type of either 'SocketMsgText t', containing (strict) Text, or 'SocketMsgData d', containing a (lazy) ByteString.

Will return the message as soon as any is received, or throw a ConnectionException if the connection is closed while waiting. Throws an exception immediately if the connection is already closed.

To handle closed sockets with Maybe, use receiveMessageMaybe.

receiveMessageMaybe :: Connection -> IO (Maybe SocketMsg)

Block and wait until the Connection receives any message, and returns the message wrapped in a SocketMsg. A SocketMsg is a sum type of either 'SocketMsgText t', containing (strict) Text, or 'SocketMsgData d', containing a (lazy) ByteString.

Will return 'Just msg' as soon as any message is received, or Nothing if the Connection closes first. Returns Nothing immediately if the Connection is already closed.

receiveEither :: WSReceivable a => Connection -> IO (Either SocketMsg a)

Block and wait until the Connection receives any message, and attempts to decode it depending on the desired type. If Text is requested, assumes Utf8-encoded text or just a plain Javascript string. If an instance of Binary is requested, attempts to decode it into that instance. Successful parses return 'Right x', and failed parses return 'Left SocketMsg' (A sum type between SocketMsgText containing (strict) Text and SocketMsgData containing a (lazy) ByteString). Nothing is ever discarded.

Will return the message as soon as any is received, or throw a ConnectionException if the connection is closed while waiting. Throws an exception immediately if the connection is already closed and there are no queued messages left.

To handle closed sockets with Maybe, use receiveEitherMaybe.

receiveEitherMaybe :: WSReceivable a => Connection -> IO (Maybe (Either SocketMsg a))

Block and wait until the Connection receives any message, and attempts to decode it depending on the desired type. If Text is requested, assumes Utf8-encoded text or just a plain Javascript string. If an instance of Binary is requested, attempts to decode it into that instance. Successful parses return 'Right x', and failed parses return 'Left SocketMsg' (A sum type between SocketMsgText containing (strict) Text and SocketMsgData containing a (lazy) ByteString). Nothing is ever discarded.

Returns Just result on the first message received, or Nothing if the Connection closes while waiting. Returns Nothing if the connection is already closed and there are no queued messages left.