Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
rawhat committed May 23, 2024
1 parent c6f84ad commit aa092bb
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 35 deletions.
9 changes: 8 additions & 1 deletion src/mist/internal/handler.gleam
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import gleam/erlang/process.{type Selector, type Subject}
import gleam/function
import gleam/http as ghttp
import gleam/http/response
import gleam/option.{type Option, Some}
import gleam/otp/actor
Expand Down Expand Up @@ -61,7 +62,13 @@ pub fn with_func(handler: Handler) -> Loop(Message, State) {
Bytes(bytes) -> {
resp
|> response.set_body(bytes)
|> http2.send_bytes_builder(conn, state.send_hpack_context, id)
|> http2.send_bytes_builder(
conn,
state.send_hpack_context,
id,
// TODO: this is kinda lame
ghttp.Post,
)
}
File(..) ->
Error(process.Abnormal("File sending unsupported over HTTP/2"))
Expand Down
71 changes: 50 additions & 21 deletions src/mist/internal/http.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ fn read_chunk(
}
}
}
<<>> as data, _ | data, Error(Nil) -> {
<<>> as data, _
| data, Error(Nil) -> {
use next <- result.then(read_data(
socket,
transport,
Expand Down Expand Up @@ -383,7 +384,9 @@ pub fn read_body(
data: data,
remaining: remaining,
attempts: attempts,
) if remaining > 0 -> {
)
if remaining > 0
-> {
let res =
selector
|> process.select(1000)
Expand Down Expand Up @@ -474,7 +477,7 @@ pub fn upgrade(

use _sent <- result.then(
resp
|> add_default_headers(True)
|> add_default_headers(True, req.method == http.Head)
|> encoder.to_bytes_builder
|> transport.send(transport, socket, _)
|> result.nil_error,
Expand All @@ -486,36 +489,62 @@ pub fn upgrade(
pub fn add_default_headers(
resp: Response(BytesBuilder),
keep_alive: Bool,
is_head_response: Bool,
) -> Response(BytesBuilder) {
let body_size = bytes_builder.byte_size(resp.body)

let defaults = [#("content-length", int.to_string(body_size))]
let defaults = {
use <- bool.guard(when: !keep_alive, return: defaults)
[#("connection", "keep-alive"), ..defaults]
let include_content_length = case resp.status {
n if n >= 100 && n <= 199 -> False
n if n == 204 -> False
n if n == 304 -> False
_ -> True
}

let #(_existing, headers) =
resp.headers
|> list.key_pop("content-length")
|> result.lazy_unwrap(fn() { #("", resp.headers) })

let headers = case include_content_length, is_head_response {
True, True -> resp.headers
True, False -> {
[#("content-length", int.to_string(body_size)), ..resp.headers]
}
False, _ -> headers
}

let existing = list.key_pop(headers, "connection")
let keep_alive_header = #("connection", "keep-alive")
let headers = case keep_alive, existing {
False, Ok(#(connection, headers)) -> [
#("connection", connection),
..headers
]
False, _ -> headers
_, Ok(#("keep-alive", headers)) -> [keep_alive_header, ..headers]
True, _ -> [keep_alive_header, ..headers]
}
let defaults = {
let headers = {
case response.get_header(resp, "date") {
Error(_nil) -> [#("date", clock.get_date()), ..defaults]
_ -> defaults
Error(_nil) -> [#("date", clock.get_date()), ..headers]
_ -> headers
}
}

let headers =
dict.from_list(defaults)
|> list.fold(
resp.headers,
_,
fn(defaults, tup) {
let #(key, value) = tup
dict.insert(defaults, key, value)
},
)
|> dict.to_list
io.debug(#(
"building headers with",
keep_alive,
"and response",
resp,
"and got",
headers,
))

Response(..resp, headers: headers)
}

import gleam/io

fn is_continue(req: Request(Connection)) -> Bool {
req.headers
|> list.find(fn(tup) {
Expand Down
15 changes: 10 additions & 5 deletions src/mist/internal/http/handler.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import gleam/bytes_builder.{type BytesBuilder}
import gleam/dynamic
import gleam/erlang.{Errored, Exited, Thrown, rescue}
import gleam/erlang/process.{type Subject}
import gleam/http as ghttp
import gleam/http/request.{type Request}
import gleam/http/response
import gleam/int
Expand Down Expand Up @@ -35,7 +36,7 @@ pub fn call(
sender: Subject(handler.Message(user_message)),
) -> Result(State, process.ExitReason) {
rescue(fn() { handler(req) })
|> result.map_error(log_and_error(_, conn.socket, conn.transport))
|> result.map_error(log_and_error(_, conn.socket, conn.transport, req))
|> result.then(fn(resp) {
case resp {
response.Response(body: Websocket(selector), ..)
Expand All @@ -45,7 +46,7 @@ pub fn call(
}
response.Response(body: body, ..) as resp -> {
case body {
Bytes(body) -> handle_bytes_builder_body(resp, body, conn)
Bytes(body) -> handle_bytes_builder_body(resp, body, conn, req)
Chunked(body) -> handle_chunked_body(resp, body, conn)
File(..) -> handle_file_body(resp, body, conn)
_ -> panic as "This shouldn't ever happen 🤞"
Expand All @@ -61,17 +62,20 @@ fn log_and_error(
error: erlang.Crash,
socket: Socket,
transport: Transport,
req: Request(Connection),
) -> process.ExitReason {
case error {
Exited(msg) | Thrown(msg) | Errored(msg) -> {
Exited(msg)
| Thrown(msg)
| Errored(msg) -> {
logging.log(logging.Error, string.inspect(error))
let _ =
response.new(500)
|> response.set_body(
bytes_builder.from_bit_array(<<"Internal Server Error":utf8>>),
)
|> response.prepend_header("content-length", "21")
|> http.add_default_headers(True)
|> http.add_default_headers(True, req.method == ghttp.Head)
|> encoder.to_bytes_builder
|> transport.send(transport, socket, _)
let _ = transport.close(transport, socket)
Expand Down Expand Up @@ -178,10 +182,11 @@ fn handle_bytes_builder_body(
resp: response.Response(ResponseData),
body: BytesBuilder,
conn: Connection,
req: Request(Connection),
) -> Result(Nil, SocketReason) {
resp
|> response.set_body(body)
|> http.add_default_headers(True)
|> http.add_default_headers(True, req.method == ghttp.Head)
|> encoder.to_bytes_builder
|> transport.send(conn.transport, conn.socket, _)
}
Expand Down
21 changes: 13 additions & 8 deletions src/mist/internal/http2.gleam
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import gleam/bytes_builder.{type BytesBuilder}
import gleam/erlang
import gleam/erlang/process
import gleam/http.{type Header} as _http
import gleam/http.{type Header} as ghttp
import gleam/http/response.{type Response}
import gleam/int
import gleam/list
Expand Down Expand Up @@ -127,23 +127,28 @@ pub fn send_bytes_builder(
conn: Connection,
context: HpackContext,
id: StreamIdentifier(Frame),
method: ghttp.Method,
) -> Result(HpackContext, process.ExitReason) {
let resp =
resp
|> http.add_default_headers(False)
|> http.add_default_headers(False, method == ghttp.Head)

let headers = [#(":status", int.to_string(resp.status)), ..resp.headers]

case bytes_builder.byte_size(resp.body) {
0 -> send_headers(context, conn, headers, True, id)
_ -> {
send_headers(context, conn, headers, False, id)
|> result.then(fn(context) {
// TODO: this should be broken up by window size
// TODO: fix end_stream
send_data(conn, bytes_builder.to_bit_array(resp.body), id, True)
|> result.replace(context)
})
|> result.then(
fn(
context,
// TODO: this should be broken up by window size
// TODO: fix end_stream
) {
send_data(conn, bytes_builder.to_bit_array(resp.body), id, True)
|> result.replace(context)
},
)
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions test/http1_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ pub fn it_supports_patch_requests_test() {
string_response_should_equal(resp, get_default_response())
}

import gleam/io

pub fn it_rejects_large_requests_test() {
let req =
string.repeat("a", 4_000_001)
Expand All @@ -102,6 +104,8 @@ pub fn it_rejects_large_requests_test() {
|> response.prepend_header("content-length", "0")
|> response.prepend_header("connection", "close")

io.debug(#("got a resp", resp))

string_response_should_equal(resp, expected)
}

Expand Down

0 comments on commit aa092bb

Please sign in to comment.