diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1d7daad..384410e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: - otp_version: [25,26,27] + otp_version: [26,27,28] os: [ubuntu-latest] container: diff --git a/.gitignore b/.gitignore index f1e47d7..3d38335 100644 --- a/.gitignore +++ b/.gitignore @@ -20,5 +20,4 @@ doc/* !doc/img !doc/img/* !doc/img/logo.png -*.lock .vscode diff --git a/rebar.config b/rebar.config index 7c9b062..7ff39ea 100644 --- a/rebar.config +++ b/rebar.config @@ -6,7 +6,8 @@ {deps, [ {zotonic_stdlib, "~> 1.6"}, - {cowboy, "2.9.0"} + {cowlib, "~> 2.16"}, + {cowboy, "~> 2.14"} ]}. {profiles, [ diff --git a/rebar.lock b/rebar.lock new file mode 100644 index 0000000..89c3ea6 --- /dev/null +++ b/rebar.lock @@ -0,0 +1,28 @@ +{"1.2.0", +[{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.9.0">>},0}, + {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.16.0">>},0}, + {<<"qdate_localtime">>,{pkg,<<"qdate_localtime">>,<<"1.2.1">>},1}, + {<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},1}, + {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.7">>},2}, + {<<"tls_certificate_check">>, + {pkg,<<"tls_certificate_check">>,<<"1.29.0">>}, + 1}, + {<<"zotonic_stdlib">>,{pkg,<<"zotonic_stdlib">>,<<"1.24.0">>},0}]}. +[ +{pkg_hash,[ + {<<"cowboy">>, <<"865DD8B6607E14CF03282E10E934023A1BD8BE6F6BACF921A7E2A96D800CD452">>}, + {<<"cowlib">>, <<"54592074EBBBB92EE4746C8A8846E5605052F29309D3A873468D76CDF932076F">>}, + {<<"qdate_localtime">>, <<"72E1034DC6B7FEE8F588281EDDD0BD0DC5260005D758052F50634D265D382C18">>}, + {<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>}, + {<<"ssl_verify_fun">>, <<"354C321CF377240C7B8716899E182CE4890C5938111A1296ADD3EC74CF1715DF">>}, + {<<"tls_certificate_check">>, <<"4473005EB0BBDAD215D7083A230E2E076F538D9EA472C8009FD22006A4CFC5F6">>}, + {<<"zotonic_stdlib">>, <<"31456B4C25B41043B83E539C25FF387C5594BE8542906242DC4E07FAE1B81844">>}]}, +{pkg_hash_ext,[ + {<<"cowboy">>, <<"2C729F934B4E1AA149AFF882F57C6372C15399A20D54F65C8D67BEF583021BDE">>}, + {<<"cowlib">>, <<"7F478D80D66B747344F0EA7708C187645CFCC08B11AA424632F78E25BF05DB51">>}, + {<<"qdate_localtime">>, <<"1109958D205C65C595C8C5694CB83EBAF2DBE770CF902E4DCE8AFB2C4123764D">>}, + {<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>}, + {<<"ssl_verify_fun">>, <<"FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8">>}, + {<<"tls_certificate_check">>, <<"5B0D0E5CB0F928BC4F210DF667304ED91C5BFF2A391CE6BDEDFBFE70A8F096C5">>}, + {<<"zotonic_stdlib">>, <<"BC626DE1E5884E4695DA91A1169A0797D13F8EE933FE1C5452B9C10EFABADA03">>}]} +]. diff --git a/src/cowmachine_decision_core.erl b/src/cowmachine_decision_core.erl index 4462fa0..7f3ecce 100644 --- a/src/cowmachine_decision_core.erl +++ b/src/cowmachine_decision_core.erl @@ -847,7 +847,7 @@ is_if_range_ok(<<$", _/binary>> = IfETag, ETag, _LM) -> lists:member(ETag, ETags); is_if_range_ok(Date, _ETag, LM) -> ErlDate = cowmachine_util:convert_request_date(Date), - ErlDate =/= undefined andalso ErlDate >= LM. + ErlDate =/= bad_date andalso ErlDate >= LM. choose_content_encoding(AccEncHdr, State, Context) -> diff --git a/src/cowmachine_response.erl b/src/cowmachine_response.erl index 941fd21..275fe9b 100644 --- a/src/cowmachine_response.erl +++ b/src/cowmachine_response.erl @@ -36,6 +36,7 @@ ]). -define(FILE_CHUNK_LENGTH, 16#80000). % 512KB +-define(DEFAULT_IDLE_TIMEOUT, 60000). %% @doc Returns server header. -spec server_header() -> Result when @@ -116,8 +117,6 @@ send_response_code(Code, Parts, Context) -> Parts :: cowmachine_req:parts(), Context :: cowmachine_req:context(), Result :: cowmachine_req:context(). -% send_response_bodyfun(undefined, Code, Parts, Context) -> -% send_response_bodyfun(<<>>, Code, Parts, Context); send_response_bodyfun({device, IO}, Code, Parts, Context) -> Length = iodevice_size(IO), send_response_bodyfun({device, Length, IO}, Code, Parts, Context); @@ -191,11 +190,14 @@ send_response_bodyfun(Body, Code, all, Context) -> Req1 = cowboy_req:reply(Code, Headers, Body, Req), cowmachine_req:set_req(Req1, Context); send_response_bodyfun(Body, Code, Parts, Context) -> + set_idle_timeout(infinity, Context), Headers = response_headers(Context), Req = cowmachine_req:req(Context), Req1 = cowboy_req:stream_reply(Code, Headers, Req), Context1 = cowmachine_req:set_req(Req1, Context), - send_parts(Context1, Parts, iolist_to_binary(Body)). + Context2 = send_parts(Context1, Parts, iolist_to_binary(Body)), + set_idle_timeout(?DEFAULT_IDLE_TIMEOUT, Context2), + Context2. -spec start_response_stream(Code, Length, InitialStream, Parts, Context) -> Result when Code :: integer(), @@ -205,6 +207,7 @@ send_response_bodyfun(Body, Code, Parts, Context) -> Context :: cowmachine_req:context(), Result :: cowmachine_req:context(). start_response_stream(Code, Length, InitialStream, Parts, Context) -> + set_idle_timeout(infinity, Context), {Code1, Context1, Parts1} = case is_streaming_range(InitialStream) of false when Parts =/= all -> % Drop range response header @@ -227,7 +230,9 @@ start_response_stream(Code, Length, InitialStream, Parts, Context) -> InitialFun -> stream_initial_fun(InitialFun, Parts1) end, - send_stream_body(FirstHunk, Context2). + Context3 = send_stream_body(FirstHunk, Context2), + set_idle_timeout(?DEFAULT_IDLE_TIMEOUT, Context3), + Context3. -spec stream_initial_fun(Fun, Parts) -> Result when Fun :: function(), @@ -240,6 +245,17 @@ stream_initial_fun(F, _Parts) when is_function(F) -> stream_initial_fun(done, _Parts) -> done. +%% @doc Set the idle timeout for the connection. This will reset the idle timeout +%% timer for HTTP/1.x connections. During streaming we do not want a timeout. +%% The default is 60 seconds. +-spec set_idle_timeout(Timeout, Context) -> ok when + Timeout :: pos_integer() | infinity, + Context :: cowmachine_req:context(). +set_idle_timeout(Timeout, Context) -> + Req = cowmachine_req:req(Context), + cowboy_req:cast({set_options, #{ + idle_timeout => Timeout + }}, Req). %% @doc Check if we support ranges on the data stream (body or function) -spec is_streaming_range(Stream) -> Result when diff --git a/src/cowmachine_util.erl b/src/cowmachine_util.erl index b20438a..94e8c0b 100644 --- a/src/cowmachine_util.erl +++ b/src/cowmachine_util.erl @@ -87,7 +87,7 @@ valid_location(Location) -> -spec convert_request_date(Date) -> Result when Date :: binary(), - Result :: calendar:datetime(). + Result :: calendar:datetime() | bad_date. convert_request_date(Date) -> try cow_date:parse_date(Date)