ranch_transport.erl 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. %% Copyright (c) 2012-2014, 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. -module(ranch_transport).
  15. -export([sendfile/6]).
  16. -type socket() :: any().
  17. -type opts() :: any().
  18. -type sendfile_opts() :: [{chunk_size, non_neg_integer()}].
  19. -export_type([sendfile_opts/0]).
  20. -callback name() -> atom().
  21. %% @todo -callback caps(secure | sendfile) -> boolean().
  22. -callback messages() -> {OK::atom(), Closed::atom(), Error::atom()}.
  23. -callback listen(opts()) -> {ok, socket()} | {error, atom()}.
  24. -callback accept(socket(), timeout())
  25. -> {ok, socket()} | {error, closed | timeout | atom()}.
  26. -callback accept_ack(socket(), timeout()) -> ok.
  27. -callback connect(string(), inet:port_number(), opts())
  28. -> {ok, socket()} | {error, atom()}.
  29. -callback connect(string(), inet:port_number(), opts(), timeout())
  30. -> {ok, socket()} | {error, atom()}.
  31. -callback recv(socket(), non_neg_integer(), timeout())
  32. -> {ok, any()} | {error, closed | timeout | atom()}.
  33. -callback send(socket(), iodata()) -> ok | {error, atom()}.
  34. -callback sendfile(socket(), file:name() | file:fd())
  35. -> {ok, non_neg_integer()} | {error, atom()}.
  36. -callback sendfile(socket(), file:name() | file:fd(), non_neg_integer(),
  37. non_neg_integer()) -> {ok, non_neg_integer()} | {error, atom()}.
  38. -callback sendfile(socket(), file:name() | file:fd(), non_neg_integer(),
  39. non_neg_integer(), sendfile_opts())
  40. -> {ok, non_neg_integer()} | {error, atom()}.
  41. -callback setopts(socket(), opts()) -> ok | {error, atom()}.
  42. -callback controlling_process(socket(), pid())
  43. -> ok | {error, closed | not_owner | atom()}.
  44. -callback peername(socket())
  45. -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
  46. -callback sockname(socket())
  47. -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
  48. -callback shutdown(socket(), read | write | read_write)
  49. -> ok | {error, atom()}.
  50. -callback close(socket()) -> ok.
  51. %% A fallback for transports that don't have a native sendfile implementation.
  52. %% Note that the ordering of arguments is different from file:sendfile/5 and
  53. %% that this function accepts either a raw file or a file name.
  54. -spec sendfile(module(), socket(), file:filename_all() | file:fd(),
  55. non_neg_integer(), non_neg_integer(), sendfile_opts())
  56. -> {ok, non_neg_integer()} | {error, atom()}.
  57. sendfile(Transport, Socket, Filename, Offset, Bytes, Opts)
  58. when is_list(Filename) orelse is_atom(Filename)
  59. orelse is_binary(Filename) ->
  60. ChunkSize = chunk_size(Opts),
  61. case file:open(Filename, [read, raw, binary]) of
  62. {ok, RawFile} ->
  63. _ = case Offset of
  64. 0 ->
  65. ok;
  66. _ ->
  67. {ok, _} = file:position(RawFile, {bof, Offset})
  68. end,
  69. try
  70. sendfile_loop(Transport, Socket, RawFile, Bytes, 0, ChunkSize)
  71. after
  72. ok = file:close(RawFile)
  73. end;
  74. {error, _Reason} = Error ->
  75. Error
  76. end;
  77. sendfile(Transport, Socket, RawFile, Offset, Bytes, Opts) ->
  78. ChunkSize = chunk_size(Opts),
  79. Initial2 = case file:position(RawFile, {cur, 0}) of
  80. {ok, Offset} ->
  81. Offset;
  82. {ok, Initial} ->
  83. {ok, _} = file:position(RawFile, {bof, Offset}),
  84. Initial
  85. end,
  86. case sendfile_loop(Transport, Socket, RawFile, Bytes, 0, ChunkSize) of
  87. {ok, _Sent} = Result ->
  88. {ok, _} = file:position(RawFile, {bof, Initial2}),
  89. Result;
  90. {error, _Reason} = Error ->
  91. Error
  92. end.
  93. -spec chunk_size(sendfile_opts()) -> pos_integer().
  94. chunk_size(Opts) ->
  95. case lists:keyfind(chunk_size, 1, Opts) of
  96. {chunk_size, ChunkSize}
  97. when is_integer(ChunkSize) andalso ChunkSize > 0 ->
  98. ChunkSize;
  99. {chunk_size, 0} ->
  100. 16#1FFF;
  101. false ->
  102. 16#1FFF
  103. end.
  104. -spec sendfile_loop(module(), socket(), file:fd(), non_neg_integer(),
  105. non_neg_integer(), pos_integer())
  106. -> {ok, non_neg_integer()} | {error, term()}.
  107. sendfile_loop(_Transport, _Socket, _RawFile, Sent, Sent, _ChunkSize)
  108. when Sent =/= 0 ->
  109. %% All requested data has been read and sent, return number of bytes sent.
  110. {ok, Sent};
  111. sendfile_loop(Transport, Socket, RawFile, Bytes, Sent, ChunkSize) ->
  112. ReadSize = read_size(Bytes, Sent, ChunkSize),
  113. case file:read(RawFile, ReadSize) of
  114. {ok, IoData} ->
  115. case Transport:send(Socket, IoData) of
  116. ok ->
  117. Sent2 = iolist_size(IoData) + Sent,
  118. sendfile_loop(Transport, Socket, RawFile, Bytes, Sent2,
  119. ChunkSize);
  120. {error, _Reason} = Error ->
  121. Error
  122. end;
  123. eof ->
  124. {ok, Sent};
  125. {error, _Reason} = Error ->
  126. Error
  127. end.
  128. -spec read_size(non_neg_integer(), non_neg_integer(), non_neg_integer()) ->
  129. non_neg_integer().
  130. read_size(0, _Sent, ChunkSize) ->
  131. ChunkSize;
  132. read_size(Bytes, Sent, ChunkSize) ->
  133. min(Bytes - Sent, ChunkSize).