diff --git a/src/ewe/internal/http2.gleam b/src/ewe/internal/http2.gleam index 804ee84..6f23a88 100644 --- a/src/ewe/internal/http2.gleam +++ b/src/ewe/internal/http2.gleam @@ -115,6 +115,7 @@ pub type Stream { content_length: Option(Int), body_bytes_received: Int, trailers: List(#(String, String)), + method: http.Method, ) } @@ -949,7 +950,16 @@ fn spawn_stream( False -> { let pid = stream.start(state.reply_subject, stream_id, request, state.handler) - Proceed(track_stream(state, stream_id, pid, end_stream, content_length)) + + track_stream( + state, + stream_id, + pid, + end_stream, + content_length, + request.method, + ) + |> Proceed } } } @@ -960,10 +970,12 @@ fn track_stream( pid: process.Pid, end_stream: Bool, content_length: Option(Int), + method: http.Method, ) -> State { let entry = Stream( status: Computing(pid), + method:, send_window: state.peer_settings.initial_window_size, pending: PendingBytes(<<>>), pending_end_stream: True, @@ -1619,29 +1631,54 @@ fn respond( response: Response(connection.Body), connection: glisten.Connection(connection.Message), ) -> Next { - case open_pending(response.body, state.options.file_read_threshold) { - Ok(pending) -> + case entry.method { + http.Head -> send_response( state, stream_id, entry, response.status, response.headers, - response_body_size(response.body), - pending, - connection, - ) - Error(_error) -> - send_response( - state, - stream_id, - entry, - 500, - [], - 0, + head_content_length(response.body), PendingBytes(<<>>), connection, ) + _method -> + case open_pending(response.body, state.options.file_read_threshold) { + Ok(pending) -> + send_response( + state, + stream_id, + entry, + response.status, + response.headers, + Some(response_body_size(response.body)), + pending, + connection, + ) + Error(_error) -> + send_response( + state, + stream_id, + entry, + 500, + [], + Some(0), + PendingBytes(<<>>), + connection, + ) + } + } +} + +fn head_content_length(body: connection.Body) -> Option(Int) { + case body { + connection.Streaming(_metadata) | connection.Sse(_metadata) -> None + connection.Bytes(_tree) + | connection.Text(_text) + | connection.Empty + | connection.File(_file) + | connection.Websocket(_metadata) -> Some(response_body_size(body)) } } @@ -1651,18 +1688,19 @@ fn send_response( entry: Stream, status: Int, headers: List(#(String, String)), - body_size: Int, + content_length: Option(Int), pending: Pending, connection: glisten.Connection(connection.Message), ) -> Next { let fields = build_response_headers(headers, state.patterns.forbidden) - let content_length_fields = case status { - status if status == 204 || { status >= 100 && status < 200 } -> [] - _status -> [ + let content_length_fields = case status, content_length { + status, _length if status == 204 || { status >= 100 && status < 200 } -> [] + _status, None -> [] + _status, Some(length) -> [ alpacki.HeaderField( <<"content-length":utf8>>, - <>, + <>, alpacki.WithoutIndexing, ), ] @@ -1683,7 +1721,7 @@ fn send_response( let state = State(..state, hpack_encoder:) - let has_body = body_size != 0 + let has_body = pending_has_bytes(pending) let out = bytes_tree.new() |> append_header_frames( @@ -2144,7 +2182,11 @@ pub fn flush_stream( } fn has_pending(entry: Stream) -> Bool { - case entry.pending { + pending_has_bytes(entry.pending) +} + +fn pending_has_bytes(pending: Pending) -> Bool { + case pending { PendingBytes(<<>>) -> False PendingFile(_descriptor, _offset, 0) -> False _pending -> True diff --git a/src/ewe/internal/http2/stream.gleam b/src/ewe/internal/http2/stream.gleam index f2402ec..88cb4b2 100644 --- a/src/ewe/internal/http2/stream.gleam +++ b/src/ewe/internal/http2/stream.gleam @@ -3,6 +3,7 @@ import ewe/internal/http2/connection as http2 import ewe/internal/rescue import gleam/erlang/process import gleam/erlang/reference +import gleam/http import gleam/http/request import gleam/http/response import gleam/result @@ -20,7 +21,7 @@ pub fn start( process.trap_exits(True) case rescue.handler(fn() { handler(request) }) { - Ok(response) -> deliver(reply_to, stream_id, response) + Ok(response) -> deliver(reply_to, stream_id, response, request.method) Error(details) -> crashed(reply_to, stream_id, details) } } @@ -49,14 +50,25 @@ fn deliver( reply_to: process.Subject(http2.Reply(connection.Body)), stream_id: Int, response: response.Response(connection.Body), + method: http.Method, ) -> Nil { - case response.body { - connection.Streaming(connection.StreamingMetadata(handler:)) -> + case response.body, method { + connection.Websocket(_metadata), _method -> { + logging.log( + logging.Error, + "Discarded a WebSocket response: HTTP/2 connections do not carry them", + ) + + internal_error(reply_to, stream_id) + } + _body, http.Head -> + process.send(reply_to, http2.Respond(stream_id, response)) + connection.Streaming(connection.StreamingMetadata(handler:)), _method -> case begin(reply_to, stream_id, response, http2.Nothing) { Error(_interrupted) -> Nil Ok(writer) -> handler(connection.Http2Writer(writer)) } - connection.Sse(connection.SseMetadata(handler:)) -> + connection.Sse(connection.SseMetadata(handler:)), _method -> case begin(reply_to, stream_id, response, http2.SseHeaders) { Error(_interrupted) -> Nil Ok(writer) -> @@ -65,19 +77,11 @@ fn deliver( connection.StoppedAbnormal(reason) -> abort(reason) } } - connection.Websocket(_metadata) -> { - logging.log( - logging.Error, - "Discarded a WebSocket response: HTTP/2 connections do not carry them", - ) - - internal_error(reply_to, stream_id) - } - connection.Bytes(_tree) - | connection.Text(_text) - | connection.Empty - | connection.File(_file) -> - process.send(reply_to, http2.Respond(stream_id, response)) + connection.Bytes(_tree), _method + | connection.Text(_text), _method + | connection.Empty, _method + | connection.File(_file), _method + -> process.send(reply_to, http2.Respond(stream_id, response)) } } diff --git a/test/ewe/internal/http2/connection_test.gleam b/test/ewe/internal/http2/connection_test.gleam index e0f27bd..dd0f0e4 100644 --- a/test/ewe/internal/http2/connection_test.gleam +++ b/test/ewe/internal/http2/connection_test.gleam @@ -25,6 +25,7 @@ fn pending_stream(send_window: Int, pending: BitArray) -> connection.Stream { content_length: None, body_bytes_received: 0, trailers: [], + method: http.Get, ) } @@ -45,6 +46,7 @@ fn inbound_stream( content_length: None, body_bytes_received: 0, trailers: [], + method: http.Get, ) }