Skip to content

Commit 55aa3e9

Browse files
authored
Set idle timeout when streaming responses (#52)
* Set idle timeout when streaming responses * Run CI on 26,27 and 28 * Bit more consistent timeout setting
1 parent cf21184 commit 55aa3e9

File tree

7 files changed

+53
-9
lines changed

7 files changed

+53
-9
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818

1919
strategy:
2020
matrix:
21-
otp_version: [25,26,27]
21+
otp_version: [26,27,28]
2222
os: [ubuntu-latest]
2323

2424
container:

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,4 @@ doc/*
2020
!doc/img
2121
!doc/img/*
2222
!doc/img/logo.png
23-
*.lock
2423
.vscode

rebar.config

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
{deps, [
88
{zotonic_stdlib, "~> 1.6"},
9-
{cowboy, "2.9.0"}
9+
{cowlib, "~> 2.16"},
10+
{cowboy, "~> 2.14"}
1011
]}.
1112

1213
{profiles, [

rebar.lock

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{"1.2.0",
2+
[{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.9.0">>},0},
3+
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.16.0">>},0},
4+
{<<"qdate_localtime">>,{pkg,<<"qdate_localtime">>,<<"1.2.1">>},1},
5+
{<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},1},
6+
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.7">>},2},
7+
{<<"tls_certificate_check">>,
8+
{pkg,<<"tls_certificate_check">>,<<"1.29.0">>},
9+
1},
10+
{<<"zotonic_stdlib">>,{pkg,<<"zotonic_stdlib">>,<<"1.24.0">>},0}]}.
11+
[
12+
{pkg_hash,[
13+
{<<"cowboy">>, <<"865DD8B6607E14CF03282E10E934023A1BD8BE6F6BACF921A7E2A96D800CD452">>},
14+
{<<"cowlib">>, <<"54592074EBBBB92EE4746C8A8846E5605052F29309D3A873468D76CDF932076F">>},
15+
{<<"qdate_localtime">>, <<"72E1034DC6B7FEE8F588281EDDD0BD0DC5260005D758052F50634D265D382C18">>},
16+
{<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>},
17+
{<<"ssl_verify_fun">>, <<"354C321CF377240C7B8716899E182CE4890C5938111A1296ADD3EC74CF1715DF">>},
18+
{<<"tls_certificate_check">>, <<"4473005EB0BBDAD215D7083A230E2E076F538D9EA472C8009FD22006A4CFC5F6">>},
19+
{<<"zotonic_stdlib">>, <<"31456B4C25B41043B83E539C25FF387C5594BE8542906242DC4E07FAE1B81844">>}]},
20+
{pkg_hash_ext,[
21+
{<<"cowboy">>, <<"2C729F934B4E1AA149AFF882F57C6372C15399A20D54F65C8D67BEF583021BDE">>},
22+
{<<"cowlib">>, <<"7F478D80D66B747344F0EA7708C187645CFCC08B11AA424632F78E25BF05DB51">>},
23+
{<<"qdate_localtime">>, <<"1109958D205C65C595C8C5694CB83EBAF2DBE770CF902E4DCE8AFB2C4123764D">>},
24+
{<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>},
25+
{<<"ssl_verify_fun">>, <<"FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8">>},
26+
{<<"tls_certificate_check">>, <<"5B0D0E5CB0F928BC4F210DF667304ED91C5BFF2A391CE6BDEDFBFE70A8F096C5">>},
27+
{<<"zotonic_stdlib">>, <<"BC626DE1E5884E4695DA91A1169A0797D13F8EE933FE1C5452B9C10EFABADA03">>}]}
28+
].

src/cowmachine_decision_core.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,7 @@ is_if_range_ok(<<$", _/binary>> = IfETag, ETag, _LM) ->
847847
lists:member(ETag, ETags);
848848
is_if_range_ok(Date, _ETag, LM) ->
849849
ErlDate = cowmachine_util:convert_request_date(Date),
850-
ErlDate =/= undefined andalso ErlDate >= LM.
850+
ErlDate =/= bad_date andalso ErlDate >= LM.
851851

852852

853853
choose_content_encoding(AccEncHdr, State, Context) ->

src/cowmachine_response.erl

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
]).
3737

3838
-define(FILE_CHUNK_LENGTH, 16#80000). % 512KB
39+
-define(DEFAULT_IDLE_TIMEOUT, 60000).
3940

4041
%% @doc Returns server header.
4142
-spec server_header() -> Result when
@@ -116,8 +117,6 @@ send_response_code(Code, Parts, Context) ->
116117
Parts :: cowmachine_req:parts(),
117118
Context :: cowmachine_req:context(),
118119
Result :: cowmachine_req:context().
119-
% send_response_bodyfun(undefined, Code, Parts, Context) ->
120-
% send_response_bodyfun(<<>>, Code, Parts, Context);
121120
send_response_bodyfun({device, IO}, Code, Parts, Context) ->
122121
Length = iodevice_size(IO),
123122
send_response_bodyfun({device, Length, IO}, Code, Parts, Context);
@@ -191,11 +190,14 @@ send_response_bodyfun(Body, Code, all, Context) ->
191190
Req1 = cowboy_req:reply(Code, Headers, Body, Req),
192191
cowmachine_req:set_req(Req1, Context);
193192
send_response_bodyfun(Body, Code, Parts, Context) ->
193+
set_idle_timeout(infinity, Context),
194194
Headers = response_headers(Context),
195195
Req = cowmachine_req:req(Context),
196196
Req1 = cowboy_req:stream_reply(Code, Headers, Req),
197197
Context1 = cowmachine_req:set_req(Req1, Context),
198-
send_parts(Context1, Parts, iolist_to_binary(Body)).
198+
Context2 = send_parts(Context1, Parts, iolist_to_binary(Body)),
199+
set_idle_timeout(?DEFAULT_IDLE_TIMEOUT, Context2),
200+
Context2.
199201

200202
-spec start_response_stream(Code, Length, InitialStream, Parts, Context) -> Result when
201203
Code :: integer(),
@@ -205,6 +207,7 @@ send_response_bodyfun(Body, Code, Parts, Context) ->
205207
Context :: cowmachine_req:context(),
206208
Result :: cowmachine_req:context().
207209
start_response_stream(Code, Length, InitialStream, Parts, Context) ->
210+
set_idle_timeout(infinity, Context),
208211
{Code1, Context1, Parts1} = case is_streaming_range(InitialStream) of
209212
false when Parts =/= all ->
210213
% Drop range response header
@@ -227,7 +230,9 @@ start_response_stream(Code, Length, InitialStream, Parts, Context) ->
227230
InitialFun ->
228231
stream_initial_fun(InitialFun, Parts1)
229232
end,
230-
send_stream_body(FirstHunk, Context2).
233+
Context3 = send_stream_body(FirstHunk, Context2),
234+
set_idle_timeout(?DEFAULT_IDLE_TIMEOUT, Context3),
235+
Context3.
231236

232237
-spec stream_initial_fun(Fun, Parts) -> Result when
233238
Fun :: function(),
@@ -240,6 +245,17 @@ stream_initial_fun(F, _Parts) when is_function(F) ->
240245
stream_initial_fun(done, _Parts) ->
241246
done.
242247

248+
%% @doc Set the idle timeout for the connection. This will reset the idle timeout
249+
%% timer for HTTP/1.x connections. During streaming we do not want a timeout.
250+
%% The default is 60 seconds.
251+
-spec set_idle_timeout(Timeout, Context) -> ok when
252+
Timeout :: pos_integer() | infinity,
253+
Context :: cowmachine_req:context().
254+
set_idle_timeout(Timeout, Context) ->
255+
Req = cowmachine_req:req(Context),
256+
cowboy_req:cast({set_options, #{
257+
idle_timeout => Timeout
258+
}}, Req).
243259

244260
%% @doc Check if we support ranges on the data stream (body or function)
245261
-spec is_streaming_range(Stream) -> Result when

src/cowmachine_util.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ valid_location(Location) ->
8787

8888
-spec convert_request_date(Date) -> Result when
8989
Date :: binary(),
90-
Result :: calendar:datetime().
90+
Result :: calendar:datetime() | bad_date.
9191
convert_request_date(Date) ->
9292
try
9393
cow_date:parse_date(Date)

0 commit comments

Comments
 (0)