Packages
mist
6.0.2
6.0.3
6.0.2
6.0.1
6.0.0
5.0.4
5.0.3
5.0.2
5.0.1
5.0.0
5.0.0-rc1
4.0.7
4.0.6
4.0.5
4.0.4
4.0.3
4.0.2
4.0.1
4.0.0
3.0.0
2.0.0
1.2.0
1.1.0
1.0.0
1.0.0-rc3
1.0.0-rc2
1.0.0-rc1
0.17.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.3
a misty Gleam web server
Current section
Files
Jump to
Current section
Files
src/mist/internal/websocket.gleam
import exception
import gleam/bit_array
import gleam/dynamic/decode
import gleam/erlang/atom
import gleam/erlang/process.{type Selector}
import gleam/option.{type Option, None, Some}
import gleam/otp/actor
import gleam/result
import glisten.{type Socket}
import glisten/socket/options
import glisten/transport.{type Transport}
import gramps/websocket.{type Frame, CloseFrame, Control, PingFrame}
import gramps/websocket/compression.{type Compression, type Context}
import logging
import mist/internal/next.{type Next, AbnormalStop, Continue, NormalStop}
pub type ValidMessage(user_message) {
SocketMessage(BitArray)
SocketClosedMessage
UserMessage(user_message)
}
pub type WebsocketMessage(user_message) {
Valid(ValidMessage(user_message))
Invalid
}
pub type WebsocketConnection {
WebsocketConnection(
socket: Socket,
transport: Transport,
deflate: Option(Context),
)
}
pub type HandlerMessage(user_message) {
Internal(Frame)
User(user_message)
}
pub type WebsocketState(state) {
WebsocketState(
buffer: BitArray,
user: state,
permessage_deflate: Option(Compression),
)
}
pub type Handler(state, message) =
fn(state, HandlerMessage(message), WebsocketConnection) ->
Next(state, message)
// TODO: this is pulled straight from glisten, prob should share it
fn message_selector() -> Selector(WebsocketMessage(user_message)) {
process.new_selector()
|> process.select_record(atom.create("tcp"), 2, fn(record) {
{
use data <- decode.field(2, decode.bit_array)
decode.success(SocketMessage(data))
}
|> decode.run(record, _)
|> result.replace_error(Nil)
|> result.map(Valid)
|> result.unwrap(Invalid)
})
|> process.select_record(atom.create("ssl"), 2, fn(record) {
{
use data <- decode.field(2, decode.bit_array)
decode.success(SocketMessage(data))
}
|> decode.run(record, _)
|> result.replace_error(Nil)
|> result.map(Valid)
|> result.unwrap(Invalid)
})
|> process.select_record(atom.create("ssl_closed"), 1, fn(_nil) {
Valid(SocketClosedMessage)
})
|> process.select_record(atom.create("tcp_closed"), 1, fn(_nil) {
Valid(SocketClosedMessage)
})
}
pub fn initialize_connection(
on_init: fn(WebsocketConnection) -> #(state, Option(Selector(user_message))),
on_close: fn(state) -> Nil,
handler: Handler(state, user_message),
socket: Socket,
transport: Transport,
extensions: List(String),
) -> Result(actor.Started(process.Pid), actor.StartError) {
let takeovers = websocket.get_context_takeovers(extensions)
actor.new_with_initialiser(500, fn(subject) {
let compression = case websocket.has_deflate(extensions) {
True -> Some(compression.init(takeovers))
False -> None
}
let connection =
WebsocketConnection(
socket:,
transport:,
deflate: option.map(compression, fn(compression) { compression.deflate }),
)
let #(initial_state, user_selector) = on_init(connection)
let selector = case user_selector {
Some(user_selector) ->
user_selector
|> process.map_selector(UserMessage)
|> process.map_selector(Valid)
|> process.merge_selector(message_selector())
_ -> message_selector()
}
WebsocketState(
buffer: <<>>,
user: initial_state,
permessage_deflate: compression,
)
|> actor.initialised
|> actor.selecting(selector)
|> actor.returning(subject)
|> Ok
})
|> actor.on_message(fn(state, msg) {
let connection =
WebsocketConnection(
socket:,
transport:,
deflate: option.map(state.permessage_deflate, fn(compression) {
compression.deflate
}),
)
case msg {
Valid(SocketMessage(data)) -> {
let #(frames, rest) =
websocket.decode_many_frames(
<<state.buffer:bits, data:bits>>,
option.map(state.permessage_deflate, fn(compression) {
compression.inflate
}),
[],
)
frames
|> websocket.aggregate_frames(None, [])
|> result.map(fn(frames) {
let next =
apply_frames(
frames,
handler,
connection,
Continue(state.user, None),
on_close,
)
case next {
Continue(user_state, selector) -> {
let next =
actor.continue(
WebsocketState(..state, buffer: rest, user: user_state),
)
case selector {
Some(selector) -> actor.with_selector(next, selector)
_ -> next
}
}
NormalStop -> {
let _ =
option.map(state.permessage_deflate, fn(contexts) {
compression.close(contexts.deflate)
compression.close(contexts.inflate)
})
actor.stop()
}
AbnormalStop(reason) -> {
let _ =
option.map(state.permessage_deflate, fn(contexts) {
compression.close(contexts.deflate)
compression.close(contexts.inflate)
})
actor.stop_abnormal(reason)
}
}
})
|> result.lazy_unwrap(fn() {
logging.log(logging.Error, "Received a malformed WebSocket frame")
on_close(state.user)
let _ =
option.map(state.permessage_deflate, fn(contexts) {
compression.close(contexts.deflate)
compression.close(contexts.inflate)
})
actor.stop_abnormal("WebSocket received a malformed message")
})
}
Valid(UserMessage(msg)) -> {
exception.rescue(fn() { handler(state.user, User(msg), connection) })
|> result.map(fn(cont) {
case cont {
Continue(user_state, selector) -> {
let selector =
selector
|> map_user_selector
|> option.map(fn(with_user) {
process.merge_selector(message_selector(), with_user)
})
let next =
actor.continue(WebsocketState(..state, user: user_state))
case selector {
Some(selector) -> actor.with_selector(next, selector)
_ -> next
}
}
NormalStop -> {
let _ =
transport.send(
connection.transport,
connection.socket,
websocket.encode_close_frame(websocket.Normal(<<>>), None),
)
let _ =
option.map(state.permessage_deflate, fn(contexts) {
compression.close(contexts.deflate)
compression.close(contexts.inflate)
})
on_close(state.user)
actor.stop()
}
AbnormalStop(reason) -> {
let _ =
transport.send(
connection.transport,
connection.socket,
websocket.encode_close_frame(
websocket.CustomCloseReason(
4000,
bit_array.from_string(reason),
),
None,
),
)
let _ =
option.map(state.permessage_deflate, fn(contexts) {
compression.close(contexts.deflate)
compression.close(contexts.inflate)
})
on_close(state.user)
actor.stop_abnormal(reason)
}
}
})
|> result.map_error(fn(_err) {
logging.log(logging.Error, "Caught error in websocket handler")
})
|> result.lazy_unwrap(fn() {
let _ =
option.map(state.permessage_deflate, fn(contexts) {
compression.close(contexts.deflate)
compression.close(contexts.inflate)
})
on_close(state.user)
actor.stop_abnormal("Crash in user websocket handler")
})
}
Valid(SocketClosedMessage) -> {
let _ =
option.map(state.permessage_deflate, fn(contexts) {
compression.close(contexts.deflate)
compression.close(contexts.inflate)
})
on_close(state.user)
actor.stop()
}
// TODO: do we need to send something back for this?
Invalid -> {
logging.log(logging.Error, "Received a malformed WebSocket frame")
let _ =
option.map(state.permessage_deflate, fn(contexts) {
compression.close(contexts.deflate)
compression.close(contexts.inflate)
})
on_close(state.user)
actor.stop_abnormal("WebSocket received a malformed message")
}
}
})
|> actor.start
|> result.map(fn(subj) {
let assert Ok(websocket_pid) = process.subject_owner(subj.data)
actor.Started(websocket_pid, websocket_pid)
})
}
fn apply_frames(
frames: List(Frame),
handler: Handler(state, user_message),
connection: WebsocketConnection,
next: Next(state, WebsocketMessage(user_message)),
on_close: fn(state) -> Nil,
) -> Next(state, WebsocketMessage(user_message)) {
case frames, next {
_, AbnormalStop(reason) -> AbnormalStop(reason)
_, NormalStop -> NormalStop
[], next -> {
set_active(connection.transport, connection.socket)
next
}
[Control(CloseFrame(reason)), ..], Continue(state, _selector) -> {
let _ =
transport.send(
connection.transport,
connection.socket,
websocket.encode_close_frame(reason, None),
)
on_close(state)
NormalStop
}
[Control(PingFrame(payload)), ..], Continue(state, _selector) -> {
transport.send(
connection.transport,
connection.socket,
websocket.encode_pong_frame(payload, None),
)
|> result.map(fn(_nil) {
set_active(connection.transport, connection.socket)
Continue(state, None)
})
|> result.lazy_unwrap(fn() {
on_close(state)
AbnormalStop("Failed to send pong frame")
})
}
[frame, ..rest], Continue(state, prev_selector) -> {
case
exception.rescue(fn() { handler(state, Internal(frame), connection) })
{
Ok(Continue(state, selector)) -> {
let next_selector =
selector
|> map_user_selector
|> option.or(prev_selector)
|> option.map(fn(with_user) {
process.merge_selector(message_selector(), with_user)
})
apply_frames(
rest,
handler,
connection,
Continue(state, next_selector),
on_close,
)
}
Ok(AbnormalStop(reason)) -> {
let _ =
transport.send(
connection.transport,
connection.socket,
websocket.encode_close_frame(
websocket.CustomCloseReason(4000, bit_array.from_string(reason)),
None,
),
)
on_close(state)
AbnormalStop(reason)
}
Ok(NormalStop) -> {
let _ =
transport.send(
connection.transport,
connection.socket,
websocket.encode_close_frame(websocket.Normal(<<>>), None),
)
on_close(state)
NormalStop
}
Error(_reason) -> {
logging.log(logging.Error, "Caught error in websocket handler")
on_close(state)
AbnormalStop("Crash in user websocket handler")
}
}
}
}
}
pub fn set_active(transport: Transport, socket: Socket) -> Nil {
let assert Ok(_) =
transport.set_opts(transport, socket, [options.ActiveMode(options.Once)])
Nil
}
fn map_user_selector(
selector: Option(Selector(user_message)),
) -> Option(Selector(WebsocketMessage(user_message))) {
option.map(
selector,
process.map_selector(_, fn(msg) { Valid(UserMessage(msg)) }),
)
}