diff options
Diffstat (limited to 'src/ranch_transport.erl')
-rw-r--r-- | src/ranch_transport.erl | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/src/ranch_transport.erl b/src/ranch_transport.erl index 38dca5a..6168b35 100644 --- a/src/ranch_transport.erl +++ b/src/ranch_transport.erl @@ -15,8 +15,12 @@ %% @private -module(ranch_transport). +-export([sendfile/6]). + -type socket() :: any(). -type opts() :: any(). +-type sendfile_opts() :: [{chunk_size, non_neg_integer()}]. +-export_type([sendfile_opts/0]). %% Name of the transport. -callback name() -> atom(). @@ -55,6 +59,19 @@ %% Send data on a socket. -callback send(socket(), iodata()) -> ok | {error, atom()}. +%% Send a file on a socket. +-callback sendfile(socket(), file:name()) + -> {ok, non_neg_integer()} | {error, atom()}. + +%% Send part of a file on a socket. +-callback sendfile(socket(), file:name() | file:fd(), non_neg_integer(), + non_neg_integer()) -> {ok, non_neg_integer()} | {error, atom()}. + +%% Send part of a file on a socket. +-callback sendfile(socket(), file:name() | file:fd(), non_neg_integer(), + non_neg_integer(), sendfile_opts()) + -> {ok, non_neg_integer()} | {error, atom()}. + %% Set options on the given socket. -callback setopts(socket(), opts()) -> ok | {error, atom()}. @@ -75,3 +92,94 @@ %% Close the given socket. -callback close(socket()) -> ok. + +%% @doc Send part of a file on a socket. +%% +%% A fallback for transports that don't have a native sendfile implementation. +%% Note that the ordering of arguments is different from file:sendfile/5 and +%% that this function accepts either a raw file or a file name. +%% +%% @see file:sendfile/5 +-spec sendfile(module(), socket(), file:filename_all() | file:fd(), + non_neg_integer(), non_neg_integer(), sendfile_opts()) + -> {ok, non_neg_integer()} | {error, atom()}. +sendfile(Transport, Socket, Filename, Offset, Bytes, Opts) + when is_list(Filename) orelse is_atom(Filename) + orelse is_binary(Filename) -> + ChunkSize = chunk_size(Opts), + case file:open(Filename, [read, raw, binary]) of + {ok, RawFile} -> + _ = case Offset of + 0 -> + ok; + _ -> + {ok, _} = file:position(RawFile, {bof, Offset}) + end, + try + sendfile_loop(Transport, Socket, RawFile, Bytes, 0, ChunkSize) + after + ok = file:close(RawFile) + end; + {error, _Reason} = Error -> + Error + end; +sendfile(Transport, Socket, RawFile, Offset, Bytes, Opts) -> + ChunkSize = chunk_size(Opts), + Initial2 = case file:position(RawFile, {cur, 0}) of + {ok, Offset} -> + Offset; + {ok, Initial} -> + {ok, _} = file:position(RawFile, {bof, Offset}), + Initial + end, + case sendfile_loop(Transport, Socket, RawFile, Bytes, 0, ChunkSize) of + {ok, _Sent} = Result -> + {ok, _} = file:position(RawFile, {bof, Initial2}), + Result; + {error, _Reason} = Error -> + Error + end. + +-spec chunk_size(sendfile_opts()) -> pos_integer(). +chunk_size(Opts) -> + case lists:keyfind(chunk_size, 1, Opts) of + {chunk_size, ChunkSize} + when is_integer(ChunkSize) andalso ChunkSize > 0 -> + ChunkSize; + {chunk_size, 0} -> + 16#1FFF; + false -> + 16#1FFF + end. + +-spec sendfile_loop(module(), socket(), file:fd(), non_neg_integer(), + non_neg_integer(), pos_integer()) + -> {ok, non_neg_integer()} | {error, term()}. +sendfile_loop(_Transport, _Socket, _RawFile, Sent, Sent, _ChunkSize) + when Sent =/= 0 -> + %% All requested data has been read and sent, return number of bytes sent. + {ok, Sent}; +sendfile_loop(Transport, Socket, RawFile, Bytes, Sent, ChunkSize) -> + ReadSize = read_size(Bytes, Sent, ChunkSize), + case file:read(RawFile, ReadSize) of + {ok, IoData} -> + case Transport:send(Socket, IoData) of + ok -> + Sent2 = iolist_size(IoData) + Sent, + sendfile_loop(Transport, Socket, RawFile, Bytes, Sent2, + ChunkSize); + {error, _Reason} = Error -> + Error + end; + eof -> + {ok, Sent}; + {error, _Reason} = Error -> + Error + end. + +-spec read_size(non_neg_integer(), non_neg_integer(), non_neg_integer()) -> + non_neg_integer(). +read_size(0, _Sent, ChunkSize) -> + ChunkSize; +read_size(Bytes, Sent, ChunkSize) -> + min(Bytes - Sent, ChunkSize). |