stream_handler_SUITE.erl 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. %% Copyright (c) 2017, 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(stream_handler_SUITE).
  15. -compile(export_all).
  16. -compile(nowarn_export_all).
  17. -import(ct_helper, [config/2]).
  18. -import(ct_helper, [doc/1]).
  19. -import(cowboy_test, [gun_open/1]).
  20. -import(cowboy_test, [gun_down/1]).
  21. %% ct.
  22. all() ->
  23. cowboy_test:common_all().
  24. groups() ->
  25. cowboy_test:common_groups(ct_helper:all(?MODULE)).
  26. %% We set this module as a logger in order to silence expected errors.
  27. init_per_group(Name = http, Config) ->
  28. cowboy_test:init_http(Name, init_plain_opts(), Config);
  29. init_per_group(Name = https, Config) ->
  30. cowboy_test:init_https(Name, init_plain_opts(), Config);
  31. init_per_group(Name = h2, Config) ->
  32. cowboy_test:init_http2(Name, init_plain_opts(), Config);
  33. init_per_group(Name = h2c, Config) ->
  34. Config1 = cowboy_test:init_http(Name, init_plain_opts(), Config),
  35. lists:keyreplace(protocol, 1, Config1, {protocol, http2});
  36. init_per_group(Name = h3, Config) ->
  37. cowboy_test:init_http3(Name, init_plain_opts(), Config);
  38. init_per_group(Name = http_compress, Config) ->
  39. cowboy_test:init_http(Name, init_compress_opts(), Config);
  40. init_per_group(Name = https_compress, Config) ->
  41. cowboy_test:init_https(Name, init_compress_opts(), Config);
  42. init_per_group(Name = h2_compress, Config) ->
  43. cowboy_test:init_http2(Name, init_compress_opts(), Config);
  44. init_per_group(Name = h2c_compress, Config) ->
  45. Config1 = cowboy_test:init_http(Name, init_compress_opts(), Config),
  46. lists:keyreplace(protocol, 1, Config1, {protocol, http2});
  47. init_per_group(Name = h3_compress, Config) ->
  48. cowboy_test:init_http3(Name, init_compress_opts(), Config).
  49. end_per_group(Name, _) ->
  50. cowboy_test:stop_group(Name).
  51. init_plain_opts() ->
  52. #{
  53. logger => ?MODULE,
  54. stream_handlers => [stream_handler_h]
  55. }.
  56. init_compress_opts() ->
  57. #{
  58. logger => ?MODULE,
  59. stream_handlers => [cowboy_compress_h, stream_handler_h]
  60. }.
  61. %% Logger function silencing the expected crashes.
  62. error("Unhandled exception " ++ _, [error, crash|_]) ->
  63. ok;
  64. error(Format, Args) ->
  65. error_logger:error_msg(Format, Args).
  66. %% Tests.
  67. crash_in_init(Config) ->
  68. doc("Confirm an error is sent when a stream handler crashes in init/3."),
  69. Self = self(),
  70. ConnPid = gun_open(Config),
  71. Ref = gun:get(ConnPid, "/long_polling", [
  72. {<<"accept-encoding">>, <<"gzip">>},
  73. {<<"x-test-case">>, <<"crash_in_init">>},
  74. {<<"x-test-pid">>, pid_to_list(Self)}
  75. ]),
  76. %% Confirm init/3 is called.
  77. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  78. %% Confirm terminate/3 is NOT called. We have no state to give to it.
  79. receive {Self, Pid, terminate, _, _, _} -> error(terminate) after 1000 -> ok end,
  80. %% Confirm early_error/5 is called in HTTP/1.1's case.
  81. %% HTTP/2 and HTTP/3 do not send a response back so there is no early_error call.
  82. case config(protocol, Config) of
  83. http -> receive {Self, Pid, early_error, _, _, _, _, _} -> ok after 1000 -> error(timeout) end;
  84. http2 -> ok;
  85. http3 -> ok
  86. end,
  87. do_await_internal_error(ConnPid, Ref, Config).
  88. do_await_internal_error(ConnPid, Ref, Config) ->
  89. Protocol = config(protocol, Config),
  90. case {Protocol, gun:await(ConnPid, Ref)} of
  91. {http, {response, fin, 500, _}} -> ok;
  92. {http2, {error, {stream_error, {stream_error, internal_error, _}}}} -> ok;
  93. {http3, {error, {stream_error, {stream_error, h3_internal_error, _}}}} -> ok
  94. end.
  95. crash_in_data(Config) ->
  96. doc("Confirm an error is sent when a stream handler crashes in data/4."),
  97. Self = self(),
  98. ConnPid = gun_open(Config),
  99. Ref = gun:post(ConnPid, "/long_polling", [
  100. {<<"accept-encoding">>, <<"gzip">>},
  101. {<<"content-length">>, <<"6">>},
  102. {<<"x-test-case">>, <<"crash_in_data">>},
  103. {<<"x-test-pid">>, pid_to_list(Self)}
  104. ]),
  105. %% Confirm init/3 is called.
  106. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  107. %% Send data to make the stream handler crash.
  108. gun:data(ConnPid, Ref, fin, <<"Hello!">>),
  109. %% Confirm terminate/3 is called, indicating the stream ended.
  110. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  111. do_await_internal_error(ConnPid, Ref, Config).
  112. crash_in_info(Config) ->
  113. doc("Confirm an error is sent when a stream handler crashes in info/3."),
  114. Self = self(),
  115. ConnPid = gun_open(Config),
  116. Ref = gun:get(ConnPid, "/long_polling", [
  117. {<<"accept-encoding">>, <<"gzip">>},
  118. {<<"x-test-case">>, <<"crash_in_info">>},
  119. {<<"x-test-pid">>, pid_to_list(Self)}
  120. ]),
  121. %% Confirm init/3 is called.
  122. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  123. %% Send a message to make the stream handler crash.
  124. StreamID = case config(protocol, Config) of
  125. http3 -> 0;
  126. _ -> 1
  127. end,
  128. Pid ! {{Pid, StreamID}, crash},
  129. %% Confirm terminate/3 is called, indicating the stream ended.
  130. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  131. do_await_internal_error(ConnPid, Ref, Config).
  132. crash_in_terminate(Config) ->
  133. doc("Confirm the state is correct when a stream handler crashes in terminate/3."),
  134. Self = self(),
  135. ConnPid = gun_open(Config),
  136. %% Do a first request.
  137. Ref1 = gun:get(ConnPid, "/hello_world", [
  138. {<<"accept-encoding">>, <<"gzip">>},
  139. {<<"x-test-case">>, <<"crash_in_terminate">>},
  140. {<<"x-test-pid">>, pid_to_list(Self)}
  141. ]),
  142. %% Confirm init/3 is called.
  143. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  144. %% Confirm terminate/3 is called.
  145. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  146. %% Receive the response.
  147. {response, nofin, 200, _} = gun:await(ConnPid, Ref1),
  148. {ok, <<"Hello world!">>} = gun:await_body(ConnPid, Ref1),
  149. %% Do a second request to make sure the connection state is still good.
  150. Ref2 = gun:get(ConnPid, "/hello_world", [
  151. {<<"accept-encoding">>, <<"gzip">>},
  152. {<<"x-test-case">>, <<"crash_in_terminate">>},
  153. {<<"x-test-pid">>, pid_to_list(Self)}
  154. ]),
  155. %% Confirm init/3 is called. The pid shouldn't change.
  156. receive {Self, Pid, init, _, _, _} -> ok after 1000 -> error(timeout) end,
  157. %% Confirm terminate/3 is called.
  158. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  159. %% Receive the second response.
  160. {response, nofin, 200, _} = gun:await(ConnPid, Ref2),
  161. {ok, <<"Hello world!">>} = gun:await_body(ConnPid, Ref2),
  162. ok.
  163. %% @todo The callbacks ARE used for HTTP/2 and HTTP/3 CONNECT/TRACE requests.
  164. crash_in_early_error(Config) ->
  165. case config(protocol, Config) of
  166. http -> do_crash_in_early_error(Config);
  167. http2 -> doc("The callback early_error/5 is not currently used for HTTP/2.");
  168. http3 -> doc("The callback early_error/5 is not currently used for HTTP/3.")
  169. end.
  170. do_crash_in_early_error(Config) ->
  171. doc("Confirm an error is sent when a stream handler crashes in early_error/5."
  172. "The connection is kept open by Cowboy."),
  173. Self = self(),
  174. ConnPid = gun_open(Config),
  175. Ref1 = gun:get(ConnPid, "/long_polling", [
  176. {<<"accept-encoding">>, <<"gzip">>},
  177. {<<"x-test-case">>, <<"crash_in_early_error">>},
  178. {<<"x-test-pid">>, pid_to_list(Self)}
  179. ]),
  180. %% Confirm init/3 is called.
  181. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  182. %% Confirm terminate/3 is NOT called. We have no state to give to it.
  183. receive {Self, Pid, terminate, _, _, _} -> error(terminate) after 1000 -> ok end,
  184. %% Confirm early_error/5 is called.
  185. receive {Self, Pid, early_error, _, _, _, _, _} -> ok after 1000 -> error(timeout) end,
  186. %% Receive a 500 error response.
  187. {response, fin, 500, _} = gun:await(ConnPid, Ref1),
  188. %% This error is not fatal. We should be able to repeat it on the same connection.
  189. Ref2 = gun:get(ConnPid, "/long_polling", [
  190. {<<"accept-encoding">>, <<"gzip">>},
  191. {<<"x-test-case">>, <<"crash_in_early_error">>},
  192. {<<"x-test-pid">>, pid_to_list(Self)}
  193. ]),
  194. %% Confirm init/3 is called.
  195. receive {Self, Pid, init, _, _, _} -> ok after 1000 -> error(timeout) end,
  196. %% Confirm terminate/3 is NOT called. We have no state to give to it.
  197. receive {Self, Pid, terminate, _, _, _} -> error(terminate) after 1000 -> ok end,
  198. %% Confirm early_error/5 is called.
  199. receive {Self, Pid, early_error, _, _, _, _, _} -> ok after 1000 -> error(timeout) end,
  200. %% Receive a 500 error response.
  201. {response, fin, 500, _} = gun:await(ConnPid, Ref2),
  202. ok.
  203. %% @todo The callbacks ARE used for HTTP/2 and HTTP/3 CONNECT/TRACE requests.
  204. crash_in_early_error_fatal(Config) ->
  205. case config(protocol, Config) of
  206. http -> do_crash_in_early_error_fatal(Config);
  207. http2 -> doc("The callback early_error/5 is not currently used for HTTP/2.");
  208. http3 -> doc("The callback early_error/5 is not currently used for HTTP/3.")
  209. end.
  210. do_crash_in_early_error_fatal(Config) ->
  211. doc("Confirm an error is sent when a stream handler crashes in early_error/5."
  212. "The error was fatal and the connection is closed by Cowboy."),
  213. Self = self(),
  214. ConnPid = gun_open(Config),
  215. Ref = gun:get(ConnPid, "/long_polling", [
  216. {<<"accept-encoding">>, <<"gzip">>},
  217. {<<"host">>, <<"host:port">>},
  218. {<<"x-test-case">>, <<"crash_in_early_error_fatal">>},
  219. {<<"x-test-pid">>, pid_to_list(Self)}
  220. ]),
  221. %% Confirm init/3 is NOT called. The error occurs before we reach this step.
  222. receive {Self, _, init, _, _, _} -> error(init) after 1000 -> ok end,
  223. %% Confirm terminate/3 is NOT called. We have no state to give to it.
  224. receive {Self, _, terminate, _, _, _} -> error(terminate) after 1000 -> ok end,
  225. %% Confirm early_error/5 is called.
  226. receive {Self, _, early_error, _, _, _, _, _} -> ok after 1000 -> error(timeout) end,
  227. %% Receive a 400 error response. We do not send a 500 when
  228. %% early_error/5 crashes, we send the original error.
  229. {response, fin, 400, _} = gun:await(ConnPid, Ref),
  230. %% Confirm the connection gets closed.
  231. gun_down(ConnPid).
  232. early_error_stream_error_reason(Config) ->
  233. doc("Confirm that the stream_error given to early_error/5 is consistent between protocols."),
  234. Self = self(),
  235. ConnPid = gun_open(Config),
  236. %% We must use different solutions to hit early_error with a stream_error
  237. %% reason in both protocols.
  238. {Method, Headers, Status, Error} = case config(protocol, Config) of
  239. http -> {<<"GET">>, [{<<"host">>, <<"host:port">>}], 400, protocol_error};
  240. http2 -> {<<"TRACE">>, [], 501, no_error};
  241. http3 -> {<<"TRACE">>, [], 501, h3_no_error}
  242. end,
  243. Ref = gun:request(ConnPid, Method, "/long_polling", [
  244. {<<"accept-encoding">>, <<"gzip">>},
  245. {<<"x-test-case">>, <<"early_error_stream_error_reason">>},
  246. {<<"x-test-pid">>, pid_to_list(Self)}
  247. |Headers], <<>>),
  248. %% Confirm init/3 is NOT called. The error occurs before we reach this step.
  249. receive {Self, _, init, _, _, _} -> error(init) after 1000 -> ok end,
  250. %% Confirm terminate/3 is NOT called. We have no state to give to it.
  251. receive {Self, _, terminate, _, _, _} -> error(terminate) after 1000 -> ok end,
  252. %% Confirm early_error/5 is called.
  253. Reason = receive {Self, _, early_error, _, R, _, _, _} -> R after 1000 -> error(timeout) end,
  254. %% Confirm that the Reason is a {stream_error, Reason, Human}.
  255. {stream_error, Error, HumanReadable} = Reason,
  256. true = is_atom(HumanReadable),
  257. %% Receive a 400 or 501 error response.
  258. {response, fin, Status, _} = gun:await(ConnPid, Ref),
  259. ok.
  260. flow_after_body_fully_read(Config) ->
  261. doc("A flow command may be returned even after the body was read fully."),
  262. Self = self(),
  263. ConnPid = gun_open(Config),
  264. Ref = gun:post(ConnPid, "/long_polling", [
  265. {<<"x-test-case">>, <<"flow_after_body_fully_read">>},
  266. {<<"x-test-pid">>, pid_to_list(Self)}
  267. ], <<"Hello world!">>),
  268. %% Receive a 200 response, sent after the second flow command,
  269. %% confirming that the flow command was accepted.
  270. {response, _, 200, _} = gun:await(ConnPid, Ref),
  271. ok.
  272. set_options_ignore_unknown(Config) ->
  273. doc("Confirm that unknown options are ignored when using the set_options commands."),
  274. Self = self(),
  275. ConnPid = gun_open(Config),
  276. Ref = gun:get(ConnPid, "/long_polling", [
  277. {<<"accept-encoding">>, <<"gzip">>},
  278. {<<"x-test-case">>, <<"set_options_ignore_unknown">>},
  279. {<<"x-test-pid">>, pid_to_list(Self)}
  280. ]),
  281. %% Confirm init/3 is called.
  282. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  283. %% Confirm terminate/3 is called, indicating the stream ended.
  284. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  285. %% Confirm the response is sent.
  286. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  287. {ok, _} = gun:await_body(ConnPid, Ref),
  288. ok.
  289. shutdown_on_stream_stop(Config) ->
  290. doc("Confirm supervised processes are shutdown when stopping the stream."),
  291. Self = self(),
  292. ConnPid = gun_open(Config),
  293. Ref = gun:get(ConnPid, "/long_polling", [
  294. {<<"accept-encoding">>, <<"gzip">>},
  295. {<<"x-test-case">>, <<"shutdown_on_stream_stop">>},
  296. {<<"x-test-pid">>, pid_to_list(Self)}
  297. ]),
  298. %% Confirm init/3 is called.
  299. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  300. %% Receive the pid of the newly started process and monitor it.
  301. Spawn = receive {Self, Pid, spawned, S} -> S after 1000 -> error(timeout) end,
  302. MRef = monitor(process, Spawn),
  303. Spawn ! {Self, ready},
  304. %% Confirm terminate/3 is called, indicating the stream ended.
  305. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  306. %% We should receive a DOWN message soon after (or before) because the stream
  307. %% handler is stopping the stream immediately after the process started.
  308. receive {'DOWN', MRef, process, Spawn, shutdown} -> ok after 1000 -> error(timeout) end,
  309. %% The response is still sent.
  310. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  311. {ok, _} = gun:await_body(ConnPid, Ref),
  312. ok.
  313. shutdown_on_socket_close(Config) ->
  314. doc("Confirm supervised processes are shutdown when the socket closes."),
  315. Self = self(),
  316. ConnPid = gun_open(Config),
  317. _ = gun:get(ConnPid, "/long_polling", [
  318. {<<"accept-encoding">>, <<"gzip">>},
  319. {<<"x-test-case">>, <<"shutdown_on_socket_close">>},
  320. {<<"x-test-pid">>, pid_to_list(Self)}
  321. ]),
  322. %% Confirm init/3 is called.
  323. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  324. %% Receive the pid of the newly started process and monitor it.
  325. Spawn = receive {Self, Pid, spawned, S} -> S after 1000 -> error(timeout) end,
  326. MRef = monitor(process, Spawn),
  327. Spawn ! {Self, ready},
  328. %% Close the socket.
  329. ok = gun:close(ConnPid),
  330. %% Confirm terminate/3 is called, indicating the stream ended.
  331. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  332. %% Confirm we receive a DOWN message for the child process.
  333. receive {'DOWN', MRef, process, Spawn, shutdown} -> ok after 1000 -> error(timeout) end,
  334. ok.
  335. shutdown_timeout_on_stream_stop(Config) ->
  336. doc("Confirm supervised processes are killed "
  337. "when the shutdown timeout triggers after stopping the stream."),
  338. Self = self(),
  339. ConnPid = gun_open(Config),
  340. Ref = gun:get(ConnPid, "/long_polling", [
  341. {<<"accept-encoding">>, <<"gzip">>},
  342. {<<"x-test-case">>, <<"shutdown_timeout_on_stream_stop">>},
  343. {<<"x-test-pid">>, pid_to_list(Self)}
  344. ]),
  345. %% Confirm init/3 is called.
  346. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  347. %% Receive the pid of the newly started process and monitor it.
  348. Spawn = receive {Self, Pid, spawned, S} -> S after 1000 -> error(timeout) end,
  349. MRef = monitor(process, Spawn),
  350. Spawn ! {Self, ready},
  351. %% Confirm terminate/3 is called, indicating the stream ended.
  352. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  353. %% We should NOT receive a DOWN message immediately.
  354. receive {'DOWN', MRef, process, Spawn, killed} -> error(killed) after 1500 -> ok end,
  355. %% We should received it now.
  356. receive {'DOWN', MRef, process, Spawn, killed} -> ok after 1000 -> error(timeout) end,
  357. %% The response is still sent.
  358. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  359. {ok, _} = gun:await_body(ConnPid, Ref),
  360. ok.
  361. shutdown_timeout_on_socket_close(Config) ->
  362. doc("Confirm supervised processes are killed "
  363. "when the shutdown timeout triggers after the socket has closed."),
  364. Self = self(),
  365. ConnPid = gun_open(Config),
  366. _ = gun:get(ConnPid, "/long_polling", [
  367. {<<"accept-encoding">>, <<"gzip">>},
  368. {<<"x-test-case">>, <<"shutdown_timeout_on_socket_close">>},
  369. {<<"x-test-pid">>, pid_to_list(Self)}
  370. ]),
  371. %% Confirm init/3 is called.
  372. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  373. %% Receive the pid of the newly started process and monitor it.
  374. Spawn = receive {Self, Pid, spawned, S} -> S after 1000 -> error(timeout) end,
  375. MRef = monitor(process, Spawn),
  376. Spawn ! {Self, ready},
  377. %% Close the socket.
  378. ok = gun:close(ConnPid),
  379. %% Confirm terminate/3 is called, indicating the stream ended.
  380. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  381. %% We should NOT receive a DOWN message immediately.
  382. receive {'DOWN', MRef, process, Spawn, killed} -> error(killed) after 1500 -> ok end,
  383. %% We should received it now.
  384. receive {'DOWN', MRef, process, Spawn, killed} -> ok after 1000 -> error(timeout) end,
  385. ok.
  386. switch_protocol_after_headers(Config) ->
  387. case config(protocol, Config) of
  388. http -> do_switch_protocol_after_response(
  389. <<"switch_protocol_after_headers">>, Config);
  390. http2 -> doc("The switch_protocol command is not currently supported for HTTP/2.");
  391. http3 -> doc("The switch_protocol command is not currently supported for HTTP/3.")
  392. end.
  393. switch_protocol_after_headers_data(Config) ->
  394. case config(protocol, Config) of
  395. http -> do_switch_protocol_after_response(
  396. <<"switch_protocol_after_headers_data">>, Config);
  397. http2 -> doc("The switch_protocol command is not currently supported for HTTP/2.");
  398. http3 -> doc("The switch_protocol command is not currently supported for HTTP/3.")
  399. end.
  400. switch_protocol_after_response(Config) ->
  401. case config(protocol, Config) of
  402. http -> do_switch_protocol_after_response(
  403. <<"switch_protocol_after_response">>, Config);
  404. http2 -> doc("The switch_protocol command is not currently supported for HTTP/2.");
  405. http3 -> doc("The switch_protocol command is not currently supported for HTTP/3.")
  406. end.
  407. do_switch_protocol_after_response(TestCase, Config) ->
  408. doc("The 101 informational response must not be sent when a response "
  409. "has already been sent before the switch_protocol is returned."),
  410. Self = self(),
  411. ConnPid = gun_open(Config),
  412. Ref = gun:get(ConnPid, "/long_polling", [
  413. {<<"accept-encoding">>, <<"gzip">>},
  414. {<<"x-test-case">>, TestCase},
  415. {<<"x-test-pid">>, pid_to_list(Self)}
  416. ]),
  417. %% Confirm init/3 is called and receive the response.
  418. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  419. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  420. Gzipped =
  421. lists:keyfind(<<"content-encoding">>, 1, Headers)
  422. =:= {<<"content-encoding">>, <<"gzip">>},
  423. case TestCase of
  424. <<"switch_protocol_after_headers">> ->
  425. ok;
  426. _ ->
  427. <<"{}">> = case gun:await_body(ConnPid, Ref) of
  428. {ok, Body} when Gzipped ->
  429. zlib:gunzip(Body);
  430. {ok, Body} ->
  431. Body
  432. end,
  433. ok
  434. end,
  435. {error, _} = gun:await(ConnPid, Ref),
  436. %% Confirm terminate/3 is called.
  437. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  438. %% Confirm takeover/7 is called.
  439. receive {Self, Pid, takeover, _, _, _, _, _, _, _} -> ok after 1000 -> error(timeout) end,
  440. ok.
  441. terminate_on_socket_close(Config) ->
  442. doc("Confirm terminate/3 is called when the socket gets closed brutally."),
  443. Self = self(),
  444. ConnPid = gun_open(Config),
  445. Ref = gun:get(ConnPid, "/long_polling", [
  446. {<<"accept-encoding">>, <<"gzip">>},
  447. {<<"x-test-case">>, <<"terminate_on_socket_close">>},
  448. {<<"x-test-pid">>, pid_to_list(Self)}
  449. ]),
  450. %% Confirm init/3 is called and receive the beginning of the response.
  451. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  452. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  453. %% Close the socket.
  454. ok = gun:close(ConnPid),
  455. %% Confirm terminate/3 is called.
  456. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  457. ok.
  458. terminate_on_stop(Config) ->
  459. doc("Confirm terminate/3 is called after stop is returned."),
  460. Self = self(),
  461. ConnPid = gun_open(Config),
  462. Ref = gun:get(ConnPid, "/long_polling", [
  463. {<<"accept-encoding">>, <<"gzip">>},
  464. {<<"x-test-case">>, <<"terminate_on_stop">>},
  465. {<<"x-test-pid">>, pid_to_list(Self)}
  466. ]),
  467. %% Confirm init/3 is called and receive the response.
  468. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  469. {response, fin, 204, _} = gun:await(ConnPid, Ref),
  470. %% Confirm the stream is still alive even though we
  471. %% received the response fully, and tell it to stop.
  472. StreamID = case config(protocol, Config) of
  473. http -> 1;
  474. http2 -> 1;
  475. http3 -> 0
  476. end,
  477. Pid ! {{Pid, StreamID}, please_stop},
  478. receive {Self, Pid, info, _, please_stop, _} -> ok after 1000 -> error(timeout) end,
  479. %% Confirm terminate/3 is called.
  480. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  481. ok.
  482. terminate_on_switch_protocol(Config) ->
  483. case config(protocol, Config) of
  484. http -> do_terminate_on_switch_protocol(Config);
  485. http2 -> doc("The switch_protocol command is not currently supported for HTTP/2.");
  486. http3 -> doc("The switch_protocol command is not currently supported for HTTP/3.")
  487. end.
  488. do_terminate_on_switch_protocol(Config) ->
  489. doc("Confirm terminate/3 is called after switch_protocol is returned."),
  490. Self = self(),
  491. ConnPid = gun_open(Config),
  492. Ref = gun:get(ConnPid, "/long_polling", [
  493. {<<"accept-encoding">>, <<"gzip">>},
  494. {<<"x-test-case">>, <<"terminate_on_switch_protocol">>},
  495. {<<"x-test-pid">>, pid_to_list(Self)}
  496. ]),
  497. %% Confirm init/3 is called and receive the response.
  498. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  499. {inform, 101, _} = gun:await(ConnPid, Ref),
  500. %% Confirm terminate/3 is called.
  501. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  502. %% Confirm takeover/7 is called.
  503. receive {Self, Pid, takeover, _, _, _, _, _, _, _} -> ok after 1000 -> error(timeout) end,
  504. ok.