ranch_transport.erl (5679B)
1 %% Copyright (c) 2012-2018, Loïc Hoguin <essen@ninenines.eu> 2 %% 3 %% Permission to use, copy, modify, and/or distribute this software for any 4 %% purpose with or without fee is hereby granted, provided that the above 5 %% copyright notice and this permission notice appear in all copies. 6 %% 7 %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15 -module(ranch_transport). 16 17 -export([sendfile/6]). 18 19 -type socket() :: any(). 20 -export_type([socket/0]). 21 22 -type opts() :: any(). 23 -type stats() :: any(). 24 -type sendfile_opts() :: [{chunk_size, non_neg_integer()}]. 25 -export_type([sendfile_opts/0]). 26 27 -callback name() -> atom(). 28 -callback secure() -> boolean(). 29 -callback messages() -> {OK::atom(), Closed::atom(), Error::atom()}. 30 -callback listen(opts()) -> {ok, socket()} | {error, atom()}. 31 -callback accept(socket(), timeout()) 32 -> {ok, socket()} | {error, closed | timeout | atom()}. 33 -callback handshake(socket(), opts(), timeout()) -> {ok, socket()} | {error, any()}. 34 -callback connect(string(), inet:port_number(), opts()) 35 -> {ok, socket()} | {error, atom()}. 36 -callback connect(string(), inet:port_number(), opts(), timeout()) 37 -> {ok, socket()} | {error, atom()}. 38 -callback recv(socket(), non_neg_integer(), timeout()) 39 -> {ok, any()} | {error, closed | timeout | atom()}. 40 -callback recv_proxy_header(socket(), timeout()) 41 -> {ok, ranch_proxy_header:proxy_info()} 42 | {error, closed | atom()} 43 | {error, protocol_error, atom()}. 44 -callback send(socket(), iodata()) -> ok | {error, atom()}. 45 -callback sendfile(socket(), file:name_all() | file:fd()) 46 -> {ok, non_neg_integer()} | {error, atom()}. 47 -callback sendfile(socket(), file:name_all() | file:fd(), non_neg_integer(), 48 non_neg_integer()) -> {ok, non_neg_integer()} | {error, atom()}. 49 -callback sendfile(socket(), file:name_all() | file:fd(), non_neg_integer(), 50 non_neg_integer(), sendfile_opts()) 51 -> {ok, non_neg_integer()} | {error, atom()}. 52 -callback setopts(socket(), opts()) -> ok | {error, atom()}. 53 -callback getopts(socket(), [atom()]) -> {ok, opts()} | {error, atom()}. 54 -callback getstat(socket()) -> {ok, stats()} | {error, atom()}. 55 -callback getstat(socket(), [atom()]) -> {ok, stats()} | {error, atom()}. 56 -callback controlling_process(socket(), pid()) 57 -> ok | {error, closed | not_owner | atom()}. 58 -callback peername(socket()) 59 -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. 60 -callback sockname(socket()) 61 -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. 62 -callback shutdown(socket(), read | write | read_write) 63 -> ok | {error, atom()}. 64 -callback close(socket()) -> ok. 65 66 %% A fallback for transports that don't have a native sendfile implementation. 67 %% Note that the ordering of arguments is different from file:sendfile/5 and 68 %% that this function accepts either a raw file or a file name. 69 -spec sendfile(module(), socket(), file:name_all() | file:fd(), 70 non_neg_integer(), non_neg_integer(), sendfile_opts()) 71 -> {ok, non_neg_integer()} | {error, atom()}. 72 sendfile(Transport, Socket, Filename, Offset, Bytes, Opts) 73 when is_list(Filename) orelse is_atom(Filename) 74 orelse is_binary(Filename) -> 75 ChunkSize = chunk_size(Opts), 76 case file:open(Filename, [read, raw, binary]) of 77 {ok, RawFile} -> 78 _ = case Offset of 79 0 -> 80 ok; 81 _ -> 82 {ok, _} = file:position(RawFile, {bof, Offset}) 83 end, 84 try 85 sendfile_loop(Transport, Socket, RawFile, Bytes, 0, ChunkSize) 86 after 87 ok = file:close(RawFile) 88 end; 89 {error, _Reason} = Error -> 90 Error 91 end; 92 sendfile(Transport, Socket, RawFile, Offset, Bytes, Opts) -> 93 ChunkSize = chunk_size(Opts), 94 Initial2 = case file:position(RawFile, {cur, 0}) of 95 {ok, Offset} -> 96 Offset; 97 {ok, Initial} -> 98 {ok, _} = file:position(RawFile, {bof, Offset}), 99 Initial 100 end, 101 case sendfile_loop(Transport, Socket, RawFile, Bytes, 0, ChunkSize) of 102 {ok, _Sent} = Result -> 103 {ok, _} = file:position(RawFile, {bof, Initial2}), 104 Result; 105 {error, _Reason} = Error -> 106 Error 107 end. 108 109 -spec chunk_size(sendfile_opts()) -> pos_integer(). 110 chunk_size(Opts) -> 111 case lists:keyfind(chunk_size, 1, Opts) of 112 {chunk_size, ChunkSize} 113 when is_integer(ChunkSize) andalso ChunkSize > 0 -> 114 ChunkSize; 115 {chunk_size, 0} -> 116 16#1FFF; 117 false -> 118 16#1FFF 119 end. 120 121 -spec sendfile_loop(module(), socket(), file:fd(), non_neg_integer(), 122 non_neg_integer(), pos_integer()) 123 -> {ok, non_neg_integer()} | {error, any()}. 124 sendfile_loop(_Transport, _Socket, _RawFile, Sent, Sent, _ChunkSize) 125 when Sent =/= 0 -> 126 %% All requested data has been read and sent, return number of bytes sent. 127 {ok, Sent}; 128 sendfile_loop(Transport, Socket, RawFile, Bytes, Sent, ChunkSize) -> 129 ReadSize = read_size(Bytes, Sent, ChunkSize), 130 case file:read(RawFile, ReadSize) of 131 {ok, IoData} -> 132 case Transport:send(Socket, IoData) of 133 ok -> 134 Sent2 = iolist_size(IoData) + Sent, 135 sendfile_loop(Transport, Socket, RawFile, Bytes, Sent2, 136 ChunkSize); 137 {error, _Reason} = Error -> 138 Error 139 end; 140 eof -> 141 {ok, Sent}; 142 {error, _Reason} = Error -> 143 Error 144 end. 145 146 -spec read_size(non_neg_integer(), non_neg_integer(), non_neg_integer()) -> 147 non_neg_integer(). 148 read_size(0, _Sent, ChunkSize) -> 149 ChunkSize; 150 read_size(Bytes, Sent, ChunkSize) -> 151 min(Bytes - Sent, ChunkSize).