aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2019-10-10 11:33:35 +0200
committerLoïc Hoguin <[email protected]>2019-10-10 11:33:35 +0200
commitd52e84bdd97b93d7d9cea827de57bd4a0edea9a8 (patch)
tree48958da1fc3bd2b2e3666fc9ee2b545099116d47
parentcc54c207e35f3ab7a2dfc105eef39fe7d3bf1633 (diff)
downloadcowboy-d52e84bdd97b93d7d9cea827de57bd4a0edea9a8.tar.gz
cowboy-d52e84bdd97b93d7d9cea827de57bd4a0edea9a8.tar.bz2
cowboy-d52e84bdd97b93d7d9cea827de57bd4a0edea9a8.zip
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.
-rw-r--r--doc/src/guide/migrating_from_2.6.asciidoc6
-rw-r--r--doc/src/manual/cowboy_websocket.asciidoc15
-rw-r--r--src/cowboy_websocket.erl13
-rw-r--r--test/handlers/ws_shutdown_reason_commands_h.erl38
-rw-r--r--test/ws_handler_SUITE.erl21
5 files changed, 87 insertions, 6 deletions
diff --git a/doc/src/guide/migrating_from_2.6.asciidoc b/doc/src/guide/migrating_from_2.6.asciidoc
index a582ee4..91d1588 100644
--- a/doc/src/guide/migrating_from_2.6.asciidoc
+++ b/doc/src/guide/migrating_from_2.6.asciidoc
@@ -81,6 +81,12 @@ Cowboy 2.7 requires Erlang/OTP 20.0 or greater.
is now considered stable and has been documented.
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`
and `cowboy_tracer_h` are now considered stable and
have been documented.
diff --git a/doc/src/manual/cowboy_websocket.asciidoc b/doc/src/manual/cowboy_websocket.asciidoc
index 440a0e8..59d412d 100644
--- a/doc/src/manual/cowboy_websocket.asciidoc
+++ b/doc/src/manual/cowboy_websocket.asciidoc
@@ -141,6 +141,7 @@ commands() :: [Command]
Command :: {active, boolean()}
| {deflate, boolean()}
| {set_options, #{idle_timeout => timeout()}}
+ | {shutdown_reason, any()}
| Frame :: cow_ws:frame()
----
@@ -163,6 +164,15 @@ set_options::
Set Websocket options. Currently only the option `idle_timeout`
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::
Send the corresponding Websocket frame.
@@ -266,8 +276,9 @@ normal circumstances if necessary.
== Changelog
-* *2.7*: The commands based interface has been added. The old
- interface is now deprecated.
+* *2.7*: The commands based interface has been documented.
+ The old interface is now deprecated.
+* *2.7*: The command `shutdown_reason` was introduced.
* *2.7*: The option `validate_utf8` has been added.
* *2.6*: Deflate options can now be configured via `deflate_opts`.
* *2.0*: The Req object is no longer passed to Websocket callbacks.
diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl
index ad0dad5..31103ac 100644
--- a/src/cowboy_websocket.erl
+++ b/src/cowboy_websocket.erl
@@ -35,6 +35,7 @@
| {active, boolean()}
| {deflate, boolean()}
| {set_options, map()}
+ | {shutdown_reason, any()}
].
-export_type([commands/0]).
@@ -95,7 +96,8 @@
utf8_state :: cow_ws:utf8_state(),
deflate = true :: boolean(),
extensions = #{} :: map(),
- req = #{} :: map()
+ req = #{} :: map(),
+ shutdown_reason = normal :: any()
}).
%% 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
end,
commands(Tail, State, Data);
+commands([{shutdown_reason, ShutdownReason}|Tail], State, Data) ->
+ commands(Tail, State#state{shutdown_reason=ShutdownReason}, Data);
commands([Frame|Tail], State, Data0) ->
Data = [frame(Frame, State)|Data0],
case is_close_frame(Frame) of
@@ -623,9 +627,12 @@ frame(Frame, #state{extensions=Extensions}) ->
cow_ws:frame(Frame, Extensions).
-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),
- exit(normal).
+ case Shutdown of
+ normal -> exit(normal);
+ _ -> exit({shutdown, Shutdown})
+ end.
handler_terminate(#state{handler=Handler, req=Req}, HandlerState, Reason) ->
cowboy_handler:terminate(Reason, Req, HandlerState, Handler).
diff --git a/test/handlers/ws_shutdown_reason_commands_h.erl b/test/handlers/ws_shutdown_reason_commands_h.erl
new file mode 100644
index 0000000..90b435c
--- /dev/null
+++ b/test/handlers/ws_shutdown_reason_commands_h.erl
@@ -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}.
diff --git a/test/ws_handler_SUITE.erl b/test/ws_handler_SUITE.erl
index 67d50d2..872b152 100644
--- a/test/ws_handler_SUITE.erl
+++ b/test/ws_handler_SUITE.erl
@@ -52,7 +52,8 @@ init_dispatch(Name) ->
{"/info", ws_info_commands_h, RunOrHibernate},
{"/active", ws_active_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.
@@ -286,3 +287,21 @@ websocket_set_options_idle_timeout(Config) ->
after 2000 ->
error(timeout)
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.