Packages
mist
4.0.6
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/stream.gleam
import gleam/dynamic
import gleam/erlang
import gleam/erlang/process.{type Selector, type Subject}
import gleam/function
import gleam/http.{type Header} as ghttp
import gleam/http/request.{type Request, Request}
import gleam/http/response.{type Response}
import gleam/int
import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/otp/actor
import gleam/pair
import gleam/result
import gleam/string
import gleam/uri
import mist/internal/http.{
type Connection, type Handler, type ResponseData, Connection, Stream,
}
import mist/internal/http2/flow_control
import mist/internal/http2/frame.{type Frame, type StreamIdentifier}
pub type Message {
Ready
Data(bits: BitArray, end: Bool)
Done
}
pub type StreamState {
Open
RemoteClosed
LocalClosed
Closed
}
pub type State {
State(
id: StreamIdentifier(Frame),
state: StreamState,
subject: Subject(Message),
receive_window_size: Int,
send_window_size: Int,
pending_content_length: Option(Int),
)
}
pub type InternalState {
InternalState(
data_selector: Selector(Message),
data_subject: Subject(Message),
end: Bool,
pending_response: Option(Response(ResponseData)),
to_remove: BitArray,
)
}
pub fn new(
handler: Handler,
headers: List(Header),
connection: Connection,
send: fn(Response(ResponseData)) -> todo_resp,
end: Bool,
) -> Result(Subject(Message), actor.StartError) {
actor.start_spec(
actor.Spec(
init: fn() {
let data_subj = process.new_subject()
let data_selector =
process.new_selector()
|> process.selecting(data_subj, function.identity)
actor.Ready(
InternalState(data_selector, data_subj, end, None, <<>>),
data_selector,
)
},
init_timeout: 1000,
loop: fn(msg, state) {
case msg, state.end {
Ready, _ -> {
let content_length =
headers
|> list.key_find("content-length")
|> result.then(int.parse)
|> result.unwrap(0)
let conn =
Connection(
..connection,
body: Stream(
selector: process.map_selector(state.data_selector, fn(val) {
let assert Data(bits, ..) = val
bits
}),
attempts: 0,
data: <<>>,
remaining: content_length,
),
)
request.new()
|> request.set_body(conn)
|> make_request(headers, _)
|> result.map(handler)
|> result.map(fn(resp) {
process.send(state.data_subject, Done)
actor.continue(
InternalState(..state, pending_response: Some(resp)),
)
})
|> result.map_error(fn(err) {
actor.Stop(process.Abnormal(
"Failed to respond to request: " <> erlang.format(err),
))
})
|> result.unwrap_both
}
Done, True -> {
let assert Some(resp) = state.pending_response
send(resp)
actor.continue(state)
}
Data(bits: bits, end: True), _ -> {
process.send(state.data_subject, Done)
actor.continue(
InternalState(..state, end: True, to_remove: <<
state.to_remove:bits,
bits:bits,
>>),
)
}
Data(bits: bits, ..), _ -> {
actor.continue(
InternalState(..state, to_remove: <<
state.to_remove:bits,
bits:bits,
>>),
)
}
_msg, _ -> {
// TODO: probably just discard this?
actor.continue(state)
}
}
},
),
)
}
pub fn make_request(
headers: List(Header),
req: Request(Connection),
) -> Result(Request(Connection), Nil) {
case headers {
[] -> Ok(req)
[#("method", method), ..rest] -> {
method
|> dynamic.from
|> ghttp.method_from_dynamic
|> result.replace_error(Nil)
|> result.map(request.set_method(req, _))
|> result.then(make_request(rest, _))
}
[#("scheme", scheme), ..rest] -> {
scheme
|> ghttp.scheme_from_string
|> result.replace_error(Nil)
|> result.map(request.set_scheme(req, _))
|> result.then(make_request(rest, _))
}
// TODO
[#("authority", _authority), ..rest] -> make_request(rest, req)
[#("path", path), ..rest] -> {
path
|> string.split_once(on: "?")
|> result.map(fn(split) {
pair.map_second(split, fn(query) {
query
|> uri.parse_query
|> result.map(Some)
|> result.unwrap(None)
})
})
|> result.unwrap(#(path, None))
|> fn(tup: #(String, Option(List(#(String, String))))) {
case tup.1 {
Some(query) ->
req
|> request.set_path(tup.0)
|> request.set_query(query)
_ -> request.set_path(req, tup.0)
}
|> make_request(rest, _)
}
}
[#(key, value), ..rest] ->
req
|> request.set_header(key, value)
|> make_request(rest, _)
}
}
pub fn receive_data(state: State, size: Int) -> #(State, Int) {
let #(new_window_size, increment) =
flow_control.compute_receive_window(state.receive_window_size, size)
let new_state =
State(
..state,
receive_window_size: new_window_size,
pending_content_length: option.map(state.pending_content_length, fn(val) {
val - size
}),
)
#(new_state, increment)
}