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:
parent
29043aa7b4
commit
d7b7580b39
12 changed files with 219 additions and 100 deletions
|
@ -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",
|
||||
|
|
|
@ -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}.
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue