WebSocket
A WebSocket (RFC 6455) is a full-duplex connection that carries messages rather than bytes and is opened by an HTTP request, and this module opens WebSockets as a client and answers them as a server.
A client connects to a ws:// or wss:// URL. A server receives a client's
opening request as an Upgrade, either on a Socket.Connection
it accepted (receiveUpgrade) or from an HTTP server
(Http.Server.upgradeRequest), and answers it with
accept or reject. Either way an open connection is a
WebSocket.
WebSocket.connect (WebSocket.defaultConnectOptions "wss://example.com/chat")
|> Task.andThen
(\ws ->
Stream.write (WebSocket.Text "hello") (WebSocket.writable ws)
|> Task.mapError (\_ -> ...)
)Several modules define functions such as close, accept and readable, so import this module
qualified:
import WebSocketMessages. A message is text or binary data (Message). A connection is opened in
one of two modes, and its type says which. In the whole mode (connect,
accept) every message arrives in one piece, on readable or through
onMessage, and a message larger than the maxMessageSize of its options fails
the connection. In the streamed mode (connectStreamed,
acceptStreamed) every message arrives as a stream of its own
(StreamedMessage), whatever its size. A streamed message must be read to
its end (Closed) or cancelled, because the connection delivers nothing after it until then. In
both modes each value written to writable is one message, and
sendText and sendBinary send one message whose content is read
from a stream. While 1 MiB of received messages, or 1024 of them, wait unread, the connection
stops reading from the network.
Ending. A connection normally ends with a closing handshake: one end sends a Close frame
carrying a CloseCode and a reason, the other answers with its own, and the
connection is then gone. This end can also fail a connection, for example on a protocol error,
an invalid or oversized message, or a silent peer: it sends a Close frame with a code saying why
and closes the connection without waiting for an answer. And the network connection can simply
be lost. awaitClose and onClose report how a connection ended, as
a CloseInfo.
The tasks of this module that can fail fail with this module's Error, which holds
the failure of whichever layer failed: the connection underneath, TLS for wss://, or the
WebSocket itself. Once a connection is open, a failure of the connection arrives on its streams
instead, as the Stream module describes: a read of readable fails with
Stream.Failed, and fromStreamFailure reads that failure back as an
Error, such as ProtocolViolation when the peer broke the protocol.
Liveness. By default this end checks that the peer is still there: when nothing has arrived
for 30 seconds it sends a ping, and when still nothing at all has arrived 30 seconds later it
fails the connection (Heartbeat). Pings from the peer are answered until this
end starts closing.
Compression. permessage-deflate (RFC 7692) compresses the content of each message. By
default a client offers it and a server accepts the offer, both without context takeover: each
message is compressed on its own, without referring back to earlier ones. With
contextTakeover = True in the compression options of both sides a message may refer back into
earlier messages, which compresses a run of similar messages better. A message written to
writable that is shorter than the compression options' threshold bytes is sent
uncompressed, while a non-empty message sent with
sendText or sendBinary is compressed whatever its size.
compression tells what the handshake agreed. Do not send secrets and data an
attacker controls on one compressed connection: the compressed sizes can reveal the secrets (the
CRIME and BREACH attacks).
A connection keeps the program running while an onMessage subscription exists
and the connection is open, while an onClose subscription waits for its
CloseInfo, while an awaitClose waits, during a closing handshake this end
started, and while an operation on it, such as a read, a write or a ping, is waiting.
Client limits. A client opens a new connection for every connect, even to a
host it is already connecting to, although RFC 6455 asks a client to wait for one opening
handshake with a host to finish before it starts another. Redirects are not followed (a redirect
status fails the handshake), and no proxy is used.
On native Windows, WebSockets are not supported: connecting, receiving an opening request and
accepting one fail with SocketError of kind
Socket.NotSupported.
Connections
type alias WebSocket mode =
WebSocket modetype Wholetype StreamedThe mode of a connection whose messages arrive as streams, each a
StreamedMessage. It has no values; it is only used as the mode of a
WebSocket.
type Message
= Text String
| Binary BytesA message: Text carries text and Binary carries bytes. Both kinds can be sent and
received on one connection, in any order.
A received Text has been checked to be valid UTF-8: a text message that is not fails the
connection with InvalidData. On the native backend a lone surrogate in a sent Text goes out
as bytes that are not valid UTF-8; the JavaScript backend sends U+FFFD in its place.
type StreamedMessage
= StreamedText (Readable String)
| StreamedBinary (Readable Bytes)A message received in the streamed mode, whose content arrives as a stream, chunk by chunk, as the peer sends it, decompressed when the message was compressed.
StreamedText carries a stream of text whose chunks always end on a whole character; text that
is not valid UTF-8 fails the connection with InvalidData. StreamedBinary carries a stream of
bytes.
The next message is delivered only once this one's stream has been read to Closed, or
cancelled with Stream.cancelReadable, which drops the rest of the message. When the connection
ends or fails before the message is complete, what had arrived can still be read, and then the
stream fails with Stream.Failed, which fromStreamFailure reads as the
Error that ended the connection.
readable : WebSocket Whole -> Readable MessageReturns the stream of the messages the peer sends, in order.
Once the peer's Close frame has arrived, reads fail with Closed after the messages received
before it. When the connection fails or is lost instead, reads fail with Stream.Failed, again
after the messages received before it. fromStreamFailure reads the
failure as an Error: ProtocolViolation when the peer broke the protocol,
MessageTooLarge for a message over maxMessageSize, HeartbeatTimedOut
when the heartbeat found the peer silent, and SocketError when the connection
underneath failed or was closed. awaitClose gives the close code and reason.
While the connection has an onMessage subscription, messages go to the
subscription instead, and a read fails with Locked.
readableStreamed : WebSocket Streamed -> Readable StreamedMessageReturns the stream of the messages of a streamed connection, each a
StreamedMessage. It ends and fails as readable does.
writable : WebSocket mode -> Writable MessageReturns the stream that sends messages: each value written is one message, sent after the messages written or streamed before it.
Closing it with Stream.closeWritable starts the closing handshake with Normal and no reason,
as close does. Cancelling it with Stream.cancelWritable fails the connection with
InternalError. Once the connection is closing or has ended, writes fail with Stream.Closed.
sendText : Readable String -> WebSocket mode -> Task Error ()Sends one text message whose content is read from source as it arrives, so that a message
too large to hold in memory can be sent. The task succeeds once source has ended and the
message's last frame has been written.
The message takes its place among the outgoing messages when the task starts: messages written
to writable, or sent with sendText or sendBinary, after that
go out after it, while pings and pongs are not held up. Each chunk of source is sent as one or
more pieces of the message, compressed when the connection uses compression. An empty source
sends an empty message.
If source fails after part of the message has gone out, the message cannot be completed and
the connection fails with InternalError; if nothing of it has gone out yet, nothing is sent.
Either way the task fails with SocketError of kind
Socket.Cancelled. It fails with SocketError of kind
Socket.Busy when source is locked, and of kind Socket.Cancelled when
the connection is closing or has ended, or ends before the message is complete.
sendBinary : Readable Bytes -> WebSocket mode -> Task Error ()Sends one binary message whose content is read from source as it arrives, exactly as
sendText does for text.
protocol : WebSocket mode -> Maybe StringReturns the subprotocol the handshake agreed on (Sec-WebSocket-Protocol), or Nothing
when it agreed none.
compression : WebSocket mode -> Maybe NegotiatedReturns the permessage-deflate settings the handshake agreed on, or Nothing when it agreed
none.
A Just does not mean that every message is compressed: a message written to
writable that is shorter than the threshold is still sent uncompressed, and what
the peer compresses is up to the peer.
type alias Negotiated =
{ serverNoContextTakeover : Bool
, clientNoContextTakeover : Bool
, serverMaxWindowBits : Int
, clientMaxWindowBits : Int
}The permessage-deflate settings the two ends of a connection have agreed on.
serverNoContextTakeover and clientNoContextTakeover are True when that end, the server or
the client, compresses every message on its own. serverMaxWindowBits and clientMaxWindowBits
are the largest window that end compresses with, in window bits (as
ClientCompression explains them); in the settings
compression returns, they are from 8 to 15.
localEndpoint : WebSocket mode -> EndpointReturns this end of the connection.
remoteEndpoint : WebSocket mode -> EndpointReturns the peer's end of the connection.
Events and closing
onMessage : WebSocket Whole -> (Message -> msg) -> Sub msgCreates a subscription to the messages of a connection.
While a connection has subscriptions, every message goes to all of them, in subscription order,
and reads of readable fail with Locked. Messages that arrive while it has none
wait on the readable. A subscription does not start while a Stream.read of the readable is
waiting.
While the connection is open, an onMessage subscription keeps the program running.
onClose : WebSocket mode -> (CloseInfo -> msg) -> Sub msgCreates a subscription to the end of a connection. Its CloseInfo is
delivered once, to every onClose subscription the connection has at that moment. A
subscription made after it was delivered receives nothing.
Until its CloseInfo has been delivered, an onClose subscription keeps the program running.
ping : WebSocket mode -> Task Error IntSends a ping and waits for its pong, giving the round trip time in milliseconds.
Fails with SocketError of kind Socket.Cancelled when the
connection is closing or has ended, or ends before the pong arrives, and of kind
Socket.TimedOut when no pong arrives within the heartbeat's timeout (30
seconds when heartbeat is Nothing or its timeout is 0 or less). While the connection has
stopped reading because messages wait unread, that wait is suspended, and it starts again when
reading resumes. A pong also answers every earlier ping still waiting.
close : CloseCode -> String -> WebSocket mode -> Task Error ()Starts the closing handshake with code and reason: the messages already written or being
streamed are sent first, then a Close frame. On the JavaScript backend the reason is cut to its
longest start that is at most 123 bytes of UTF-8; on the native backend a character outside the
Basic Multilingual Plane is counted as 6 bytes, so the reason can be cut shorter, and the cut
can fall inside such a character. The task succeeds once the Close frame has been written. The
connection ends when the peer's Close frame arrives, or 30 seconds after this end started
closing if it does not, which is reported as Abnormal.
From then on, messages that arrive are dropped (a streamed message being received fails with
Stream.Failed), and writes to writable fail with Stream.Closed.
Fails with SocketError of kind Socket.InvalidInput for a code
that cannot be sent: only 1000 to 1003, 1007 to 1014 and 3000 to 4999 can, so NoStatus,
Abnormal, and Other with a code outside those ranges cannot.
Closing a connection that is closing or has ended, with a code that can be sent, succeeds and
sends nothing more.
awaitClose : WebSocket mode -> Task x CloseInfoWaits until the connection has ended and returns how it ended. Once it has ended, it returns
at once. On the native backend, a connection that ended before more than 4096 other connections
ended may be reported as Abnormal, with an empty reason and clean False, whatever its real
end.
A waiting awaitClose keeps the program running.
type CloseCode
= Normal
| GoingAway
| ProtocolError
| UnsupportedData
| NoStatus
| Abnormal
| InvalidData
| PolicyViolation
| MessageTooBig
| MandatoryExtension
| InternalError
| Other IntThe code a Close frame carries to say why a connection is closing, from the codes RFC 6455 defines.
Normal, GoingAway, ProtocolError and UnsupportedData are the codes 1000 to 1003, and
InvalidData, PolicyViolation, MessageTooBig, MandatoryExtension and InternalError the
codes 1007 to 1011.
NoStatus (1005) and Abnormal (1006) are never sent; they describe how a connection ended.
NoStatus is reported when the peer's Close frame carried no code. Abnormal is reported when,
for example, the network connection was lost, the peer did not answer a Close frame in time, or
the heartbeat found the peer silent.
Other carries any other code, such as an application's own code from 4000 to 4999. A received
code that has a constructor of its own is always given that constructor, never Other.
type alias CloseInfo =
{ code : CloseCode, reason : String, clean : Bool }How a connection ended.
When the peer's Close frame arrived, code and reason are the ones it carried and clean is
True, whichever end started closing. When this end failed the connection, they are the code
and reason of the failure, and clean is False; a heartbeat that found the peer silent
is the exception, reported as Abnormal with an empty reason. When the connection ended in any
other way, code is Abnormal, reason is empty and clean is False.
type alias Heartbeat =
{ interval : Int, timeout : Int }The liveness check of a connection, both times in milliseconds.
When nothing has arrived from the peer for interval, a ping is sent. If nothing at all
arrives within a further timeout, the connection is failed: a Close frame with GoingAway is
sent, the connection is closed, its end is reported as Abnormal, and reads fail with
Stream.Failed, which fromStreamFailure reads as
HeartbeatTimedOut. Anything received counts, not only the pong. While the connection has
stopped reading because messages wait unread, the check is suspended.
An interval of 0 or less turns the check off, as heartbeat = Nothing does, while a timeout
of 0 or less means 30 seconds. Both are plain Ints, so the compiler accepts any value.
Clients
type alias ConnectOptions =
{ url : String
, headers : List ( String, String )
, protocols : List String
, verification : Verification
, timeout : Maybe Int
, maxMessageSize : Int
, heartbeat : Maybe Heartbeat
, compression : Maybe ClientCompression
, http2 : Bool
}How a client connects.
url:ws://host[:port]/path?query, orwss://...for a connection over TLS. An IPv6 address is written in brackets, and a zone in it may be written%25, as inws://[fe80::1%25eth0]/. A fragment (#) or user information makes the URL invalid.headers: extra request headers, such asAuthorization. A name must be an HTTP token and a value may not hold a carriage return, line feed or NUL.Host,Upgrade,Connection,Sec-WebSocket-Key,Sec-WebSocket-Version,Sec-WebSocket-Accept,Sec-WebSocket-Protocol,Sec-WebSocket-Extensions,Content-LengthandTransfer-Encodingare refused, in any case.protocols: the subprotocols to offer, in order; each must be an HTTP token and be listed once.protocoltells which one the server chose.verification: which server certificates awssconnection accepts.timeout: the time allowed, in milliseconds, for connecting, TLS and the HTTP exchange together; the name lookup before them is not limited.Nothing, or a value of 0 or less, is no limit.maxMessageSize: the largest message accepted, in bytes after decompression; a larger one fails the connection withMessageTooBig. A value of 0 or less accepts only empty messages. It does not apply to a streamed connection.heartbeat: theHeartbeat, orNothingfor none.compression: the permessage-deflate offer, orNothingto offer none.http2: for awssURL, try a WebSocket over HTTP/2 (RFC 8441) first; awsURL always uses HTTP/1.1. The TLS handshake then offersh2. When the server chooses it and its settings allow WebSockets (SETTINGS_ENABLE_CONNECT_PROTOCOL), the WebSocket runs on one stream of a new HTTP/2 connection. A server that chooses HTTP/1.1 gets the HTTP/1.1 handshake on the same connection, and an HTTP/2 server whose settings do not allow WebSockets is left and dialed again over HTTP/1.1, within the sametimeout.
type alias ClientCompression =
{ clientMaxWindowBits : Maybe (Maybe Int)
, serverMaxWindowBits : Maybe Int
, contextTakeover : Bool
, threshold : Int
}What a client offers for permessage-deflate.
Window bits is the size of a compressor's window, the distance back it may refer, as a power of two: from 8 (256 bytes) to 15 (32 KiB). A window size outside 8 to 15 is clamped into that range.
clientMaxWindowBits:Just (Just n)limits the client's own window tonwindow bits.Just Nothingsends the parameter without a value, which lets the server choose a limit for the client's window.Nothingleaves the parameter out, and a server that answers with a limit for the client's window then fails the handshake.serverMaxWindowBits:Just nasks the server to compress with at mostnwindow bits; a server that answers with more fails the handshake.contextTakeover:Falseoffersclient_no_context_takeoverandserver_no_context_takeover, and the client then compresses every message on its own.Trueoffers neither, and the server may still answer with them.threshold: a message written towritablethat is shorter than this many bytes is sent uncompressed.
defaultConnectOptions : String -> ConnectOptionsReturns the options for connecting to url: no extra headers or subprotocols,
Socket.Tls.SystemCertificates, a 30 second timeout, messages up to 16 MiB, the default
heartbeat (a ping after 30 seconds without anything received, then 30 seconds for anything to
arrive), permessage-deflate offered without context takeover, with client_max_window_bits
sent without a value and a threshold of 64 bytes, and http2 = False.
connect : ConnectOptions -> Task Error (WebSocket Whole)Opens a connection in the whole mode to options.url.
A host name is looked up first; an address written in the URL is used as it is. The addresses are tried in order until a connection is made, and the opening handshake then runs on that connection. When no connection can be made, the error of the last address tried is reported.
Fails with:
SocketErrorof kindSocket.InvalidInputfor an invalid URL, header or subprotocol;SocketErrorwith the errors ofSocket.lookup, such asSocket.HostNotFound, and of connecting, such asSocket.ConnectionRefused;TlsErrorwhen the TLS of awss://connection fails, for exampleSocket.Tls.CertificateRejectedwhen the server's certificate is rejected;SocketErrorof kindSocket.TimedOutwhen thetimeoutruns out;HandshakeRefused statuswhen the server answers with an HTTP status other than 101 (over HTTP/2, other than 2xx), andHandshakeFailedwhen its answer is not an acceptable opening handshake for another reason.
connectStreamed : ConnectOptions -> Task Error (WebSocket Streamed)Opens a connection in the streamed mode; everything else is as for connect.
Servers
type alias Upgrade =
UpgradeA client's opening request that a server has received.
One is obtained from receiveUpgrade or Http.Server.upgradeRequest, read
with the upgrade functions below, and answered with accept,
acceptStreamed or reject.
receiveUpgrade : Connection -> Task Error UpgradeReads a client's opening request from connection, a connection a server accepted, such as
one from a Socket.Tcp.listen or Socket.Tls.listen listener.
The connection is taken over: from then on its streams fail with Stream.Failed, which
Socket.fromStreamFailure reads as kind
Socket.ConnectionTakenOver, and bytes the client sent right behind its
request are kept for the WebSocket. The request is only read as HTTP here; whether it is a valid
opening request is checked by accept.
Fails with SocketError of kind Socket.Busy while a read or
write is waiting on the connection's streams or when the connection has already been taken over,
and of kind Socket.Cancelled when the connection, or its writable, has been
closed; these leave the connection as it is. Otherwise it fails with SocketError of kind
Socket.TimedOut when the request has not arrived within 30 seconds; with
HandshakeFailed when what the client sends is not an HTTP request or has a head over
64 KiB (both answered with 400), or the client closes the connection before sending a request;
and with SocketError holding the connection's own error when reading fails. After any of these
the connection is closed.
upgradeTarget : Upgrade -> StringReturns the request target, the path and the query, for example "/chat?room=1", as the
client sent it.
upgradeHeaders : Upgrade -> List ( String, String )Returns the request headers in the order they were received, with lower-case names and one entry for each occurrence of a repeated header.
upgradeProtocols : Upgrade -> List StringReturns the subprotocols the client offers, in the order it lists them: the comma-separated
elements of every Sec-WebSocket-Protocol header, trimmed, with empty ones dropped.
upgradeOrigin : Upgrade -> Maybe StringReturns the value of the first Origin header, or Nothing when the request has none.
upgradeRemote : Upgrade -> EndpointReturns the client's end of the connection.
type alias AcceptOptions =
{ protocol : Maybe String
, headers : List ( String, String )
, maxMessageSize : Int
, heartbeat : Maybe Heartbeat
, compression : Maybe ServerCompression
}How a server accepts an opening request.
protocol: the subprotocol to use, which must be one the client offers (upgradeProtocols), orNothingfor none.headers: extra response headers, refused by the same rules as theheadersofConnectOptions.maxMessageSize,heartbeat: as inConnectOptions.compression: the server's permessage-deflate policy, orNothingto decline every offer.
type alias ServerCompression =
{ maxWindowBits : Int, contextTakeover : Bool, threshold : Int }What a server agrees to for permessage-deflate. The first of the client's offers that is valid is accepted under it.
maxWindowBits: the largest window the server compresses with, in window bits (asClientCompressionexplains them, clamped to 8 to 15), or the client's limit when that is smaller.contextTakeover:Falseanswers withserver_no_context_takeoverandclient_no_context_takeover, which ask both ends to compress every message on its own.Truekeeps context takeover for each end whose no-context-takeover parameter the client did not send.threshold: a message written towritablethat is shorter than this many bytes is sent uncompressed.
defaultAcceptOptions : AcceptOptionsThe default options for accepting: no subprotocol, no extra headers, messages up to 16 MiB,
the default heartbeat (as in defaultConnectOptions), and
permessage-deflate accepted without context takeover, with a 15-bit window and a threshold of 64
bytes.
accept : AcceptOptions -> Upgrade -> Task Error (WebSocket Whole)Accepts an opening request in the whole mode: checks it, agrees on the subprotocol and the compression, answers it (101 over HTTP/1.1, 200 over HTTP/2), and opens the connection.
The request must be a valid opening request. Over HTTP/1.1 it is a GET of HTTP/1.1 or later
with a Host header, Upgrade: websocket, Connection: Upgrade, Sec-WebSocket-Version: 13
and a valid Sec-WebSocket-Key; over HTTP/2 an extended CONNECT with
Sec-WebSocket-Version: 13. Its subprotocols must be HTTP tokens and its extensions well
formed. The checks are made in that order, and the first that fails decides the answer: 426,
with Sec-WebSocket-Version: 13, for the version, and 400 for any other. The task then fails
with HandshakeFailed.
A protocol the client did not offer, or a refused header in headers, fails with
SocketError of kind Socket.InvalidInput and leaves the request
unanswered. Accepting an Upgrade that has already been answered fails.
acceptStreamed :
AcceptOptions
-> Upgrade
-> Task Error (WebSocket Streamed)Accepts an opening request in the streamed mode; everything else is as for
accept.
reject :
Int
-> List ( String, String )
-> String
-> Upgrade
-> Task x ()Refuses an opening request with an HTTP status, extra headers and a body, for example
reject 403 [] "Forbidden", and succeeds once the answer has been written. Over HTTP/1.1 the
connection is closed afterwards; over HTTP/2 the request's stream is ended.
A status outside 100 to 999 is sent as 400, and over HTTP/2 on the JavaScript backend a
status below 200 is sent as 500. An Upgrade that has already been answered is
left as it is.
Errors
type Error
= SocketError Error
| TlsError Problem
| HandshakeRefused Int
| HandshakeFailed
| HeartbeatTimedOut
| MessageTooLarge
| ProtocolViolation StringWhy a WebSocket task failed, or why a connection's message streams failed.
A WebSocket runs on layers: a connection, TLS for a wss:// URL, and the WebSocket protocol
itself. Each layer's own failure appears here once, at the same depth, whichever URL scheme was
used:
| Failure | ws:// | wss:// |
|---|---|---|
| The connection: refused, reset, timed out, host not found | SocketError | SocketError |
| TLS: certificate rejected, handshake failed | (cannot happen) | TlsError |
| The opening handshake | HandshakeRefused, HandshakeFailed | HandshakeRefused, HandshakeFailed |
So a refused connection is SocketError with kind
Socket.ConnectionRefused for both schemes, and one pattern handles it.
SocketError: the connection underneath failed, or a task was refused at the socket level, for example with kindSocket.InvalidInputfor an invalid argument,Socket.Busyfor a locked stream orSocket.Cancelledfor a connection that is closing or has ended.TlsError: the TLS of awss://connection failed, for exampleSocket.Tls.CertificateRejected. A peer that goes away during the TLS handshake is aSocketError, since only failures made by TLS itself are aTlsError.Socket.Tls.CertificateUnusablemeans theTrustedCertificatesof the connection'sverificationhold no certificate that can be read.HandshakeRefused status: the server answered the opening request with an HTTPstatusother than 101, or other than 2xx over HTTP/2, for exampleHandshakeRefused 403.HandshakeFailed: the opening handshake failed for any other reason. A client gets it when the server's answer has missing or wrong handshake headers or a subprotocol or extension that was not offered, is not HTTP, or never comes because the server closes the connection. A server gets it fromreceiveUpgradeandacceptwhen the client's request is not HTTP or not a valid opening request, or the client closes the connection before sending one.HeartbeatTimedOut: theHeartbeatfound the peer silent and failed the connection.MessageTooLarge: the peer sent a message larger than themaxMessageSizeof the connection's options, and the connection was failed with the close codeMessageTooBig.ProtocolViolation details: the peer broke the WebSocket protocol, for example with an invalid frame, and the connection was failed;detailssays how.
The tasks of this module, and Http.Server.upgradeRequest, fail with SocketError, TlsError,
HandshakeRefused and HandshakeFailed. A connection's message streams fail with Stream.Failed,
which fromStreamFailure reads as SocketError, HeartbeatTimedOut,
MessageTooLarge or ProtocolViolation. One type covers both, so a program can handle them in
one place, at the cost that each can hold cases the other never produces.
errorToString : Error -> StringReturns a description of the error for people to read. A SocketError or a TlsError is
described as Socket.errorToString and
Socket.Tls.errorToString describe it, and the others by what
happened, such as "handshake refused with status 403".
fromStreamFailure : Failure -> Maybe ErrorReads a stream failure of a WebSocket's message streams, from readable,
readableStreamed or a StreamedMessage, as an
Error. Returns Nothing for a failure that another module's stream made.
case streamError of
Stream.Failed failure ->
case WebSocket.fromStreamFailure failure of
Just WebSocket.HeartbeatTimedOut ->
...
_ ->
...
_ ->
...The result is SocketError when the connection underneath failed or was closed,
HeartbeatTimedOut, MessageTooLarge or ProtocolViolation when this end failed the connection,
and TlsError when the TLS of a wss:// connection failed after the handshake.
An open WebSocket connection, in the mode
mode:WholeorStreamed.A value is made only by
connect,connectStreamed,acceptandacceptStreamed, and itsmodeis the mode that function opened the connection in, so a function that takes only one mode can rely on it. The value stays usable after the connection has ended.