From e73e379ab59840e8561b5730878f16e29ab06217 Mon Sep 17 00:00:00 2001 From: Mat Trudel Date: Fri, 6 Dec 2024 08:52:42 -0500 Subject: [PATCH] Handle pipelined requests (#437) * 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 --- lib/bandit/http1/handler.ex | 11 ++++++++++- lib/bandit/http1/socket.ex | 5 +++-- test/bandit/http1/request_test.exs | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/bandit/http1/handler.ex b/lib/bandit/http1/handler.ex index 7b01aab2..5b64df6b 100644 --- a/lib/bandit/http1/handler.ex +++ b/lib/bandit/http1/handler.ex @@ -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 diff --git a/lib/bandit/http1/socket.ex b/lib/bandit/http1/socket.ex index 6534d8af..e8130c8b 100644 --- a/lib/bandit/http1/socket.ex +++ b/lib/bandit/http1/socket.ex @@ -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) diff --git a/test/bandit/http1/request_test.exs b/test/bandit/http1/request_test.exs index 124a9d63..190088b5 100644 --- a/test/bandit/http1/request_test.exs +++ b/test/bandit/http1/request_test.exs @@ -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)