mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
Add ProvideRangeCallback tests using sendfile
And fix this case when multiple ranges are requested.
This commit is contained in:
parent
dd0fbab6b7
commit
7840f6db7b
3 changed files with 91 additions and 1 deletions
|
@ -1363,7 +1363,13 @@ set_ranged_body_callback(Req, State=#state{handler=Handler}, Callback) ->
|
||||||
%% this for non-bytes units they can always return a single range with a binary
|
%% this for non-bytes units they can always return a single range with a binary
|
||||||
%% content-range information.
|
%% content-range information.
|
||||||
{Ranges, Req2, State2} when length(Ranges) > 1 ->
|
{Ranges, Req2, State2} when length(Ranges) > 1 ->
|
||||||
set_multipart_ranged_body(Req2, State2, Ranges)
|
%% We have to check whether there are sendfile tuples in the
|
||||||
|
%% ranges to be sent. If there are we must use stream_reply.
|
||||||
|
HasSendfile = [] =/= [true || {_, {sendfile, _, _, _}} <- Ranges],
|
||||||
|
case HasSendfile of
|
||||||
|
true -> send_multipart_ranged_body(Req2, State2, Ranges);
|
||||||
|
false -> set_multipart_ranged_body(Req2, State2, Ranges)
|
||||||
|
end
|
||||||
end catch Class:{case_clause, no_call} ->
|
end catch Class:{case_clause, no_call} ->
|
||||||
error_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}},
|
error_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}},
|
||||||
'A callback specified in ranges_provided/2 is not exported.'})
|
'A callback specified in ranges_provided/2 is not exported.'})
|
||||||
|
|
|
@ -43,11 +43,31 @@ get_text_plain(Req, State) ->
|
||||||
get_text_plain_bytes(#{qs := <<"missing">>}, _) ->
|
get_text_plain_bytes(#{qs := <<"missing">>}, _) ->
|
||||||
ct_helper_error_h:ignore(cowboy_rest, set_ranged_body_callback, 3),
|
ct_helper_error_h:ignore(cowboy_rest, set_ranged_body_callback, 3),
|
||||||
no_call;
|
no_call;
|
||||||
|
get_text_plain_bytes(Req=#{qs := <<"sendfile">>, range := {_, [{From=0, infinity}]}}, State) ->
|
||||||
|
Path = code:lib_dir(cowboy) ++ "/ebin/cowboy.app",
|
||||||
|
Size = filelib:file_size(Path),
|
||||||
|
{[{{From, Size - 1, Size}, {sendfile, From, Size, Path}}], Req, State};
|
||||||
get_text_plain_bytes(Req=#{range := {_, [{From=0, infinity}]}}, State) ->
|
get_text_plain_bytes(Req=#{range := {_, [{From=0, infinity}]}}, State) ->
|
||||||
%% We send everything in one part.
|
%% We send everything in one part.
|
||||||
Body = <<"This is ranged REST!">>,
|
Body = <<"This is ranged REST!">>,
|
||||||
Total = byte_size(Body),
|
Total = byte_size(Body),
|
||||||
{[{{From, Total - 1, Total}, Body}], Req, State};
|
{[{{From, Total - 1, Total}, Body}], Req, State};
|
||||||
|
get_text_plain_bytes(Req=#{qs := <<"sendfile">>, range := {_, Range}}, State) ->
|
||||||
|
%% We check the range header we get and send everything hardcoded.
|
||||||
|
[
|
||||||
|
{50, 99},
|
||||||
|
{150, 199},
|
||||||
|
{250, 299},
|
||||||
|
-99
|
||||||
|
] = Range,
|
||||||
|
Path = code:lib_dir(cowboy) ++ "/ebin/cowboy.app",
|
||||||
|
Size = filelib:file_size(Path),
|
||||||
|
{[
|
||||||
|
{{50, 99, Size}, {sendfile, 50, 50, Path}},
|
||||||
|
{{150, 199, Size}, {sendfile, 150, 50, Path}},
|
||||||
|
{{250, 299, Size}, {sendfile, 250, 50, Path}},
|
||||||
|
{{Size - 99, Size - 1, Size}, {sendfile, Size - 99, 99, Path}}
|
||||||
|
], Req, State};
|
||||||
get_text_plain_bytes(Req=#{range := {_, Range}}, State) ->
|
get_text_plain_bytes(Req=#{range := {_, Range}}, State) ->
|
||||||
%% We check the range header we get and send everything hardcoded.
|
%% We check the range header we get and send everything hardcoded.
|
||||||
[
|
[
|
||||||
|
|
|
@ -413,6 +413,30 @@ provide_range_callback(Config) ->
|
||||||
{ok, <<"This is ranged REST!">>} = gun:await_body(ConnPid, Ref),
|
{ok, <<"This is ranged REST!">>} = gun:await_body(ConnPid, Ref),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
provide_range_callback_sendfile(Config) ->
|
||||||
|
doc("A successful request for a single range results in a "
|
||||||
|
"206 partial content response with content-range set. (RFC7233 4.1, RFC7233 4.2)"),
|
||||||
|
ConnPid = gun_open(Config),
|
||||||
|
Ref = gun:get(ConnPid, "/provide_range_callback?sendfile", [
|
||||||
|
{<<"accept-encoding">>, <<"gzip">>},
|
||||||
|
{<<"range">>, <<"bytes=0-">>}
|
||||||
|
]),
|
||||||
|
Path = code:lib_dir(cowboy) ++ "/ebin/cowboy.app",
|
||||||
|
Size = filelib:file_size(Path),
|
||||||
|
{ok, Body} = file:read_file(Path),
|
||||||
|
{response, nofin, 206, Headers} = gun:await(ConnPid, Ref),
|
||||||
|
{_, <<"bytes">>} = lists:keyfind(<<"accept-ranges">>, 1, Headers),
|
||||||
|
{_, ContentRange} = lists:keyfind(<<"content-range">>, 1, Headers),
|
||||||
|
ContentRange = iolist_to_binary([
|
||||||
|
<<"bytes 0-">>,
|
||||||
|
integer_to_binary(Size - 1),
|
||||||
|
<<"/">>,
|
||||||
|
integer_to_binary(Size)
|
||||||
|
]),
|
||||||
|
{_, <<"text/plain">>} = lists:keyfind(<<"content-type">>, 1, Headers),
|
||||||
|
{ok, Body} = gun:await_body(ConnPid, Ref),
|
||||||
|
ok.
|
||||||
|
|
||||||
provide_range_callback_multipart(Config) ->
|
provide_range_callback_multipart(Config) ->
|
||||||
doc("A successful request for multiple ranges results in a "
|
doc("A successful request for multiple ranges results in a "
|
||||||
"206 partial content response using the multipart/byteranges "
|
"206 partial content response using the multipart/byteranges "
|
||||||
|
@ -442,6 +466,46 @@ provide_range_callback_multipart(Config) ->
|
||||||
<<"ThisisrangedREST!">> = BodyAcc,
|
<<"ThisisrangedREST!">> = BodyAcc,
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
provide_range_callback_multipart_sendfile(Config) ->
|
||||||
|
doc("A successful request for multiple ranges results in a "
|
||||||
|
"206 partial content response using the multipart/byteranges "
|
||||||
|
"content-type and the content-range not being set. The real "
|
||||||
|
"content-type and content-range of the parts can be found in "
|
||||||
|
"the multipart headers. (RFC7233 4.1, RFC7233 A)"),
|
||||||
|
ConnPid = gun_open(Config),
|
||||||
|
Ref = gun:get(ConnPid, "/provide_range_callback?sendfile", [
|
||||||
|
{<<"accept-encoding">>, <<"gzip">>},
|
||||||
|
%% This range selects a few random chunks of the file.
|
||||||
|
{<<"range">>, <<"bytes=50-99, 150-199, 250-299, -99">>}
|
||||||
|
]),
|
||||||
|
Path = code:lib_dir(cowboy) ++ "/ebin/cowboy.app",
|
||||||
|
Size = filelib:file_size(Path),
|
||||||
|
Skip = Size - 399,
|
||||||
|
{ok, <<
|
||||||
|
_:50/binary, Body1:50/binary,
|
||||||
|
_:50/binary, Body2:50/binary,
|
||||||
|
_:50/binary, Body3:50/binary,
|
||||||
|
_:Skip/binary, Body4/bits>>} = file:read_file(Path),
|
||||||
|
{response, nofin, 206, Headers} = gun:await(ConnPid, Ref),
|
||||||
|
{_, <<"bytes">>} = lists:keyfind(<<"accept-ranges">>, 1, Headers),
|
||||||
|
false = lists:keyfind(<<"content-range">>, 1, Headers),
|
||||||
|
{_, <<"multipart/byteranges; boundary=", Boundary/bits>>}
|
||||||
|
= lists:keyfind(<<"content-type">>, 1, Headers),
|
||||||
|
{ok, Body0} = gun:await_body(ConnPid, Ref),
|
||||||
|
Body = do_decode(Headers, Body0),
|
||||||
|
%% We will receive the ranges in the same order as requested.
|
||||||
|
{ContentRanges, BodyAcc} = do_provide_range_callback_multipart_body(Body, Boundary, [], <<>>),
|
||||||
|
LastFrom = 300 + Skip,
|
||||||
|
LastTo = Size - 1,
|
||||||
|
[
|
||||||
|
{bytes, 50, 99, Size},
|
||||||
|
{bytes, 150, 199, Size},
|
||||||
|
{bytes, 250, 299, Size},
|
||||||
|
{bytes, LastFrom, LastTo, Size}
|
||||||
|
] = ContentRanges,
|
||||||
|
BodyAcc = <<Body1/binary, Body2/binary, Body3/binary, Body4/binary>>,
|
||||||
|
ok.
|
||||||
|
|
||||||
do_provide_range_callback_multipart_body(Rest, Boundary, ContentRangesAcc, BodyAcc) ->
|
do_provide_range_callback_multipart_body(Rest, Boundary, ContentRangesAcc, BodyAcc) ->
|
||||||
case cow_multipart:parse_headers(Rest, Boundary) of
|
case cow_multipart:parse_headers(Rest, Boundary) of
|
||||||
{ok, Headers, Rest1} ->
|
{ok, Headers, Rest1} ->
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue