0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 12:20:24 +00:00

Add sendfile support to cowboy_req:stream_body

It is now possible to stream one or more sendfile tuples.
A simple example of what can now be done would be for
example to build a tar file on the fly using the sendfile
syscall for sending the files, or to support Range requests
with more than one range with the sendfile syscall.

When using cowboy_compress_h unfortunately we have to read
the file in order to send it. More options will be added
at a later time to make sure users don't read too much
into memory. This is a new feature however so existing
code is not affected.

Also rework cowboy_http's data sending to be flatter.
This commit is contained in:
Loïc Hoguin 2018-11-09 17:42:37 +01:00
parent 29043aa7b4
commit d7b7580b39
No known key found for this signature in database
GPG key ID: 8A9DF795F6FED764
12 changed files with 219 additions and 100 deletions

View file

@ -121,6 +121,24 @@ gzip_stream_reply(Config) ->
_ = zlib:gunzip(GzBody),
ok.
gzip_stream_reply_sendfile(Config) ->
doc("Stream reply using sendfile for some chunks; get a gzipped response."),
{200, Headers, GzBody} = do_get("/stream_reply/sendfile",
[{<<"accept-encoding">>, <<"gzip">>}], Config),
{_, <<"gzip">>} = lists:keyfind(<<"content-encoding">>, 1, Headers),
file:write_file("/tmp/test.gz", GzBody),
_ = zlib:gunzip(GzBody),
ok.
gzip_stream_reply_sendfile_fin(Config) ->
doc("Stream reply using sendfile for some chunks; get a gzipped response."),
{200, Headers, GzBody} = do_get("/stream_reply/sendfile_fin",
[{<<"accept-encoding">>, <<"gzip">>}], Config),
{_, <<"gzip">>} = lists:keyfind(<<"content-encoding">>, 1, Headers),
file:write_file("/tmp/test.gz", GzBody),
_ = zlib:gunzip(GzBody),
ok.
gzip_stream_reply_content_encoding(Config) ->
doc("Stream reply with content-encoding header; get an uncompressed response."),
{200, Headers, Body} = do_get("/stream_reply/content-encoding",

View file

@ -27,7 +27,30 @@ init(Req0, State=stream_reply) ->
<<"large">> ->
stream_reply(#{}, Req0);
<<"content-encoding">> ->
stream_reply(#{<<"content-encoding">> => <<"compress">>}, Req0)
stream_reply(#{<<"content-encoding">> => <<"compress">>}, Req0);
<<"sendfile">> ->
Data = lists:duplicate(10000, $a),
AppFile = code:where_is_file("cowboy.app"),
Size = filelib:file_size(AppFile),
Req1 = cowboy_req:stream_reply(200, Req0),
%% We send a few files interspersed into other data.
cowboy_req:stream_body(Data, nofin, Req1),
cowboy_req:stream_body({sendfile, 0, Size, AppFile}, nofin, Req1),
cowboy_req:stream_body(Data, nofin, Req1),
cowboy_req:stream_body({sendfile, 0, Size, AppFile}, nofin, Req1),
cowboy_req:stream_body(Data, fin, Req1),
Req1;
<<"sendfile_fin">> ->
Data = lists:duplicate(10000, $a),
AppFile = code:where_is_file("cowboy.app"),
Size = filelib:file_size(AppFile),
Req1 = cowboy_req:stream_reply(200, Req0),
%% We send a few files interspersed into other data.
cowboy_req:stream_body(Data, nofin, Req1),
cowboy_req:stream_body({sendfile, 0, Size, AppFile}, nofin, Req1),
cowboy_req:stream_body(Data, nofin, Req1),
cowboy_req:stream_body({sendfile, 0, Size, AppFile}, fin, Req1),
Req1
end,
{ok, Req, State}.

View file

@ -219,6 +219,23 @@ do(<<"stream_body">>, Req0, Opts) ->
Req = cowboy_req:stream_reply(200, Req0),
cowboy_req:stream_body(<<"Hello world!">>, nofin, Req),
{ok, Req, Opts};
<<"sendfile">> ->
AppFile = code:where_is_file("cowboy.app"),
AppSize = filelib:file_size(AppFile),
Req = cowboy_req:stream_reply(200, Req0),
cowboy_req:stream_body(<<"Hello ">>, nofin, Req),
cowboy_req:stream_body({sendfile, 0, AppSize, AppFile}, nofin, Req),
cowboy_req:stream_body(<<" interspersed ">>, nofin, Req),
cowboy_req:stream_body({sendfile, 0, AppSize, AppFile}, nofin, Req),
cowboy_req:stream_body(<<" world!">>, fin, Req),
{ok, Req, Opts};
<<"sendfile_fin">> ->
AppFile = code:where_is_file("cowboy.app"),
AppSize = filelib:file_size(AppFile),
Req = cowboy_req:stream_reply(200, Req0),
cowboy_req:stream_body(<<"Hello! ">>, nofin, Req),
cowboy_req:stream_body({sendfile, 0, AppSize, AppFile}, fin, Req),
{ok, Req, Opts};
_ ->
%% Call stream_body without initiating streaming.
cowboy_req:stream_body(<<0:800000>>, fin, Req0),

View file

@ -885,21 +885,44 @@ stream_reply3(Config) ->
{500, _, _} = do_get("/resp/stream_reply3/error", Config),
ok.
stream_body_multiple(Config) ->
doc("Streamed body via multiple calls."),
{200, _, <<"Hello world!">>} = do_get("/resp/stream_body/multiple", Config),
ok.
stream_body_fin0(Config) ->
doc("Streamed body with last chunk of size 0."),
{200, _, <<"Hello world!">>} = do_get("/resp/stream_body/fin0", Config),
ok.
stream_body_multiple(Config) ->
doc("Streamed body via multiple calls."),
{200, _, <<"Hello world!">>} = do_get("/resp/stream_body/multiple", Config),
ok.
stream_body_nofin(Config) ->
doc("Unfinished streamed body."),
{200, _, <<"Hello world!">>} = do_get("/resp/stream_body/nofin", Config),
ok.
stream_body_sendfile(Config) ->
doc("Streamed body via multiple calls, including sendfile calls."),
{ok, AppFile} = file:read_file(code:where_is_file("cowboy.app")),
ExpectedBody = iolist_to_binary([
<<"Hello ">>,
AppFile,
<<" interspersed ">>,
AppFile,
<<" world!">>
]),
{200, _, ExpectedBody} = do_get("/resp/stream_body/sendfile", Config),
ok.
stream_body_sendfile_fin(Config) ->
doc("Streamed body via multiple calls, including a sendfile final call."),
{ok, AppFile} = file:read_file(code:where_is_file("cowboy.app")),
ExpectedBody = iolist_to_binary([
<<"Hello! ">>,
AppFile
]),
{200, _, ExpectedBody} = do_get("/resp/stream_body/sendfile_fin", Config),
ok.
stream_body_content_length_multiple(Config) ->
doc("Streamed body via multiple calls."),
{200, _, <<"Hello world!">>} = do_get("/resp/stream_body_content_length/multiple", Config),