stream_handler_SUITE.erl 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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, #{
  29. logger => ?MODULE,
  30. stream_handlers => [stream_handler_h]
  31. }, Config);
  32. init_per_group(Name = https, Config) ->
  33. cowboy_test:init_https(Name, #{
  34. logger => ?MODULE,
  35. stream_handlers => [stream_handler_h]
  36. }, Config);
  37. init_per_group(Name = h2, Config) ->
  38. cowboy_test:init_http2(Name, #{
  39. logger => ?MODULE,
  40. stream_handlers => [stream_handler_h]
  41. }, Config);
  42. init_per_group(Name = h2c, Config) ->
  43. Config1 = cowboy_test:init_http(Name, #{
  44. logger => ?MODULE,
  45. stream_handlers => [stream_handler_h]
  46. }, Config),
  47. lists:keyreplace(protocol, 1, Config1, {protocol, http2});
  48. init_per_group(Name = http_compress, Config) ->
  49. cowboy_test:init_http(Name, #{
  50. logger => ?MODULE,
  51. stream_handlers => [cowboy_compress_h, stream_handler_h]
  52. }, Config);
  53. init_per_group(Name = https_compress, Config) ->
  54. cowboy_test:init_https(Name, #{
  55. logger => ?MODULE,
  56. stream_handlers => [cowboy_compress_h, stream_handler_h]
  57. }, Config);
  58. init_per_group(Name = h2_compress, Config) ->
  59. cowboy_test:init_http2(Name, #{
  60. logger => ?MODULE,
  61. stream_handlers => [cowboy_compress_h, stream_handler_h]
  62. }, Config);
  63. init_per_group(Name = h2c_compress, Config) ->
  64. Config1 = cowboy_test:init_http(Name, #{
  65. logger => ?MODULE,
  66. stream_handlers => [cowboy_compress_h, stream_handler_h]
  67. }, Config),
  68. lists:keyreplace(protocol, 1, Config1, {protocol, http2}).
  69. end_per_group(Name, _) ->
  70. cowboy:stop_listener(Name).
  71. %% Logger function silencing the expected crashes.
  72. error("Unhandled exception " ++ _, [error, crash|_]) ->
  73. ok;
  74. error(Format, Args) ->
  75. error_logger:error_msg(Format, Args).
  76. %% Tests.
  77. crash_in_init(Config) ->
  78. doc("Confirm an error is sent when a stream handler crashes in init/3."),
  79. Self = self(),
  80. ConnPid = gun_open(Config),
  81. Ref = gun:get(ConnPid, "/long_polling", [
  82. {<<"accept-encoding">>, <<"gzip">>},
  83. {<<"x-test-case">>, <<"crash_in_init">>},
  84. {<<"x-test-pid">>, pid_to_list(Self)}
  85. ]),
  86. %% Confirm init/3 is called.
  87. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  88. %% Confirm terminate/3 is NOT called. We have no state to give to it.
  89. receive {Self, Pid, terminate, _, _, _} -> error(terminate) after 1000 -> ok end,
  90. %% Receive a 500 error response.
  91. case gun:await(ConnPid, Ref) of
  92. {response, fin, 500, _} -> ok;
  93. {error, {stream_error, 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. %% Receive a 500 error response.
  112. case gun:await(ConnPid, Ref) of
  113. {response, fin, 500, _} -> ok;
  114. {error, {stream_error, internal_error, _}} -> ok
  115. end.
  116. crash_in_info(Config) ->
  117. doc("Confirm an error is sent when a stream handler crashes in info/3."),
  118. Self = self(),
  119. ConnPid = gun_open(Config),
  120. Ref = gun:get(ConnPid, "/long_polling", [
  121. {<<"accept-encoding">>, <<"gzip">>},
  122. {<<"x-test-case">>, <<"crash_in_info">>},
  123. {<<"x-test-pid">>, pid_to_list(Self)}
  124. ]),
  125. %% Confirm init/3 is called.
  126. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  127. %% Send a message to make the stream handler crash.
  128. Pid ! {{Pid, 1}, crash},
  129. %% Confirm terminate/3 is called, indicating the stream ended.
  130. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  131. %% Receive a 500 error response.
  132. case gun:await(ConnPid, Ref) of
  133. {response, fin, 500, _} -> ok;
  134. {error, {stream_error, internal_error, _}} -> ok
  135. end.
  136. crash_in_terminate(Config) ->
  137. doc("Confirm the state is correct when a stream handler crashes in terminate/3."),
  138. Self = self(),
  139. ConnPid = gun_open(Config),
  140. %% Do a first request.
  141. Ref1 = gun:get(ConnPid, "/hello_world", [
  142. {<<"accept-encoding">>, <<"gzip">>},
  143. {<<"x-test-case">>, <<"crash_in_terminate">>},
  144. {<<"x-test-pid">>, pid_to_list(Self)}
  145. ]),
  146. %% Confirm init/3 is called.
  147. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  148. %% Confirm terminate/3 is called.
  149. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  150. %% Receive the response.
  151. {response, nofin, 200, _} = gun:await(ConnPid, Ref1),
  152. {ok, <<"Hello world!">>} = gun:await_body(ConnPid, Ref1),
  153. %% Do a second request to make sure the connection state is still good.
  154. Ref2 = gun:get(ConnPid, "/hello_world", [
  155. {<<"accept-encoding">>, <<"gzip">>},
  156. {<<"x-test-case">>, <<"crash_in_terminate">>},
  157. {<<"x-test-pid">>, pid_to_list(Self)}
  158. ]),
  159. %% Confirm init/3 is called. The pid shouldn't change.
  160. receive {Self, Pid, init, _, _, _} -> ok after 1000 -> error(timeout) end,
  161. %% Confirm terminate/3 is called.
  162. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  163. %% Receive the second response.
  164. {response, nofin, 200, _} = gun:await(ConnPid, Ref2),
  165. {ok, <<"Hello world!">>} = gun:await_body(ConnPid, Ref2),
  166. ok.
  167. crash_in_early_error(Config) ->
  168. case config(protocol, Config) of
  169. http -> do_crash_in_early_error(Config);
  170. http2 -> doc("The callback early_error/5 is not currently used for HTTP/2.")
  171. end.
  172. do_crash_in_early_error(Config) ->
  173. doc("Confirm an error is sent when a stream handler crashes in early_error/5."
  174. "The connection is kept open by Cowboy."),
  175. Self = self(),
  176. ConnPid = gun_open(Config),
  177. Ref1 = gun:get(ConnPid, "/long_polling", [
  178. {<<"accept-encoding">>, <<"gzip">>},
  179. {<<"x-test-case">>, <<"crash_in_early_error">>},
  180. {<<"x-test-pid">>, pid_to_list(Self)}
  181. ]),
  182. %% Confirm init/3 is called.
  183. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  184. %% Confirm terminate/3 is NOT called. We have no state to give to it.
  185. receive {Self, Pid, terminate, _, _, _} -> error(terminate) after 1000 -> ok end,
  186. %% Confirm early_error/5 is called.
  187. receive {Self, Pid, early_error, _, _, _, _, _} -> ok after 1000 -> error(timeout) end,
  188. %% Receive a 500 error response.
  189. {response, fin, 500, _} = gun:await(ConnPid, Ref1),
  190. %% This error is not fatal. We should be able to repeat it on the same connection.
  191. Ref2 = gun:get(ConnPid, "/long_polling", [
  192. {<<"accept-encoding">>, <<"gzip">>},
  193. {<<"x-test-case">>, <<"crash_in_early_error">>},
  194. {<<"x-test-pid">>, pid_to_list(Self)}
  195. ]),
  196. %% Confirm init/3 is called.
  197. receive {Self, Pid, init, _, _, _} -> ok after 1000 -> error(timeout) end,
  198. %% Confirm terminate/3 is NOT called. We have no state to give to it.
  199. receive {Self, Pid, terminate, _, _, _} -> error(terminate) after 1000 -> ok end,
  200. %% Confirm early_error/5 is called.
  201. receive {Self, Pid, early_error, _, _, _, _, _} -> ok after 1000 -> error(timeout) end,
  202. %% Receive a 500 error response.
  203. {response, fin, 500, _} = gun:await(ConnPid, Ref2),
  204. ok.
  205. crash_in_early_error_fatal(Config) ->
  206. case config(protocol, Config) of
  207. http -> do_crash_in_early_error_fatal(Config);
  208. http2 -> doc("The callback early_error/5 is not currently used for HTTP/2.")
  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. set_options_ignore_unknown(Config) ->
  233. doc("Confirm that unknown options are ignored when using the set_options commands."),
  234. Self = self(),
  235. ConnPid = gun_open(Config),
  236. Ref = gun:get(ConnPid, "/long_polling", [
  237. {<<"accept-encoding">>, <<"gzip">>},
  238. {<<"x-test-case">>, <<"set_options_ignore_unknown">>},
  239. {<<"x-test-pid">>, pid_to_list(Self)}
  240. ]),
  241. %% Confirm init/3 is called.
  242. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  243. %% Confirm terminate/3 is called, indicating the stream ended.
  244. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  245. %% Confirm the response is sent.
  246. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  247. {ok, _} = gun:await_body(ConnPid, Ref),
  248. ok.
  249. shutdown_on_stream_stop(Config) ->
  250. doc("Confirm supervised processes are shutdown when stopping the stream."),
  251. Self = self(),
  252. ConnPid = gun_open(Config),
  253. Ref = gun:get(ConnPid, "/long_polling", [
  254. {<<"accept-encoding">>, <<"gzip">>},
  255. {<<"x-test-case">>, <<"shutdown_on_stream_stop">>},
  256. {<<"x-test-pid">>, pid_to_list(Self)}
  257. ]),
  258. %% Confirm init/3 is called.
  259. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  260. %% Receive the pid of the newly started process and monitor it.
  261. Spawn = receive {Self, Pid, spawned, S} -> S after 1000 -> error(timeout) end,
  262. MRef = monitor(process, Spawn),
  263. Spawn ! {Self, ready},
  264. %% Confirm terminate/3 is called, indicating the stream ended.
  265. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  266. %% We should receive a DOWN message soon after (or before) because the stream
  267. %% handler is stopping the stream immediately after the process started.
  268. receive {'DOWN', MRef, process, Spawn, shutdown} -> ok after 1000 -> error(timeout) end,
  269. %% The response is still sent.
  270. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  271. {ok, _} = gun:await_body(ConnPid, Ref),
  272. ok.
  273. shutdown_on_socket_close(Config) ->
  274. doc("Confirm supervised processes are shutdown when the socket closes."),
  275. Self = self(),
  276. ConnPid = gun_open(Config),
  277. _ = gun:get(ConnPid, "/long_polling", [
  278. {<<"accept-encoding">>, <<"gzip">>},
  279. {<<"x-test-case">>, <<"shutdown_on_socket_close">>},
  280. {<<"x-test-pid">>, pid_to_list(Self)}
  281. ]),
  282. %% Confirm init/3 is called.
  283. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  284. %% Receive the pid of the newly started process and monitor it.
  285. Spawn = receive {Self, Pid, spawned, S} -> S after 1000 -> error(timeout) end,
  286. MRef = monitor(process, Spawn),
  287. Spawn ! {Self, ready},
  288. %% Close the socket.
  289. ok = gun:close(ConnPid),
  290. %% Confirm terminate/3 is called, indicating the stream ended.
  291. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  292. %% Confirm we receive a DOWN message for the child process.
  293. receive {'DOWN', MRef, process, Spawn, shutdown} -> ok after 1000 -> error(timeout) end,
  294. ok.
  295. shutdown_timeout_on_stream_stop(Config) ->
  296. doc("Confirm supervised processes are killed "
  297. "when the shutdown timeout triggers after stopping the stream."),
  298. Self = self(),
  299. ConnPid = gun_open(Config),
  300. Ref = gun:get(ConnPid, "/long_polling", [
  301. {<<"accept-encoding">>, <<"gzip">>},
  302. {<<"x-test-case">>, <<"shutdown_timeout_on_stream_stop">>},
  303. {<<"x-test-pid">>, pid_to_list(Self)}
  304. ]),
  305. %% Confirm init/3 is called.
  306. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  307. %% Receive the pid of the newly started process and monitor it.
  308. Spawn = receive {Self, Pid, spawned, S} -> S after 1000 -> error(timeout) end,
  309. MRef = monitor(process, Spawn),
  310. Spawn ! {Self, ready},
  311. %% Confirm terminate/3 is called, indicating the stream ended.
  312. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  313. %% We should NOT receive a DOWN message immediately.
  314. receive {'DOWN', MRef, process, Spawn, killed} -> error(killed) after 1500 -> ok end,
  315. %% We should received it now.
  316. receive {'DOWN', MRef, process, Spawn, killed} -> ok after 1000 -> error(timeout) end,
  317. %% The response is still sent.
  318. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  319. {ok, _} = gun:await_body(ConnPid, Ref),
  320. ok.
  321. shutdown_timeout_on_socket_close(Config) ->
  322. doc("Confirm supervised processes are killed "
  323. "when the shutdown timeout triggers after the socket has closed."),
  324. Self = self(),
  325. ConnPid = gun_open(Config),
  326. _ = gun:get(ConnPid, "/long_polling", [
  327. {<<"accept-encoding">>, <<"gzip">>},
  328. {<<"x-test-case">>, <<"shutdown_timeout_on_socket_close">>},
  329. {<<"x-test-pid">>, pid_to_list(Self)}
  330. ]),
  331. %% Confirm init/3 is called.
  332. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  333. %% Receive the pid of the newly started process and monitor it.
  334. Spawn = receive {Self, Pid, spawned, S} -> S after 1000 -> error(timeout) end,
  335. MRef = monitor(process, Spawn),
  336. Spawn ! {Self, ready},
  337. %% Close the socket.
  338. ok = gun:close(ConnPid),
  339. %% Confirm terminate/3 is called, indicating the stream ended.
  340. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  341. %% We should NOT receive a DOWN message immediately.
  342. receive {'DOWN', MRef, process, Spawn, killed} -> error(killed) after 1500 -> ok end,
  343. %% We should received it now.
  344. receive {'DOWN', MRef, process, Spawn, killed} -> ok after 1000 -> error(timeout) end,
  345. ok.
  346. switch_protocol_after_headers(Config) ->
  347. case config(protocol, Config) of
  348. http -> do_switch_protocol_after_response(
  349. <<"switch_protocol_after_headers">>, Config);
  350. http2 -> doc("The switch_protocol command is not currently supported for HTTP/2.")
  351. end.
  352. switch_protocol_after_headers_data(Config) ->
  353. case config(protocol, Config) of
  354. http -> do_switch_protocol_after_response(
  355. <<"switch_protocol_after_headers_data">>, Config);
  356. http2 -> doc("The switch_protocol command is not currently supported for HTTP/2.")
  357. end.
  358. switch_protocol_after_response(Config) ->
  359. case config(protocol, Config) of
  360. http -> do_switch_protocol_after_response(
  361. <<"switch_protocol_after_response">>, Config);
  362. http2 -> doc("The switch_protocol command is not currently supported for HTTP/2.")
  363. end.
  364. do_switch_protocol_after_response(TestCase, Config) ->
  365. doc("The 101 informational response must not be sent when a response "
  366. "has already been sent before the switch_protocol is returned."),
  367. Self = self(),
  368. ConnPid = gun_open(Config),
  369. Ref = gun:get(ConnPid, "/long_polling", [
  370. {<<"accept-encoding">>, <<"gzip">>},
  371. {<<"x-test-case">>, TestCase},
  372. {<<"x-test-pid">>, pid_to_list(Self)}
  373. ]),
  374. %% Confirm init/3 is called and receive the response.
  375. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  376. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  377. Gzipped =
  378. lists:keyfind(<<"content-encoding">>, 1, Headers)
  379. =:= {<<"content-encoding">>, <<"gzip">>},
  380. case TestCase of
  381. <<"switch_protocol_after_headers">> ->
  382. ok;
  383. _ ->
  384. <<"{}">> = case gun:await_body(ConnPid, Ref) of
  385. {ok, Body} when Gzipped ->
  386. zlib:gunzip(Body);
  387. {ok, Body} ->
  388. Body
  389. end,
  390. ok
  391. end,
  392. {error, _} = gun:await(ConnPid, Ref),
  393. %% Confirm terminate/3 is called.
  394. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  395. %% Confirm takeover/7 is called.
  396. receive {Self, Pid, takeover, _, _, _, _, _, _, _} -> ok after 1000 -> error(timeout) end,
  397. ok.
  398. terminate_on_socket_close(Config) ->
  399. doc("Confirm terminate/3 is called when the socket gets closed brutally."),
  400. Self = self(),
  401. ConnPid = gun_open(Config),
  402. Ref = gun:get(ConnPid, "/long_polling", [
  403. {<<"accept-encoding">>, <<"gzip">>},
  404. {<<"x-test-case">>, <<"terminate_on_socket_close">>},
  405. {<<"x-test-pid">>, pid_to_list(Self)}
  406. ]),
  407. %% Confirm init/3 is called and receive the beginning of the response.
  408. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  409. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  410. %% Close the socket.
  411. ok = gun:close(ConnPid),
  412. %% Confirm terminate/3 is called.
  413. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  414. ok.
  415. terminate_on_stop(Config) ->
  416. doc("Confirm terminate/3 is called after stop is returned."),
  417. Self = self(),
  418. ConnPid = gun_open(Config),
  419. Ref = gun:get(ConnPid, "/long_polling", [
  420. {<<"accept-encoding">>, <<"gzip">>},
  421. {<<"x-test-case">>, <<"terminate_on_stop">>},
  422. {<<"x-test-pid">>, pid_to_list(Self)}
  423. ]),
  424. %% Confirm init/3 is called and receive the response.
  425. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  426. {response, fin, 204, _} = gun:await(ConnPid, Ref),
  427. %% Confirm the stream is still alive even though we
  428. %% received the response fully, and tell it to stop.
  429. Pid ! {{Pid, 1}, please_stop},
  430. receive {Self, Pid, info, _, please_stop, _} -> ok after 1000 -> error(timeout) end,
  431. %% Confirm terminate/3 is called.
  432. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  433. ok.
  434. terminate_on_switch_protocol(Config) ->
  435. case config(protocol, Config) of
  436. http -> do_terminate_on_switch_protocol(Config);
  437. http2 -> doc("The switch_protocol command is not currently supported for HTTP/2.")
  438. end.
  439. do_terminate_on_switch_protocol(Config) ->
  440. doc("Confirm terminate/3 is called after switch_protocol is returned."),
  441. Self = self(),
  442. ConnPid = gun_open(Config),
  443. Ref = gun:get(ConnPid, "/long_polling", [
  444. {<<"accept-encoding">>, <<"gzip">>},
  445. {<<"x-test-case">>, <<"terminate_on_switch_protocol">>},
  446. {<<"x-test-pid">>, pid_to_list(Self)}
  447. ]),
  448. %% Confirm init/3 is called and receive the response.
  449. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,
  450. {inform, 101, _} = gun:await(ConnPid, Ref),
  451. %% Confirm terminate/3 is called.
  452. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,
  453. %% Confirm takeover/7 is called.
  454. receive {Self, Pid, takeover, _, _, _, _, _, _, _} -> ok after 1000 -> error(timeout) end,
  455. ok.