sendfile_SUITE.erl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. %% Copyright (c) 2013, James Fish <james@fishcakez.com>
  2. %% Copyright (c) 2015-2018, Loïc Hoguin <essen@ninenines.eu>
  3. %%
  4. %% Permission to use, copy, modify, and/or distribute this software for any
  5. %% purpose with or without fee is hereby granted, provided that the above
  6. %% copyright notice and this permission notice appear in all copies.
  7. %%
  8. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  11. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  13. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  14. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. -module(sendfile_SUITE).
  16. -compile(export_all).
  17. -compile(nowarn_export_all).
  18. -import(ct_helper, [config/2]).
  19. -import(ct_helper, [doc/1]).
  20. all() ->
  21. [{group, tcp}, {group, ssl}].
  22. suite() ->
  23. [{timetrap, {seconds, 60}}].
  24. groups() ->
  25. Tests = [
  26. filename,
  27. rawfile,
  28. rawfile_bytes_large,
  29. rawfile_bytes_zero,
  30. rawfile_chunk_size_large,
  31. rawfile_offset_large,
  32. rawfile_range_large,
  33. rawfile_range_medium,
  34. rawfile_range_small
  35. ],
  36. [{tcp, [parallel], Tests}, {ssl, [parallel], Tests ++ [ssl_chunk_size]}].
  37. init_per_suite(Config) ->
  38. Filename = filename:join(config(priv_dir, Config), "sendfile"),
  39. Binary = crypto:strong_rand_bytes(20 * 1024 * 1024),
  40. ok = file:write_file(Filename, Binary),
  41. [{filename, Filename} | Config].
  42. end_per_suite(Config) ->
  43. Filename = config(filename, Config),
  44. ok = file:delete(Filename),
  45. ok.
  46. init_per_group(ssl, Config) ->
  47. SslOpts = ct_helper:get_certs_from_ets(),
  48. [{transport, ranch_ssl}, {transport_opts, SslOpts} | Config];
  49. init_per_group(tcp, Config) ->
  50. [{transport, ranch_tcp}, {transport_opts, []} | Config].
  51. end_per_group(_, _) ->
  52. ok.
  53. filename(Config) ->
  54. doc("Use sendfile with a filename."),
  55. Transport = config(transport, Config),
  56. Filename = config(filename, Config),
  57. {ok, Binary} = file:read_file(Filename),
  58. Size = byte_size(Binary),
  59. {ok, {Server, Client}} = sockets(Config),
  60. Ref = recv(Transport, Server, Size),
  61. {ok, Size} = Transport:sendfile(Client, Filename),
  62. {ok, Binary} = result(Ref),
  63. {error, timeout} = Transport:recv(Server, 1, 100),
  64. ok = Transport:close(Client),
  65. ok = Transport:close(Server).
  66. rawfile(Config) ->
  67. doc("Use sendfile with a file descriptor (raw file)."),
  68. Transport = config(transport, Config),
  69. Filename = config(filename, Config),
  70. {ok, Binary} = file:read_file(Filename),
  71. Size = byte_size(Binary),
  72. {ok, {Server, Client}} = sockets(Config),
  73. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  74. Ref = recv(Transport, Server, Size),
  75. {ok, Size} = Transport:sendfile(Client, RawFile, 0, Size),
  76. {ok, Binary} = result(Ref),
  77. {error, timeout} = Transport:recv(Server, 1, 100),
  78. {ok, 0} = file:position(RawFile, {cur, 0}),
  79. ok = file:close(RawFile),
  80. ok = Transport:close(Client),
  81. ok = Transport:close(Server).
  82. rawfile_bytes_large(Config) ->
  83. doc("Use sendfile with a file descriptor. Try to send a size larger than file size."),
  84. Transport = config(transport, Config),
  85. Filename = config(filename, Config),
  86. {ok, Binary} = file:read_file(Filename),
  87. Size = byte_size(Binary),
  88. {ok, {Server, Client}} = sockets(Config),
  89. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  90. Ref = recv(Transport, Server, Size),
  91. %% Only send Size not Size * 2
  92. {ok, Size} = Transport:sendfile(Client, RawFile, 0, Size * 2),
  93. {ok, Binary} = result(Ref),
  94. {error, timeout} = Transport:recv(Server, 1, 100),
  95. {ok, 0} = file:position(RawFile, {cur, 0}),
  96. ok = file:close(RawFile),
  97. ok = Transport:close(Client),
  98. ok = Transport:close(Server).
  99. rawfile_bytes_zero(Config) ->
  100. doc("Use sendfile with a file descriptor. Ensure using a size of 0 sends the whole file."),
  101. Transport = config(transport, Config),
  102. Filename = config(filename, Config),
  103. {ok, Binary} = file:read_file(Filename),
  104. Size = byte_size(Binary),
  105. {ok, {Server, Client}} = sockets(Config),
  106. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  107. Ref = recv(Transport, Server, Size),
  108. {ok, Size} = Transport:sendfile(Client, RawFile, 0, 0),
  109. {ok, Binary} = result(Ref),
  110. {error, timeout} = Transport:recv(Server, 1, 100),
  111. {ok, 0} = file:position(RawFile, {cur, 0}),
  112. ok = file:close(RawFile),
  113. ok = Transport:close(Client),
  114. ok = Transport:close(Server).
  115. rawfile_chunk_size_large(Config) ->
  116. doc("Use sendfile with a file descriptor. Try to use a chunk size larger than file size."),
  117. Transport = config(transport, Config),
  118. Filename = config(filename, Config),
  119. {ok, Binary} = file:read_file(Filename),
  120. Size = byte_size(Binary),
  121. {ok, {Server, Client}} = sockets(Config),
  122. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  123. Ref = recv(Transport, Server, Size),
  124. {ok, Size} = Transport:sendfile(Client, RawFile, 0, Size, [{chunk_size, Size * 2}]),
  125. {ok, Binary} = result(Ref),
  126. {error, timeout} = Transport:recv(Server, 1, 100),
  127. {ok, 0} = file:position(RawFile, {cur, 0}),
  128. ok = file:close(RawFile),
  129. ok = Transport:close(Client),
  130. ok = Transport:close(Server).
  131. rawfile_offset_large(Config) ->
  132. doc("Use sendfile with a file descriptor. Ensure using an offset larger than file size sends nothing."),
  133. Transport = config(transport, Config),
  134. Filename = config(filename, Config),
  135. {ok, Binary} = file:read_file(Filename),
  136. Size = byte_size(Binary),
  137. {ok, {Server, Client}} = sockets(Config),
  138. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  139. {ok, 0} = Transport:sendfile(Client, RawFile, Size, 1),
  140. {error, timeout} = Transport:recv(Server, 1, 100),
  141. ok = file:close(RawFile),
  142. ok = Transport:close(Client),
  143. ok = Transport:close(Server).
  144. rawfile_range_large(Config) ->
  145. doc("Use sendfile with a file descriptor. "
  146. "Set an offset and try to send a size larger than remaining file size."),
  147. Transport = config(transport, Config),
  148. Filename = config(filename, Config),
  149. {ok, Binary} = file:read_file(Filename),
  150. Size = byte_size(Binary),
  151. {ok, {Server, Client}} = sockets(Config),
  152. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  153. Initial = 499,
  154. {ok, _} = file:position(RawFile, {bof, Initial}),
  155. Offset = 75,
  156. Bytes = Size * 2,
  157. Sent = Size - Offset,
  158. Ref = recv(Transport, Server, Sent),
  159. {ok, Sent} = Transport:sendfile(Client, RawFile, Offset, Bytes),
  160. Binary2 = binary:part(Binary, Offset, Sent),
  161. {ok, Binary2} = result(Ref),
  162. {error, timeout} = Transport:recv(Server, 1, 100),
  163. {ok, Initial} = file:position(RawFile, {cur, 0}),
  164. ok = file:close(RawFile),
  165. ok = Transport:close(Client),
  166. ok = Transport:close(Server).
  167. rawfile_range_medium(Config) ->
  168. doc("Use sendfile with a file descriptor. "
  169. "Set an offset and try to send a size lower than remaining file size."),
  170. Transport = config(transport, Config),
  171. Filename = config(filename, Config),
  172. {ok, Binary} = file:read_file(Filename),
  173. Size = byte_size(Binary),
  174. {ok, {Server, Client}} = sockets(Config),
  175. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  176. Initial = 50,
  177. {ok, _} = file:position(RawFile, {bof, Initial}),
  178. Offset = 50,
  179. Bytes = Size - Offset - 50,
  180. Ref = recv(Transport, Server, Bytes),
  181. {ok, Bytes} = Transport:sendfile(Client, RawFile, Offset, Bytes),
  182. Binary2 = binary:part(Binary, Offset, Bytes),
  183. {ok, Binary2} = result(Ref),
  184. {error, timeout} = Transport:recv(Server, 1, 100),
  185. {ok, Initial} = file:position(RawFile, {cur, 0}),
  186. ok = file:close(RawFile),
  187. ok = Transport:close(Client),
  188. ok = Transport:close(Server).
  189. rawfile_range_small(Config) ->
  190. doc("Use sendfile with a file descriptor. "
  191. "Set an offset and try to send a size lower than remaining file size, "
  192. "which is in turn lower than the chunk size."),
  193. Transport = config(transport, Config),
  194. Filename = config(filename, Config),
  195. {ok, Binary} = file:read_file(Filename),
  196. {ok, {Server, Client}} = sockets(Config),
  197. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  198. Initial = 3,
  199. {ok, _} = file:position(RawFile, {bof, Initial}),
  200. Offset = 7,
  201. Bytes = 19,
  202. Ref = recv(Transport, Server, Bytes),
  203. {ok, Bytes} = Transport:sendfile(Client, RawFile, Offset, Bytes, [{chunk_size, 16#FFFF}]),
  204. Binary2 = binary:part(Binary, Offset, Bytes),
  205. {ok, Binary2} = result(Ref),
  206. {error, timeout} = Transport:recv(Server, 1, 100),
  207. {ok, Initial} = file:position(RawFile, {cur, 0}),
  208. ok = file:close(RawFile),
  209. ok = Transport:close(Client),
  210. ok = Transport:close(Server).
  211. ssl_chunk_size(Config) ->
  212. case code:is_module_native(?MODULE) of
  213. true -> doc("This test uses tracing and is not compatible with native code.");
  214. false -> do_ssl_chunk_size(Config)
  215. end.
  216. do_ssl_chunk_size(Config) ->
  217. doc("Use sendfile with SSL. Ensure the sendfile fallback respects the chunk size."),
  218. Transport = config(transport, Config),
  219. Filename = config(filename, Config),
  220. {ok, Binary} = file:read_file(Filename),
  221. Size = byte_size(Binary),
  222. Self = self(),
  223. ChunkSize = 8 * 1024,
  224. Fun = fun() ->
  225. receive go -> ok after 1000 -> error(timeout) end,
  226. {ok, {Server, Client}} = sockets(Config),
  227. {ok, RawFile} = file:open(Filename, [read, raw, binary]),
  228. Ref = recv(Transport, Server, Size),
  229. {ok, Size} = Transport:sendfile(Client, RawFile, 0, Size, [{chunk_size, ChunkSize}]),
  230. {ok, Binary} = result(Ref),
  231. {error, timeout} = Transport:recv(Server, 1, 100),
  232. Self ! done,
  233. ok = file:close(RawFile),
  234. ok = Transport:close(Client),
  235. ok = Transport:close(Server)
  236. end,
  237. Pid = spawn_link(Fun),
  238. 1 = erlang:trace(Pid, true, [call]),
  239. _ = erlang:trace_pattern({Transport, send, 2}, true, [global]),
  240. Pid ! go,
  241. receive done -> ok after 30000 -> error(timeout) end,
  242. Sizes = lists:duplicate(Size div ChunkSize, ChunkSize) ++
  243. [Size rem ChunkSize || (Size rem ChunkSize) =/= 0],
  244. ok = recv_send_trace(Sizes, Pid),
  245. _ = erlang:trace(all, false, [all]),
  246. ok = clean_traces().
  247. %% Internal.
  248. sockets(Config) ->
  249. Transport = config(transport, Config),
  250. TransportOpts = config(transport_opts, Config),
  251. {ok, LSocket} = Transport:listen(TransportOpts),
  252. {ok, {_, Port}} = Transport:sockname(LSocket),
  253. Self = self(),
  254. Fun = fun() ->
  255. {ok, Client} = Transport:connect("localhost", Port, TransportOpts),
  256. ok = Transport:controlling_process(Client, Self),
  257. Self ! {ok, Client}
  258. end,
  259. _ = spawn_link(Fun),
  260. {ok, Server} = Transport:accept(LSocket, 500),
  261. {ok, _} = Transport:handshake(Server, [], 500),
  262. receive
  263. {ok, Client} ->
  264. ok = Transport:close(LSocket),
  265. {ok, {Server, Client}}
  266. after 1000 ->
  267. {error, timeout}
  268. end.
  269. recv(Transport, Server, Size) ->
  270. Self = self(),
  271. Ref = make_ref(),
  272. spawn_link(fun() -> Self ! {Ref, Transport:recv(Server, Size, 20000)} end),
  273. Ref.
  274. result(Ref) ->
  275. receive
  276. {Ref, Result} ->
  277. Result
  278. after
  279. 30000 ->
  280. {error, result_timedout}
  281. end.
  282. recv_send_trace([], _Pid) ->
  283. ok;
  284. recv_send_trace([Size | Rest], Pid) ->
  285. receive
  286. {trace, Pid, call, {_, _, [_, Chunk]}} when byte_size(Chunk) == Size ->
  287. recv_send_trace(Rest, Pid);
  288. {trace, Pid, call, {_, _, [_, Chunk]}} ->
  289. {error, {invalid_chunk, Size, byte_size(Chunk)}}
  290. after 1000 ->
  291. {error, timeout}
  292. end.
  293. clean_traces() ->
  294. receive
  295. {trace, _, _, _} ->
  296. clean_traces();
  297. {trace, _, _, _, _} ->
  298. clean_traces()
  299. after 0 ->
  300. ok
  301. end.