@@ -74,7 +74,8 @@ init_dispatch(Name) ->
7474 {" /active" , ws_active_commands_h , InitialState },
7575 {" /deflate" , ws_deflate_commands_h , InitialState },
7676 {" /set_options" , ws_set_options_commands_h , InitialState },
77- {" /shutdown_reason" , ws_shutdown_reason_commands_h , InitialState }
77+ {" /shutdown_reason" , ws_shutdown_reason_commands_h , InitialState },
78+ {" /terminate" , ws_terminate_h , InitialState }
7879 ]}]).
7980
8081% % Support functions for testing using Gun.
@@ -116,6 +117,15 @@ ensure_handle_is_called(ConnPid, StreamRef, "/handle") ->
116117ensure_handle_is_called (_ , _ , _ ) ->
117118 ok .
118119
120+ do_receive (Tag ) ->
121+ receive
122+ Msg when element (1 , Msg ) =:= Tag ->
123+ Msg
124+ after 1000 ->
125+ ct :pal (" do_receive(~p ): ~p " , [Tag , process_info (self (), messages )]),
126+ error (timeout )
127+ end .
128+
119129% % Tests.
120130
121131websocket_init_nothing (Config ) ->
@@ -134,7 +144,7 @@ do_nothing(Config, Path) ->
134144 {ok , ConnPid , StreamRef } = gun_open_ws (Config , Path , []),
135145 ensure_handle_is_called (ConnPid , StreamRef , Path ),
136146 {error , timeout } = receive_ws (ConnPid , StreamRef ),
137- ok .
147+ gun : close ( ConnPid ) .
138148
139149websocket_init_invalid (Config ) ->
140150 doc (" The connection must be closed when websocket_init/1 returns an invalid command." ),
@@ -178,7 +188,7 @@ do_one_frame(Config, Path) ->
178188 ]),
179189 ensure_handle_is_called (ConnPid , StreamRef , Path ),
180190 {ok , {text , <<" One frame!" >>}} = receive_ws (ConnPid , StreamRef ),
181- ok .
191+ gun : close ( ConnPid ) .
182192
183193websocket_init_many_frames (Config ) ->
184194 doc (" Multiple frames are received when websocket_init/1 returns them as commands." ),
@@ -200,7 +210,7 @@ do_many_frames(Config, Path) ->
200210 ensure_handle_is_called (ConnPid , StreamRef , Path ),
201211 {ok , {text , <<" One frame!" >>}} = receive_ws (ConnPid , StreamRef ),
202212 {ok , {binary , <<" Two frames!" >>}} = receive_ws (ConnPid , StreamRef ),
203- ok .
213+ gun : close ( ConnPid ) .
204214
205215websocket_init_close_frame (Config ) ->
206216 doc (" A single close frame is received when websocket_init/1 returns it as a command." ),
@@ -266,7 +276,7 @@ websocket_active_false(Config) ->
266276 {ok , {binary , _ }} = receive_ws (ConnPid , StreamRef ),
267277 {ok , {text , <<" Not received until the handler enables active again." >>}}
268278 = receive_ws (ConnPid , StreamRef ),
269- ok .
279+ gun : close ( ConnPid ) .
270280
271281websocket_deflate_false (Config ) ->
272282 doc (" The {deflate, false} command temporarily disables compression. "
@@ -305,7 +315,7 @@ websocket_deflate_ignore_if_not_negotiated(Config) ->
305315 gun :ws_send (ConnPid , StreamRef , {text , <<" Hello." >>}),
306316 {ok , {text , <<" Hello." >>}} = receive_ws (ConnPid , StreamRef )
307317 end || _ <- lists :seq (1 , 10 )],
308- ok .
318+ gun : close ( ConnPid ) .
309319
310320websocket_set_options_idle_timeout (Config ) ->
311321 doc (" The idle_timeout option can be modified using the "
@@ -390,3 +400,84 @@ websocket_shutdown_reason(Config) ->
390400 after 1000 ->
391401 error (timeout )
392402 end .
403+
404+ websocket_terminate_close_normal (Config ) ->
405+ doc (" Receiving a close frame results in a terminate/3 call. "
406+ " The Req object is kept in a more compact form by default." ),
407+ ConnPid = gun_open (Config , #{http2_opts => #{notify_settings_changed => true }}),
408+ do_await_enable_connect_protocol (config (protocol , Config ), ConnPid ),
409+ StreamRef = gun :ws_upgrade (ConnPid , " /terminate" , [
410+ {<<" x-test-pid" >>, pid_to_list (self ())}
411+ ]),
412+ {upgrade , [<<" websocket" >>], _ } = gun :await (ConnPid , StreamRef ),
413+ {ws_pid , WsPid } = do_receive (ws_pid ),
414+ MRef = monitor (process , WsPid ),
415+ gun :ws_send (ConnPid , StreamRef , close ),
416+ {terminate , remote , Req } = do_receive (terminate ),
417+ {'DOWN' , MRef , process , WsPid , normal } = do_receive ('DOWN' ),
418+ % % Confirm terminate/3 was called with a compacted Req.
419+ true = maps :is_key (path , Req ),
420+ false = maps :is_key (headers , Req ),
421+ ok .
422+
423+ websocket_terminate_close_reason (Config ) ->
424+ doc (" Receiving a close frame results in a terminate/3 call. "
425+ " The Req object is kept in a more compact form by default." ),
426+ ConnPid = gun_open (Config , #{http2_opts => #{notify_settings_changed => true }}),
427+ do_await_enable_connect_protocol (config (protocol , Config ), ConnPid ),
428+ StreamRef = gun :ws_upgrade (ConnPid , " /terminate" , [
429+ {<<" x-test-pid" >>, pid_to_list (self ())}
430+ ]),
431+ {upgrade , [<<" websocket" >>], _ } = gun :await (ConnPid , StreamRef ),
432+ {ws_pid , WsPid } = do_receive (ws_pid ),
433+ MRef = monitor (process , WsPid ),
434+ gun :ws_send (ConnPid , StreamRef , {close , 4000 , <<" test-close" >>}),
435+ {terminate , {remote , 4000 , <<" test-close" >>}, Req } = do_receive (terminate ),
436+ {'DOWN' , MRef , process , WsPid , normal } = do_receive ('DOWN' ),
437+ % % Confirm terminate/3 was called with a compacted Req.
438+ true = maps :is_key (path , Req ),
439+ false = maps :is_key (headers , Req ),
440+ ok .
441+
442+ websocket_terminate_socket_close (Config ) ->
443+ doc (" The socket getting closed results in a terminate/3 call. "
444+ " The Req object is kept in a more compact form by default." ),
445+ Protocol = config (protocol , Config ),
446+ ConnPid = gun_open (Config , #{http2_opts => #{notify_settings_changed => true }}),
447+ do_await_enable_connect_protocol (Protocol , ConnPid ),
448+ StreamRef = gun :ws_upgrade (ConnPid , " /terminate" , [
449+ {<<" x-test-pid" >>, pid_to_list (self ())}
450+ ]),
451+ {upgrade , [<<" websocket" >>], _ } = gun :await (ConnPid , StreamRef ),
452+ {ws_pid , WsPid } = do_receive (ws_pid ),
453+ MRef = monitor (process , WsPid ),
454+ gun :close (ConnPid ),
455+ % % Terminate reasons differ depending on the protocol.
456+ {terminate , Reason , Req } = do_receive (terminate ),
457+ case Reason of
458+ {error , closed } when Protocol =:= http -> ok ;
459+ shutdown when Protocol =:= http2 -> ok
460+ end ,
461+ {'DOWN' , MRef , process , WsPid , normal } = do_receive ('DOWN' ),
462+ % % Confirm terminate/3 was called with a compacted Req.
463+ true = maps :is_key (path , Req ),
464+ false = maps :is_key (headers , Req ),
465+ ok .
466+
467+ websocket_terminate_req_filter (Config ) ->
468+ doc (" Receiving a close frame results in a terminate/3 call. "
469+ " A function can be given to filter the Req object." ),
470+ ConnPid = gun_open (Config , #{http2_opts => #{notify_settings_changed => true }}),
471+ do_await_enable_connect_protocol (config (protocol , Config ), ConnPid ),
472+ StreamRef = gun :ws_upgrade (ConnPid , " /terminate?req_filter" , [
473+ {<<" x-test-pid" >>, pid_to_list (self ())}
474+ ]),
475+ {upgrade , [<<" websocket" >>], _ } = gun :await (ConnPid , StreamRef ),
476+ {ws_pid , WsPid } = do_receive (ws_pid ),
477+ MRef = monitor (process , WsPid ),
478+ gun :ws_send (ConnPid , StreamRef , close ),
479+ {terminate , remote , Req } = do_receive (terminate ),
480+ {'DOWN' , MRef , process , WsPid , normal } = do_receive ('DOWN' ),
481+ % % Confirm terminate/3 was called with a filtered Req.
482+ filtered = Req ,
483+ ok .
0 commit comments