mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
Add shutdown_reason Websocket command
This allows changing the normal exit reason of Websocket processes, providing a way to signal other processes of why the exit occurred.
This commit is contained in:
parent
cc54c207e3
commit
d52e84bdd9
5 changed files with 87 additions and 6 deletions
|
@ -81,6 +81,12 @@ Cowboy 2.7 requires Erlang/OTP 20.0 or greater.
|
||||||
is now considered stable and has been documented.
|
is now considered stable and has been documented.
|
||||||
The old interface is now deprecated.
|
The old interface is now deprecated.
|
||||||
|
|
||||||
|
* A new Websocket handler command `shutdown_reason`
|
||||||
|
can be used to change the normal exit reason of
|
||||||
|
Websocket processes. By default `normal` is used;
|
||||||
|
with this command the exit reason can be changed
|
||||||
|
to `{shutdown, ShutdownReason}`.
|
||||||
|
|
||||||
* The experimental stream handlers `cowboy_metrics_h`
|
* The experimental stream handlers `cowboy_metrics_h`
|
||||||
and `cowboy_tracer_h` are now considered stable and
|
and `cowboy_tracer_h` are now considered stable and
|
||||||
have been documented.
|
have been documented.
|
||||||
|
|
|
@ -141,6 +141,7 @@ commands() :: [Command]
|
||||||
Command :: {active, boolean()}
|
Command :: {active, boolean()}
|
||||||
| {deflate, boolean()}
|
| {deflate, boolean()}
|
||||||
| {set_options, #{idle_timeout => timeout()}}
|
| {set_options, #{idle_timeout => timeout()}}
|
||||||
|
| {shutdown_reason, any()}
|
||||||
| Frame :: cow_ws:frame()
|
| Frame :: cow_ws:frame()
|
||||||
----
|
----
|
||||||
|
|
||||||
|
@ -163,6 +164,15 @@ set_options::
|
||||||
Set Websocket options. Currently only the option `idle_timeout`
|
Set Websocket options. Currently only the option `idle_timeout`
|
||||||
may be updated from a Websocket handler.
|
may be updated from a Websocket handler.
|
||||||
|
|
||||||
|
shutdown_reason::
|
||||||
|
|
||||||
|
Change the shutdown reason. The Websocket process will exit
|
||||||
|
with reason `normal` by default. This command can be used to
|
||||||
|
exit with reason `{shutdown, ShutdownReason}` under normal
|
||||||
|
conditions. This command has no effect when the Websocket
|
||||||
|
process exits abnormally, for example following a crash in a
|
||||||
|
handler callback.
|
||||||
|
|
||||||
Frame::
|
Frame::
|
||||||
|
|
||||||
Send the corresponding Websocket frame.
|
Send the corresponding Websocket frame.
|
||||||
|
@ -266,8 +276,9 @@ normal circumstances if necessary.
|
||||||
|
|
||||||
== Changelog
|
== Changelog
|
||||||
|
|
||||||
* *2.7*: The commands based interface has been added. The old
|
* *2.7*: The commands based interface has been documented.
|
||||||
interface is now deprecated.
|
The old interface is now deprecated.
|
||||||
|
* *2.7*: The command `shutdown_reason` was introduced.
|
||||||
* *2.7*: The option `validate_utf8` has been added.
|
* *2.7*: The option `validate_utf8` has been added.
|
||||||
* *2.6*: Deflate options can now be configured via `deflate_opts`.
|
* *2.6*: Deflate options can now be configured via `deflate_opts`.
|
||||||
* *2.0*: The Req object is no longer passed to Websocket callbacks.
|
* *2.0*: The Req object is no longer passed to Websocket callbacks.
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
| {active, boolean()}
|
| {active, boolean()}
|
||||||
| {deflate, boolean()}
|
| {deflate, boolean()}
|
||||||
| {set_options, map()}
|
| {set_options, map()}
|
||||||
|
| {shutdown_reason, any()}
|
||||||
].
|
].
|
||||||
-export_type([commands/0]).
|
-export_type([commands/0]).
|
||||||
|
|
||||||
|
@ -95,7 +96,8 @@
|
||||||
utf8_state :: cow_ws:utf8_state(),
|
utf8_state :: cow_ws:utf8_state(),
|
||||||
deflate = true :: boolean(),
|
deflate = true :: boolean(),
|
||||||
extensions = #{} :: map(),
|
extensions = #{} :: map(),
|
||||||
req = #{} :: map()
|
req = #{} :: map(),
|
||||||
|
shutdown_reason = normal :: any()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
%% Because the HTTP/1.1 and HTTP/2 handshakes are so different,
|
%% Because the HTTP/1.1 and HTTP/2 handshakes are so different,
|
||||||
|
@ -546,6 +548,8 @@ commands([{set_options, SetOpts}|Tail], State0=#state{opts=Opts}, Data) ->
|
||||||
State0
|
State0
|
||||||
end,
|
end,
|
||||||
commands(Tail, State, Data);
|
commands(Tail, State, Data);
|
||||||
|
commands([{shutdown_reason, ShutdownReason}|Tail], State, Data) ->
|
||||||
|
commands(Tail, State#state{shutdown_reason=ShutdownReason}, Data);
|
||||||
commands([Frame|Tail], State, Data0) ->
|
commands([Frame|Tail], State, Data0) ->
|
||||||
Data = [frame(Frame, State)|Data0],
|
Data = [frame(Frame, State)|Data0],
|
||||||
case is_close_frame(Frame) of
|
case is_close_frame(Frame) of
|
||||||
|
@ -623,9 +627,12 @@ frame(Frame, #state{extensions=Extensions}) ->
|
||||||
cow_ws:frame(Frame, Extensions).
|
cow_ws:frame(Frame, Extensions).
|
||||||
|
|
||||||
-spec terminate(#state{}, any(), terminate_reason()) -> no_return().
|
-spec terminate(#state{}, any(), terminate_reason()) -> no_return().
|
||||||
terminate(State, HandlerState, Reason) ->
|
terminate(State=#state{shutdown_reason=Shutdown}, HandlerState, Reason) ->
|
||||||
handler_terminate(State, HandlerState, Reason),
|
handler_terminate(State, HandlerState, Reason),
|
||||||
exit(normal).
|
case Shutdown of
|
||||||
|
normal -> exit(normal);
|
||||||
|
_ -> exit({shutdown, Shutdown})
|
||||||
|
end.
|
||||||
|
|
||||||
handler_terminate(#state{handler=Handler, req=Req}, HandlerState, Reason) ->
|
handler_terminate(#state{handler=Handler, req=Req}, HandlerState, Reason) ->
|
||||||
cowboy_handler:terminate(Reason, Req, HandlerState, Handler).
|
cowboy_handler:terminate(Reason, Req, HandlerState, Handler).
|
||||||
|
|
38
test/handlers/ws_shutdown_reason_commands_h.erl
Normal file
38
test/handlers/ws_shutdown_reason_commands_h.erl
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
%% This module sends the process pid to the test pid
|
||||||
|
%% found in the x-test-pid header, then changes the
|
||||||
|
%% shutdown reason and closes the connection normally.
|
||||||
|
|
||||||
|
-module(ws_shutdown_reason_commands_h).
|
||||||
|
-behavior(cowboy_websocket).
|
||||||
|
|
||||||
|
-export([init/2]).
|
||||||
|
-export([websocket_init/1]).
|
||||||
|
-export([websocket_handle/2]).
|
||||||
|
-export([websocket_info/2]).
|
||||||
|
|
||||||
|
init(Req, RunOrHibernate) ->
|
||||||
|
TestPid = list_to_pid(binary_to_list(cowboy_req:header(<<"x-test-pid">>, Req))),
|
||||||
|
{cowboy_websocket, Req, {TestPid, RunOrHibernate}}.
|
||||||
|
|
||||||
|
websocket_init(State={TestPid, RunOrHibernate}) ->
|
||||||
|
TestPid ! {ws_pid, self()},
|
||||||
|
ShutdownReason = receive
|
||||||
|
{TestPid, SR} ->
|
||||||
|
SR
|
||||||
|
after 1000 ->
|
||||||
|
error(timeout)
|
||||||
|
end,
|
||||||
|
Commands = [
|
||||||
|
{shutdown_reason, ShutdownReason},
|
||||||
|
close
|
||||||
|
],
|
||||||
|
case RunOrHibernate of
|
||||||
|
run -> {Commands, State};
|
||||||
|
hibernate -> {Commands, State, hibernate}
|
||||||
|
end.
|
||||||
|
|
||||||
|
websocket_handle(_, State) ->
|
||||||
|
{[], State}.
|
||||||
|
|
||||||
|
websocket_info(_, State) ->
|
||||||
|
{[], State}.
|
|
@ -52,7 +52,8 @@ init_dispatch(Name) ->
|
||||||
{"/info", ws_info_commands_h, RunOrHibernate},
|
{"/info", ws_info_commands_h, RunOrHibernate},
|
||||||
{"/active", ws_active_commands_h, RunOrHibernate},
|
{"/active", ws_active_commands_h, RunOrHibernate},
|
||||||
{"/deflate", ws_deflate_commands_h, RunOrHibernate},
|
{"/deflate", ws_deflate_commands_h, RunOrHibernate},
|
||||||
{"/set_options", ws_set_options_commands_h, RunOrHibernate}
|
{"/set_options", ws_set_options_commands_h, RunOrHibernate},
|
||||||
|
{"/shutdown_reason", ws_shutdown_reason_commands_h, RunOrHibernate}
|
||||||
]}]).
|
]}]).
|
||||||
|
|
||||||
%% Support functions for testing using Gun.
|
%% Support functions for testing using Gun.
|
||||||
|
@ -286,3 +287,21 @@ websocket_set_options_idle_timeout(Config) ->
|
||||||
after 2000 ->
|
after 2000 ->
|
||||||
error(timeout)
|
error(timeout)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
websocket_shutdown_reason(Config) ->
|
||||||
|
doc("The command {shutdown_reason, any()} can be used to "
|
||||||
|
"change the shutdown reason of a Websocket connection."),
|
||||||
|
ConnPid = gun_open(Config),
|
||||||
|
StreamRef = gun:ws_upgrade(ConnPid, "/shutdown_reason", [
|
||||||
|
{<<"x-test-pid">>, pid_to_list(self())}
|
||||||
|
]),
|
||||||
|
{upgrade, [<<"websocket">>], _} = gun:await(ConnPid, StreamRef),
|
||||||
|
WsPid = receive {ws_pid, P} -> P after 1000 -> error(timeout) end,
|
||||||
|
MRef = monitor(process, WsPid),
|
||||||
|
WsPid ! {self(), {?MODULE, ?FUNCTION_NAME}},
|
||||||
|
receive
|
||||||
|
{'DOWN', MRef, process, WsPid, {shutdown, {?MODULE, ?FUNCTION_NAME}}} ->
|
||||||
|
ok
|
||||||
|
after 1000 ->
|
||||||
|
error(timeout)
|
||||||
|
end.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue