Packages
mist
5.0.3
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.gleam
import gleam/bytes_tree.{type BytesTree}
import gleam/http.{type Header} as _ghttp
import gleam/http/response.{type Response}
import gleam/int
import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/result
import gleam/string
import glisten/socket.{type Socket, type SocketReason}
import glisten/transport.{type Transport}
import logging
import mist/internal/http.{type Connection}
import mist/internal/http2/frame.{
type Frame, type PushState, type Setting, type StreamIdentifier, Complete,
Data, Header,
}
pub type Http2Settings {
Http2Settings(
header_table_size: Int,
server_push: PushState,
max_concurrent_streams: Int,
initial_window_size: Int,
max_frame_size: Int,
max_header_list_size: Option(Int),
)
}
pub fn default_settings() -> Http2Settings {
Http2Settings(
header_table_size: 4096,
server_push: frame.Disabled,
max_concurrent_streams: 100,
initial_window_size: 65_535,
max_frame_size: 16_384,
max_header_list_size: None,
)
}
pub fn update_settings(
current: Http2Settings,
settings: List(Setting),
) -> Http2Settings {
list.fold(settings, current, fn(settings, setting) {
case setting {
frame.HeaderTableSize(size) ->
Http2Settings(..settings, header_table_size: size)
frame.ServerPush(push) -> Http2Settings(..settings, server_push: push)
frame.MaxConcurrentStreams(max) ->
Http2Settings(..settings, max_concurrent_streams: max)
frame.InitialWindowSize(size) ->
Http2Settings(..settings, initial_window_size: size)
frame.MaxFrameSize(size) ->
Http2Settings(..settings, max_frame_size: size)
frame.MaxHeaderListSize(size) ->
Http2Settings(..settings, max_header_list_size: Some(size))
}
})
}
fn send_headers(
context: HpackContext,
conn: Connection,
headers: List(Header),
end_stream: Bool,
stream_identifier: StreamIdentifier(Frame),
) -> Result(HpackContext, String) {
hpack_encode(context, headers)
|> result.try(fn(pair) {
let #(headers, new_context) = pair
let header_frame =
Header(
data: Complete(headers),
end_stream: end_stream,
identifier: stream_identifier,
priority: None,
)
let encoded = frame.encode(header_frame)
case
transport.send(
conn.transport,
conn.socket,
bytes_tree.from_bit_array(encoded),
)
{
Ok(_nil) -> Ok(new_context)
Error(_reason) -> Error("Failed to send HTTP/2 headers")
}
})
}
fn send_data(
conn: Connection,
data: BitArray,
stream_identifier: StreamIdentifier(Frame),
end_stream: Bool,
) -> Result(Nil, String) {
let data_frame =
Data(data: data, end_stream: end_stream, identifier: stream_identifier)
let encoded = frame.encode(data_frame)
transport.send(
conn.transport,
conn.socket,
bytes_tree.from_bit_array(encoded),
)
|> result.map_error(fn(err) {
logging.log(logging.Debug, "failed to send :( " <> string.inspect(err))
"Failed to send HTTP/2 data"
})
}
// TODO: handle max frame size
pub fn send_frame(
frame_to_send: Frame,
socket: Socket,
transport: Transport,
) -> Result(Nil, SocketReason) {
let data = frame.encode(frame_to_send)
transport.send(transport, socket, bytes_tree.from_bit_array(data))
}
pub fn send_bytes_tree(
resp: Response(BytesTree),
conn: Connection,
context: HpackContext,
id: StreamIdentifier(Frame),
) -> Result(HpackContext, String) {
let resp =
resp
|> http.add_default_headers(False)
let headers = [#(":status", int.to_string(resp.status)), ..resp.headers]
case bytes_tree.byte_size(resp.body) {
0 -> send_headers(context, conn, headers, True, id)
_ -> {
send_headers(context, conn, headers, False, id)
|> result.try(fn(context) {
// TODO: this should be broken up by window size
// TODO: fix end_stream
send_data(conn, bytes_tree.to_bit_array(resp.body), id, True)
|> result.replace(context)
})
}
}
}
pub type HpackContext
@external(erlang, "hpack", "new_context")
pub fn hpack_new_context(size: Int) -> HpackContext
@external(erlang, "mist_ffi", "hpack_new_max_table_size")
pub fn hpack_max_table_size(context: HpackContext, size: Int) -> HpackContext
pub type HpackError {
Compression
BadHeaderPacket(BitArray)
}
@external(erlang, "mist_ffi", "hpack_decode")
pub fn hpack_decode(
context: HpackContext,
bin: BitArray,
) -> Result(#(List(Header), HpackContext), HpackError)
@external(erlang, "mist_ffi", "hpack_encode")
pub fn hpack_encode(
context: HpackContext,
headers: List(Header),
) -> Result(#(BitArray, HpackContext), error)