Browse Source

Document the commands based Websocket interface

The old interface with ok|reply|stop tuples is deprecated.
Loïc Hoguin 5 years ago
parent
commit
3977f2b96f

+ 12 - 13
doc/src/guide/ws_handlers.asciidoc

@@ -105,7 +105,7 @@ the upgrade:
 [source,erlang]
 ----
 websocket_init(State) ->
-    {reply, {text, <<"Hello!">>}, State}.
+    {[{text, <<"Hello!">>}], State}.
 ----
 
 === Receiving frames
@@ -122,7 +122,7 @@ ignores all others:
 [source,erlang]
 ----
 websocket_handle(Frame = {text, _}, State) ->
-    {reply, Frame, State};
+    {[Frame], State};
 websocket_handle(_Frame, State) ->
     {ok, State}.
 ----
@@ -145,7 +145,7 @@ and ignores all others:
 [source,erlang]
 ----
 websocket_info({log, Text}, State) ->
-    {reply, {text, Text}, State};
+    {[{text, Text}], State};
 websocket_info(_Info, State) ->
     {ok, State}.
 ----
@@ -167,24 +167,23 @@ websocket_info(_Info, State) ->
     {ok, State}.
 ----
 
-To send one frame, return a reply tuple with the frame to send:
+To send one frame, return the frame to be sent:
 
 [source,erlang]
 ----
 websocket_info(_Info, State) ->
-    {reply, {text, <<"Hello!">>}, State}.
+    {[{text, <<"Hello!">>}], State}.
 ----
 
 You can send frames of any type: text, binary, ping, pong
 or close frames.
 
-To send many frames at once, return a reply tuple with the
-list of frames to send:
+You can send many frames at the same time:
 
 [source,erlang]
 ----
 websocket_info(_Info, State) ->
-    {reply, [
+    {[
         {text, "Hello"},
         {text, <<"world!">>},
         {binary, <<0:8000>>}
@@ -246,18 +245,18 @@ Cowboy will have a more reasonable default.
 The Websocket connection process can be set to hibernate
 after the callback returns.
 
-Simply add an `hibernate` field to the ok or reply tuples:
+Simply add an `hibernate` field to the returned tuple:
 
 [source,erlang]
 ----
 websocket_init(State) ->
-    {ok, State, hibernate}.
+    {[], State, hibernate}.
 
 websocket_handle(_Frame, State) ->
-    {ok, State, hibernate}.
+    {[], State, hibernate}.
 
 websocket_info(_Info, State) ->
-    {reply, {text, <<"Hello!">>}, State, hibernate}.
+    {[{text, <<"Hello!">>}], State, hibernate}.
 ----
 
 It is highly recommended to write your handlers with
@@ -289,5 +288,5 @@ The following example sends a close frame with a reason message:
 [source,erlang]
 ----
 websocket_info(_Info, State) ->
-    {reply, {close, 1000, <<"some-reason">>}, State}.
+    {[{close, 1000, <<"some-reason">>}], State}.
 ----

+ 46 - 5
doc/src/manual/cowboy_websocket.asciidoc

@@ -32,14 +32,18 @@ PartialReq :: map()
 State      :: any()
 Opts       :: cowboy_websocket:opts()
 InFrame    :: ping | pong | {text | binary | ping | pong, binary()}
-OutFrame   :: cow_ws:frame()                    %% see types below
 Info       :: any()
 
-CallResult :: {ok, State}
+CallResult :: {commands(), State}
+            | {commands(), State, hibernate}
+            | Deprecated
+
+Deprecated :: {ok, State}
             | {ok, State, hibernate}
             | {reply, OutFrame | [OutFrame], State}
             | {reply, OutFrame | [OutFrame], State, hibernate}
             | {stop, State}
+OutFrame   :: cow_ws:frame()                    %% see types below
 
 Reason     :: normal | stop | timeout
             | remote | {remote, cow_ws:close_code(), binary()}
@@ -69,9 +73,9 @@ frame received. The `websocket_info/2` callback will be
 called for every Erlang message received.
 
 All three Websocket callbacks may send one or more frames
-back to the client (by returning a `reply` tuple) or terminate
-the connection (by sending a `close` frame or returning a `stop`
-tuple).
+back to the client, including close frames to terminate
+the connection; enable/disable active mode; enable/disable
+compression for subsequent frames; or change Websocket options.
 
 The optional `terminate/3` callback will ultimately be called
 with the reason for the termination of the connection. This
@@ -128,6 +132,41 @@ timeout::
 
 == Types
 
+=== commands()
+
+[source,erlang]
+----
+commands() :: [Command]
+
+Command :: {active, boolean()}
+         | {deflate, boolean()}
+         | {set_options, #{idle_timeout => timeout()}}
+         | Frame :: cow_ws:frame()
+----
+
+Commands that may be returned from Websocket callbacks.
+
+The following commands are defined:
+
+active::
+
+Whether to disable or enable reading from the socket. This
+can be used to apply flow control to a Websocket connection.
+
+deflate::
+
+Whether the subsequent frames should be compressed. Has no
+effect on connections that did not negotiate compression.
+
+set_options::
+
+Set Websocket options. Currently only the option `idle_timeout`
+may be updated from a Websocket handler.
+
+Frame::
+
+Send the corresponding Websocket frame.
+
 === cow_ws:frame()
 
 [source,erlang]
@@ -224,6 +263,8 @@ normal circumstances if necessary.
 
 == Changelog
 
+* *2.7*: The commands based interface has been added. The old
+         interface is now deprecated.
 * *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.

+ 5 - 5
examples/websocket/src/ws_h.erl

@@ -10,15 +10,15 @@ init(Req, Opts) ->
 
 websocket_init(State) ->
 	erlang:start_timer(1000, self(), <<"Hello!">>),
-	{ok, State}.
+	{[], State}.
 
 websocket_handle({text, Msg}, State) ->
-	{reply, {text, << "That's what she said! ", Msg/binary >>}, State};
+	{[{text, << "That's what she said! ", Msg/binary >>}], State};
 websocket_handle(_Data, State) ->
-	{ok, State}.
+	{[], State}.
 
 websocket_info({timeout, _Ref, Msg}, State) ->
 	erlang:start_timer(1000, self(), <<"How' you doin'?">>),
-	{reply, {text, Msg}, State};
+	{[{text, Msg}], State};
 websocket_info(_Info, State) ->
-	{ok, State}.
+	{[], State}.

+ 5 - 5
plugins.mk

@@ -61,15 +61,15 @@ init(Req, State) ->
 	{cowboy_websocket, Req, State}.
 
 websocket_init(State) ->
-	{ok, State}.
+	{[], State}.
 
 websocket_handle({text, Data}, State) ->
-	{reply, {text, Data}, State};
+	{[{text, Data}], State};
 websocket_handle({binary, Data}, State) ->
-	{reply, {binary, Data}, State};
+	{[{binary, Data}], State};
 websocket_handle(_Frame, State) ->
-	{ok, State}.
+	{[], State}.
 
 websocket_info(_Info, State) ->
-	{ok, State}.
+	{[], State}.
 endef

+ 4 - 4
test/handlers/ws_deflate_opts_h.erl

@@ -26,11 +26,11 @@ init(Req=#{qs := Qs}, State) ->
 	}}.
 
 websocket_handle({text, Data}, State) ->
-	{reply, {text, Data}, State};
+	{[{text, Data}], State};
 websocket_handle({binary, Data}, State) ->
-	{reply, {binary, Data}, State};
+	{[{binary, Data}], State};
 websocket_handle(_, State) ->
-	{ok, State}.
+	{[], State}.
 
 websocket_info(_, State) ->
-	{ok, State}.
+	{[], State}.

+ 4 - 4
test/handlers/ws_dont_validate_utf8_h.erl

@@ -13,11 +13,11 @@ init(Req, State) ->
 	}}.
 
 websocket_handle({text, Data}, State) ->
-	{reply, {text, Data}, State};
+	{[{text, Data}], State};
 websocket_handle({binary, Data}, State) ->
-	{reply, {binary, Data}, State};
+	{[{binary, Data}], State};
 websocket_handle(_, State) ->
-	{ok, State}.
+	{[], State}.
 
 websocket_info(_, State) ->
-	{ok, State}.
+	{[], State}.

+ 12 - 14
test/handlers/ws_init_h.erl

@@ -18,30 +18,28 @@ websocket_init(State) ->
 	do_websocket_init(State).
 
 do_websocket_init(State=ok) ->
-	{ok, State};
+	{[], State};
 do_websocket_init(State=ok_hibernate) ->
-	{ok, State, hibernate};
+	{[], State, hibernate};
 do_websocket_init(State=reply) ->
-	{reply, {text, "Hello"}, State};
+	{[{text, "Hello"}], State};
 do_websocket_init(State=reply_hibernate) ->
-	{reply, {text, "Hello"}, State, hibernate};
+	{[{text, "Hello"}], State, hibernate};
 do_websocket_init(State=reply_close) ->
-	{reply, close, State};
+	{[close], State};
 do_websocket_init(State=reply_close_hibernate) ->
-	{reply, close, State, hibernate};
+	{[close], State, hibernate};
 do_websocket_init(State=reply_many) ->
-	{reply, [{text, "Hello"}, {binary, "World"}], State};
+	{[{text, "Hello"}, {binary, "World"}], State};
 do_websocket_init(State=reply_many_hibernate) ->
-	{reply, [{text, "Hello"}, {binary, "World"}], State, hibernate};
+	{[{text, "Hello"}, {binary, "World"}], State, hibernate};
 do_websocket_init(State=reply_many_close) ->
-	{reply, [{text, "Hello"}, close], State};
+	{[{text, "Hello"}, close], State};
 do_websocket_init(State=reply_many_close_hibernate) ->
-	{reply, [{text, "Hello"}, close], State, hibernate};
-do_websocket_init(State=stop) ->
-	{stop, State}.
+	{[{text, "Hello"}, close], State, hibernate}.
 
 websocket_handle(_, State) ->
-	{ok, State}.
+	{[], State}.
 
 websocket_info(_, State) ->
-	{ok, State}.
+	{[], State}.

+ 0 - 7
test/ws_SUITE.erl

@@ -403,13 +403,6 @@ ws_init_return_reply_many_close_hibernate(Config) ->
 		1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 9, 6000),
 	ok.
 
-ws_init_return_stop(Config) ->
-	doc("Handler closes immediately after the handshake."),
-	{ok, Socket, _} = do_handshake("/ws_init?stop", Config),
-	{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
-	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
-	ok.
-
 ws_init_shutdown_before_handshake(Config) ->
 	doc("Handler stops before Websocket handshake."),
 	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),

+ 4 - 4
test/ws_SUITE_data/ws_echo.erl

@@ -12,11 +12,11 @@ init(Req, _) ->
 	}}.
 
 websocket_handle({text, Data}, State) ->
-	{reply, {text, Data}, State};
+	{[{text, Data}], State};
 websocket_handle({binary, Data}, State) ->
-	{reply, {binary, Data}, State};
+	{[{binary, Data}], State};
 websocket_handle(_Frame, State) ->
-	{ok, State}.
+	{[], State}.
 
 websocket_info(_Info, State) ->
-	{ok, State}.
+	{[], State}.

+ 6 - 6
test/ws_SUITE_data/ws_echo_timer.erl

@@ -12,17 +12,17 @@ init(Req, _) ->
 
 websocket_init(State) ->
 	erlang:start_timer(1000, self(), <<"websocket_init">>),
-	{ok, State}.
+	{[], State}.
 
 websocket_handle({text, Data}, State) ->
-	{reply, {text, Data}, State};
+	{[{text, Data}], State};
 websocket_handle({binary, Data}, State) ->
-	{reply, {binary, Data}, State};
+	{[{binary, Data}], State};
 websocket_handle(_Frame, State) ->
-	{ok, State}.
+	{[], State}.
 
 websocket_info({timeout, _Ref, Msg}, State) ->
 	erlang:start_timer(1000, self(), <<"websocket_handle">>),
-	{reply, {text, Msg}, State};
+	{[{text, Msg}], State};
 websocket_info(_Info, State) ->
-	{ok, State}.
+	{[], State}.

+ 4 - 8
test/ws_SUITE_data/ws_max_frame_size.erl

@@ -1,22 +1,18 @@
 -module(ws_max_frame_size).
 
 -export([init/2]).
--export([websocket_init/1]).
 -export([websocket_handle/2]).
 -export([websocket_info/2]).
 
 init(Req, State) ->
 	{cowboy_websocket, Req, State, #{max_frame_size => 8}}.
 
-websocket_init(State) ->
-	{ok, State}.
-
 websocket_handle({text, Data}, State) ->
-	{reply, {text, Data}, State};
+	{[{text, Data}], State};
 websocket_handle({binary, Data}, State) ->
-	{reply, {binary, Data}, State};
+	{[{binary, Data}], State};
 websocket_handle(_Frame, State) ->
-	{ok, State}.
+	{[], State}.
 
 websocket_info(_Info, State) ->
-	{ok, State}.
+	{[], State}.

+ 3 - 3
test/ws_SUITE_data/ws_send_many.erl

@@ -12,10 +12,10 @@ init(Req, Opts) ->
 
 websocket_init(State) ->
 	erlang:send_after(10, self(), send_many),
-	{ok, State}.
+	{[], State}.
 
 websocket_handle(_Frame, State) ->
-	{ok, State}.
+	{[], State}.
 
 websocket_info(send_many, State = [{sequence, Sequence}]) ->
-	{reply, Sequence, State}.
+	{Sequence, State}.

+ 3 - 3
test/ws_SUITE_data/ws_timeout_cancel.erl

@@ -13,10 +13,10 @@ init(Req, _) ->
 	}}.
 
 websocket_handle({text, Data}, State) ->
-	{reply, {text, Data}, State};
+	{[{text, Data}], State};
 websocket_handle({binary, Data}, State) ->
-	{reply, {binary, Data}, State}.
+	{[{binary, Data}], State}.
 
 websocket_info(_Info, State) ->
 	erlang:start_timer(500, self(), should_not_cancel_timer),
-	{ok, State}.
+	{[], State}.