| Copyright | (c) Justin Le 2015 |
|---|---|
| License | MIT |
| Maintainer | justin@jle.im |
| Stability | unstable |
| Portability | ghcjs |
| Safe Haskell | None |
| Language | Haskell2010 |
JavaScript.WebSockets
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.
- data Connection
- class WSSendable s
- class WSReceivable s
- data SocketMsg
- data ConnClosing
- = ManualClose
- | JSClose (Maybe Bool) (Maybe Int) (Maybe Text)
- | OpenInterruptedClose
- | UnexpectedClose Text
- data ConnectionException = ConnectionClosed {}
- withUrl :: Text -> (Connection -> IO a) -> IO a
- withUrlLeftovers :: Text -> (Connection -> IO a) -> IO (a, [SocketMsg])
- openConnection :: Text -> IO Connection
- closeConnection :: Connection -> IO ()
- closeConnectionLeftovers :: Connection -> IO [SocketMsg]
- clearConnectionQueue :: Connection -> IO ()
- dumpConnectionQueue :: Connection -> IO [SocketMsg]
- connectionClosed :: Connection -> IO Bool
- connectionCloseReason :: Connection -> IO (Maybe ConnClosing)
- connectionOrigin :: Connection -> Text
- sendData :: Binary a => Connection -> a -> IO Bool
- sendData_ :: Binary a => Connection -> a -> IO ()
- sendText :: Connection -> Text -> IO Bool
- sendText_ :: Connection -> Text -> IO ()
- sendMessage :: Connection -> SocketMsg -> IO Bool
- sendMessage_ :: Connection -> SocketMsg -> IO ()
- send :: WSSendable a => Connection -> a -> IO Bool
- send_ :: WSSendable a => Connection -> a -> IO ()
- receive :: WSReceivable a => Connection -> IO a
- receiveMaybe :: WSReceivable a => Connection -> IO (Maybe a)
- receiveText :: Connection -> IO Text
- receiveTextMaybe :: Connection -> IO (Maybe Text)
- receiveData :: Binary a => Connection -> IO a
- receiveDataMaybe :: Binary a => Connection -> IO (Maybe a)
- receiveMessage :: Connection -> IO SocketMsg
- receiveMessageMaybe :: Connection -> IO (Maybe SocketMsg)
- receiveEither :: WSReceivable a => Connection -> IO (Either SocketMsg a)
- receiveEitherMaybe :: WSReceivable a => Connection -> IO (Maybe (Either SocketMsg a))
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 tThe 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) connreceiveData 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 msgAnd 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 conn1receiveMessage 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 to
check if the given connectionClosed :: Connection -> IO BoolConnection 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 Source
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 Source
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
Instances
| Binary a => WSSendable a | |
| WSSendable Text |
class WSReceivable s Source
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
Instances
| Binary a => WSReceivable a | |
| WSReceivable Text |
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.
Constructors
| SocketMsgText Text | |
| SocketMsgData ByteString |
data ConnClosing Source
Data type containing information on Connection closes.
ManualClose: Closed by the HaskellWebSocketsinterface, usingcloseConnectionor 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
Textwith 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 aTextfield offering a reason.
Constructors
| ManualClose | |
| JSClose (Maybe Bool) (Maybe Int) (Maybe Text) | |
| OpenInterruptedClose | |
| UnexpectedClose Text |
Instances
data ConnectionException Source
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 | |
Opening, closing, and working with connections
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.
Arguments
| :: Text | Websocket address to connect to |
| -> (Connection -> IO a) | Process to run on connection |
| -> IO (a, [SocketMsg]) | Result of process, with leftovers |
openConnection :: Text -> IO Connection Source
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 () Source
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] Source
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 () Source
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] Source
Returns all incoming messages received by the socket and queued for
retrieval using receive functions. Empties the queue.
connectionClosed :: Connection -> IO Bool Source
Check if the given Connection is closed. Returns a Bool. To
check *why* it was closed, see connectionCloseReason.
connectionCloseReason :: Connection -> IO (Maybe ConnClosing) Source
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 Source
Returns the origin url of the given Connection.
Sending data
sendData_ :: Binary a => Connection -> a -> IO () Source
sendText_ :: Connection -> Text -> IO () Source
sendMessage :: Connection -> SocketMsg -> IO Bool Source
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 () Source
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 Source
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 () Source
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 Source
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) Source
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 Source
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) Source
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 Source
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) Source
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 Source
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) Source
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) Source
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)) Source
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.