Skip to content

Commit

Permalink
Handle pipelined requests (#437)
Browse files Browse the repository at this point in the history
* Fix error where we weren't maintaining tail buffer at the end of chunked reads

* Fix bug where we were throwing away pipelined requests during keepalive
  • Loading branch information
mtrudel authored Dec 6, 2024
1 parent 267b4af commit e73e379
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 3 deletions.
11 changes: 10 additions & 1 deletion lib/bandit/http1/handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,16 @@ defmodule Bandit.HTTP1.Handler do
if Keyword.get(state.opts.http_1, :clear_process_dict, true), do: clear_process_dict()
gc_every_n_requests = Keyword.get(state.opts.http_1, :gc_every_n_keepalive_requests, 5)
if rem(requests_processed, gc_every_n_requests) == 0, do: :erlang.garbage_collect()
{:continue, Map.put(state, :requests_processed, requests_processed)}

state = Map.put(state, :requests_processed, requests_processed)

# We have bytes that we've read but haven't yet processed, tail call handle_data to start
# reading the next request
if IO.iodata_length(transport.buffer) != 0 do
handle_data(transport.buffer, transport.socket, state)
else
{:continue, state}
end
else
{:close, state}
end
Expand Down
5 changes: 3 additions & 2 deletions lib/bandit/http1/socket.ex
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,9 @@ defmodule Bandit.HTTP1.Socket do
@dialyzer {:no_improper_lists, do_read_chunked_data!: 5}
defp do_read_chunked_data!(socket, buffer, body, read_size, read_timeout) do
case :binary.split(buffer, "\r\n") do
["0", _] ->
{IO.iodata_to_binary(body), buffer}
["0", "\r\n" <> rest] ->
# We should be reading (and ignoring) trailers here
{IO.iodata_to_binary(body), rest}

[chunk_size, rest] ->
chunk_size = String.to_integer(chunk_size, 16)
Expand Down
16 changes: 16 additions & 0 deletions test/bandit/http1/request_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,22 @@ defmodule HTTP1RequestTest do
end

describe "keepalive requests" do
test "handles pipeline requests", context do
client = SimpleHTTP1Client.tcp_client(context)

Transport.send(
client,
String.duplicate("GET /hello_world HTTP/1.1\r\nHost: localhost\r\n\r\n", 50)
)

for _ <- 1..50 do
# Need to read the exact size of the expected response because SimpleHTTP1Client
# doesn't track 'rest' bytes and ends up throwing a bunch of responses on the floor
{:ok, bytes} = Transport.recv(client, 152)
assert({:ok, "200 OK", _, _} = SimpleHTTP1Client.parse_response(client, bytes))
end
end

test "closes connection after max_requests is reached", context do
context = http_server(context, http_1_options: [max_requests: 3])
client = SimpleHTTP1Client.tcp_client(context)
Expand Down

0 comments on commit e73e379

Please sign in to comment.