Packages
mist
5.0.1
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/http2/handler.gleam
import gleam/bit_array
import gleam/dict.{type Dict}
import gleam/erlang/process.{type Subject}
import gleam/int
import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/result
import gleam/string
import logging
import mist/internal/buffer.{type Buffer}
import mist/internal/http.{type Connection, type Handler, Connection, Initial}
import mist/internal/http2.{type HpackContext, type Http2Settings, Http2Settings}
import mist/internal/http2/flow_control
import mist/internal/http2/frame.{
type Frame, type StreamIdentifier, Complete, Continued,
}
import mist/internal/http2/stream.{type SendMessage, Ready}
pub type PendingSend {
PendingSend
}
pub type State {
State(
fragment: Option(Frame),
frame_buffer: Buffer,
pending_sends: List(PendingSend),
receive_hpack_context: HpackContext,
self: Subject(SendMessage),
send_hpack_context: HpackContext,
send_window_size: Int,
receive_window_size: Int,
settings: Http2Settings,
streams: Dict(StreamIdentifier(Frame), stream.State),
)
}
pub fn send_hpack_context(state: State, context: HpackContext) -> State {
State(..state, send_hpack_context: context)
}
pub fn receive_hpack_context(state: State, context: HpackContext) -> State {
State(..state, receive_hpack_context: context)
}
pub fn append_data(state: State, data: BitArray) -> State {
State(..state, frame_buffer: buffer.append(state.frame_buffer, data))
}
pub fn upgrade(
data: BitArray,
conn: Connection,
self: Subject(SendMessage),
) -> Result(State, String) {
let initial_settings = http2.default_settings()
let settings_frame = frame.Settings(ack: False, settings: [])
let sent =
http2.send_frame(settings_frame, conn.socket, conn.transport)
|> result.replace_error("Failed to send settings frame")
use _nil <- result.map(sent)
State(
fragment: None,
frame_buffer: buffer.new(data),
pending_sends: [],
receive_hpack_context: http2.hpack_new_context(
initial_settings.header_table_size,
),
receive_window_size: 65_535,
self: self,
send_hpack_context: http2.hpack_new_context(
initial_settings.header_table_size,
),
send_window_size: 65_535,
settings: initial_settings,
streams: dict.new(),
)
}
pub fn call(
state: State,
conn: Connection,
handler: Handler,
) -> Result(State, Result(Nil, String)) {
case frame.decode(state.frame_buffer.data) {
Ok(#(frame, rest)) -> {
let new_state = State(..state, frame_buffer: buffer.new(rest))
case handle_frame(frame, new_state, conn, handler) {
Ok(updated) -> call(updated, conn, handler)
Error(reason) -> Error(Error(reason))
}
}
Error(frame.NoError) -> Ok(state)
Error(_connection_error) -> {
// TODO:
// - send GOAWAY with last good stream ID
// - close the connection
Ok(state)
}
}
}
// TODO: this should use the frame error types to actually do some shit with
// the stream(s)
fn handle_frame(
frame: Frame,
state: State,
conn: Connection,
handler: Handler,
) -> Result(State, String) {
case state.fragment, frame {
Some(frame.Header(
identifier: id1,
data: Continued(existing),
end_stream: end_stream,
priority: priority,
)),
frame.Continuation(data: Complete(data), identifier: id2)
if id1 == id2
-> {
let complete_frame =
frame.Header(
identifier: id1,
data: Complete(<<existing:bits, data:bits>>),
end_stream: end_stream,
priority: priority,
)
handle_frame(
complete_frame,
State(..state, fragment: None),
conn,
handler,
)
}
Some(frame.Header(
identifier: id1,
data: Continued(existing),
end_stream: end_stream,
priority: priority,
)),
frame.Continuation(data: Continued(data), identifier: id2)
if id1 == id2
-> {
let next =
frame.Header(
identifier: id1,
data: Continued(<<existing:bits, data:bits>>),
end_stream: end_stream,
priority: priority,
)
Ok(State(..state, fragment: Some(next)))
}
None, frame.WindowUpdate(amount, identifier) -> {
case frame.get_stream_identifier(identifier) {
0 -> {
// do_pending_sends(state)
Ok(
State(
..state,
settings: Http2Settings(
..state.settings,
initial_window_size: amount,
),
),
)
}
_stream_id -> {
state.streams
|> dict.get(identifier)
|> result.replace_error("Window update for non-existent stream")
|> result.then(fn(stream) {
case
flow_control.update_send_window(stream.send_window_size, amount)
{
Ok(update) -> {
let new_stream =
stream.State(..stream, send_window_size: update)
Ok(
State(
..state,
streams: dict.insert(state.streams, identifier, new_stream),
),
)
}
_err -> Error("Failed to update send window")
}
})
}
}
}
None, frame.Header(Complete(data), end_stream, identifier, _priority) -> {
let conn =
Connection(
body: Initial(<<>>),
socket: conn.socket,
transport: conn.transport,
)
let assert Ok(#(headers, context)) =
http2.hpack_decode(state.receive_hpack_context, data)
let pending_content_length =
headers
|> list.key_find("content-length")
|> result.then(int.parse)
|> option.from_result
let assert Ok(new_stream) =
stream.new(
identifier,
handler,
headers,
conn,
state.self,
// fn(resp) { process.send(state.self, Send(identifier, resp)) },
end_stream,
)
process.send(new_stream.data, Ready)
let stream_state =
stream.State(
id: identifier,
state: stream.Open,
subject: new_stream.data,
receive_window_size: state.settings.initial_window_size,
send_window_size: state.settings.initial_window_size,
pending_content_length: pending_content_length,
)
let streams = dict.insert(state.streams, identifier, stream_state)
Ok(State(..state, receive_hpack_context: context, streams: streams))
}
None, frame.Data(identifier: identifier, data: data, end_stream: end_stream)
-> {
let data_size = bit_array.byte_size(data)
let #(conn_receive_window_size, conn_window_increment) =
flow_control.compute_receive_window(
state.receive_window_size,
data_size,
)
state.streams
|> dict.get(identifier)
|> result.map(stream.receive_data(_, data_size))
// TODO: this whole business should much more gracefully handle
// individual stream errors rather than just blowin up
|> result.replace_error("Stream failed to receive data")
// TODO: handle end of stream?
|> result.map(fn(update) {
let #(new_stream, increment) = update
let _ = case conn_window_increment > 0 {
True -> {
http2.send_frame(
frame.WindowUpdate(
identifier: frame.stream_identifier(0),
amount: conn_window_increment,
),
conn.socket,
conn.transport,
)
}
False -> Ok(Nil)
}
let _ = case increment > 0 {
True -> {
http2.send_frame(
frame.WindowUpdate(identifier: identifier, amount: increment),
conn.socket,
conn.transport,
)
}
False -> Ok(Nil)
}
process.send(
new_stream.subject,
stream.Data(bits: data, end: end_stream),
)
State(
..state,
streams: dict.insert(state.streams, identifier, new_stream),
receive_window_size: conn_receive_window_size,
)
})
}
None, frame.Priority(..) -> {
Ok(state)
}
None, frame.Settings(ack: True, ..) -> {
Ok(state)
}
// TODO: update any settings from this
_, frame.Settings(..) -> {
http2.send_frame(frame.settings_ack(), conn.socket, conn.transport)
|> result.replace(state)
|> result.replace_error("Failed to respond to settings ACK")
}
None, frame.GoAway(..) -> {
logging.log(logging.Debug, "byteeee~~")
// TODO: Normal exit
Error("Going away...")
}
// TODO: obviously fill these out
_, frame -> {
logging.log(logging.Debug, "Ignoring frame: " <> string.inspect(frame))
Ok(state)
}
}
}