From 8e121d138c4f658cf0953eeeeea85d07c203a5b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Thu, 6 Feb 2025 12:18:59 +0100 Subject: [PATCH] Fix request_timeout triggering when a request was in the buffer The problem was that when a request immediately following another request with a large enough body, the data for the new request (including headers) would be buffered waiting for more data, instead of being processed immediately. If no more data came in on the socket the request_timeout would eventually trigger. Now the buffer is processed immediately. --- src/cowboy_http.erl | 4 ++-- test/http_SUITE.erl | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl index c1c4b8d7..bc4b5281 100644 --- a/src/cowboy_http.erl +++ b/src/cowboy_http.erl @@ -436,8 +436,8 @@ after_parse({data, StreamID, IsFin, Data, State0=#state{opts=Opts, buffer=Buffer end; %% No corresponding stream. We must skip the body of the previous request %% in order to process the next one. -after_parse({data, _, IsFin, _, State}) -> - loop(set_timeout(State, case IsFin of +after_parse({data, StreamID, IsFin, _, State=#state{buffer=Buffer}}) -> + parse(Buffer, set_timeout(State, case IsFin of fin -> request_timeout; nofin -> idle_timeout end)); diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index a7522506..4b8fc8c0 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -43,6 +43,7 @@ end_per_group(Name, _) -> init_dispatch(_) -> cowboy_router:compile([{"localhost", [ {"/", hello_h, []}, + {"/delay_hello", delay_hello_h, #{delay => 1000, notify_received => self()}}, {"/echo/:key", echo_h, []}, {"/resp/:key[/:arg]", resp_h, []}, {"/set_options/:key", set_options_h, []}, @@ -454,6 +455,26 @@ request_timeout_pipeline(Config) -> cowboy:stop_listener(?FUNCTION_NAME) end. +request_timeout_pipeline_delay(Config) -> + doc("Ensure the request_timeout does not trigger on requests " + "coming in after a large request body."), + {ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{ + env => #{dispatch => init_dispatch(Config)}, + request_timeout => 500 + }), + Port = ranch:get_port(?FUNCTION_NAME), + try + ConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]), + {ok, http} = gun:await_up(ConnPid), + StreamRef1 = gun:post(ConnPid, "/", #{}, <<0:8000000>>), + StreamRef2 = gun:get(ConnPid, "/delay_hello"), + {response, nofin, 200, _} = gun:await(ConnPid, StreamRef1), + {response, nofin, 200, _} = gun:await(ConnPid, StreamRef2), + {error, {down, {shutdown, closed}}} = gun:await(ConnPid, undefined, 1000) + after + cowboy:stop_listener(?FUNCTION_NAME) + end. + request_timeout_skip_body(Config) -> doc("Ensure the request_timeout drops connections when requests " "fail to come in fast enough after skipping a request body."),