aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2022-12-05 17:22:09 +0100
committerLoïc Hoguin <[email protected]>2022-12-05 17:22:09 +0100
commit172800967c2d53251d7cb1015e3c957c5b065bb1 (patch)
tree1ce8a3d138b1ca7894ddd965c186b313aa0aaee4
parentc98c045affe053ba37e062e2754f32dcd65a0b30 (diff)
downloadgun-172800967c2d53251d7cb1015e3c957c5b065bb1.tar.gz
gun-172800967c2d53251d7cb1015e3c957c5b065bb1.tar.bz2
gun-172800967c2d53251d7cb1015e3c957c5b065bb1.zip
Document Websocket subprotocol negotiation
-rw-r--r--doc/src/manual/gun.asciidoc33
-rw-r--r--doc/src/manual/gun.ws_upgrade.asciidoc21
-rw-r--r--doc/src/manual/gun_app.asciidoc1
-rw-r--r--doc/src/manual/gun_ws_protocol.asciidoc108
-rw-r--r--ebin/gun.app2
-rw-r--r--src/gun.erl4
-rw-r--r--src/gun_ws_h.erl1
-rw-r--r--src/gun_ws_protocol.erl25
8 files changed, 182 insertions, 13 deletions
diff --git a/doc/src/manual/gun.asciidoc b/doc/src/manual/gun.asciidoc
index 9ad3e8e..40559fb 100644
--- a/doc/src/manual/gun.asciidoc
+++ b/doc/src/manual/gun.asciidoc
@@ -525,12 +525,14 @@ detail.
[source,erlang]
----
ws_opts() :: #{
- closing_timeout => timeout(),
- compress => boolean(),
- flow => pos_integer(),
- keepalive => timeout(),
- protocols => [{binary(), module()}],
- silence_pings => boolean()
+ closing_timeout => timeout(),
+ compress => boolean(),
+ default_protocol => module(),
+ flow => pos_integer(),
+ keepalive => timeout(),
+ protocols => [{binary(), module()}],
+ silence_pings => boolean(),
+ user_opts => any()
}
----
@@ -550,6 +552,11 @@ Whether to enable permessage-deflate compression. This does
not guarantee that compression will be used as it is the
server that ultimately decides. Defaults to false.
+default_protocol (gun_ws_h)::
+
+Default protocol module when no Websocket subprotocol is
+negotiated.
+
flow - see below::
The initial flow control value for the Websocket connection.
@@ -563,8 +570,10 @@ protocols ([])::
A non-empty list enables Websocket protocol negotiation. The
list of protocols will be sent in the sec-websocket-protocol
-request header. The handler module interface is currently
-undocumented and must be set to `gun_ws_h`.
+request header. The given module must follow the
+link:man:gun_ws_protocol(3)[gun_ws_protocol(3)] interface.
+Gun comes with a default interface in `gun_ws_h` that may
+be reused for negotiated protocols.
silence_pings (true)::
@@ -572,10 +581,16 @@ Whether the ping and pong frames should be sent to the user.
In all cases Gun will automatically send a pong frame back
when receiving a ping.
-// @todo Document default_protocol and user_opts.
+user_opts - see below::
+
+Additional options that are not in use by Gun unless a custom
+Websocket subprotocol is configured and negotiated.
+By default no user option is defined.
== Changelog
+* *2.0*: The `default_protocol` and `user_opts` Websocket
+ options were added.
* *2.0*: The `stream_ref()` type was added.
* *2.0*: The option `cookie_store` was added. It can be used
to configure a cookie store that Gun will use
diff --git a/doc/src/manual/gun.ws_upgrade.asciidoc b/doc/src/manual/gun.ws_upgrade.asciidoc
index b553f3b..c6e3850 100644
--- a/doc/src/manual/gun.ws_upgrade.asciidoc
+++ b/doc/src/manual/gun.ws_upgrade.asciidoc
@@ -20,7 +20,7 @@ ws_upgrade(ConnPid, Path, Headers, WsOpts)
ConnPid :: pid()
Path :: iodata()
Headers :: gun:req_headers()
-WsOpts :: gun:ws_opts
+WsOpts :: gun:ws_opts()
StreamRef :: gun:stream_ref()
----
@@ -39,6 +39,11 @@ Gun does not currently support Websocket over HTTP/2.
By default Gun will take the Websocket options from
the connection's `ws_opts`.
+Websocket subprotocol negotiation is enabled when
+the `protocols` option is given. It takes a subprotocol
+name and a module implementing the
+link:man:gun_ws_protocol(3)[gun_ws_protocol(3)] behavior.
+
== Arguments
ConnPid::
@@ -92,9 +97,21 @@ StreamRef = gun:ws_upgrade(ConnPid, "/ws", [], #{
}).
----
+.Upgrade to Websocket with protocol negotiation
+[source,erlang]
+----
+StreamRef = gun:ws_upgrade(ConnPid, "/ws", [], #{
+ protocols => [
+ {<<"mqtt">>, gun_ws_mqtt_h},
+ {<<"v12.stomp">>, gun_ws_stomp_h}
+ ]
+}).
+----
+
== See also
link:man:gun(3)[gun(3)],
link:man:gun:ws_send(3)[gun:ws_send(3)],
link:man:gun_upgrade(3)[gun_upgrade(3)],
-link:man:gun_ws(3)[gun_ws(3)]
+link:man:gun_ws(3)[gun_ws(3)],
+link:man:gun_ws_protocol(3)[gun_ws_protocol(3)]
diff --git a/doc/src/manual/gun_app.asciidoc b/doc/src/manual/gun_app.asciidoc
index 168d0e9..ca05594 100644
--- a/doc/src/manual/gun_app.asciidoc
+++ b/doc/src/manual/gun_app.asciidoc
@@ -19,6 +19,7 @@ to the server and reconnects automatically when necessary.
* link:man:gun_cookies(3)[gun_cookies(3)] - Cookie store engine
* link:man:gun_cookies_list(3)[gun_cookies_list(3)] - Cookie store backend: in-memory, per connection
* link:man:gun_event(3)[gun_event(3)] - Events
+* link:man:gun_ws_protocol(3)[gun_ws_protocol(3)] - Websocket subprotocols
== Dependencies
diff --git a/doc/src/manual/gun_ws_protocol.asciidoc b/doc/src/manual/gun_ws_protocol.asciidoc
new file mode 100644
index 0000000..417ba94
--- /dev/null
+++ b/doc/src/manual/gun_ws_protocol.asciidoc
@@ -0,0 +1,108 @@
+= gun_ws_protocol(3)
+
+== Name
+
+gun_ws_protocol - Websocket subprotocols
+
+== Description
+
+The `gun_ws_protocol` module provides the callback interface
+and types for implementing Websocket subprotocols.
+
+== Callbacks
+
+Websocket subprotocols implement the following interface.
+
+=== init
+
+[source,erlang]
+----
+init(ReplyTo, StreamRef, Headers, Opts) -> {ok, State}
+
+ReplyTo :: pid()
+StreamRef :: reference()
+Headers :: cow_http:headers()
+Opts :: gun:ws_opts()
+State :: protocol_state()
+----
+
+Initialize the Websocket protocol.
+
+ReplyTo::
+
+The pid of the process that owns the stream and to
+which messages will be sent to.
+
+StreamRef::
+
+The reference for the stream. Must be sent in messages
+to distinguish between different streams.
+
+Headers::
+
+Headers that were sent in the response establishing
+the Websocket connection.
+
+Opts::
+
+Websocket options. Custom options can be provided in
+the `user_opts` key.
+
+State::
+
+State for the protocol.
+
+=== handle
+
+[source,erlang]
+----
+handle(Frame, State) -> {ok, FlowDec, State}
+
+Frame :: cow_ws:frame()
+State :: protocol_state()
+FlowDec :: non_neg_integer()
+----
+
+Handle a Websocket frame.
+
+This callback may receive fragmented frames depending
+on the protocol and may need to rebuild the full
+frame to process it.
+
+Frame::
+
+Websocket frame.
+
+State::
+
+State for the protocol.
+
+FlowDec::
+
+How many messages were sent. Used to update the flow
+control state when the feature is enabled.
+
+== Types
+
+=== protocol_state()
+
+[source,erlang]
+----
+protocol_state() :: any()
+----
+
+State for the protocol.
+
+As this part of the implementation of the protocol
+the type may differ between different Websocket
+protocol modules.
+
+== Changelog
+
+* *2.0*: Module introduced.
+
+== See also
+
+link:man:gun(7)[gun(7)],
+link:man:gun(3)[gun(3)],
+link:man:gun:ws_upgrade(3)[gun:ws_upgrade(3)]
diff --git a/ebin/gun.app b/ebin/gun.app
index 6341631..2b8ee7b 100644
--- a/ebin/gun.app
+++ b/ebin/gun.app
@@ -1,7 +1,7 @@
{application, 'gun', [
{description, "HTTP/1.1, HTTP/2 and Websocket client for Erlang/OTP."},
{vsn, "2.0.0-rc.2"},
- {modules, ['gun','gun_app','gun_conns_sup','gun_content_handler','gun_cookies','gun_cookies_list','gun_data_h','gun_default_event_h','gun_event','gun_http','gun_http2','gun_pool','gun_pool_events_h','gun_pools_sup','gun_protocols','gun_public_suffix','gun_raw','gun_socks','gun_sse_h','gun_sup','gun_tcp','gun_tcp_proxy','gun_tls','gun_tls_proxy','gun_tls_proxy_cb','gun_tls_proxy_http2_connect','gun_tunnel','gun_ws','gun_ws_h']},
+ {modules, ['gun','gun_app','gun_conns_sup','gun_content_handler','gun_cookies','gun_cookies_list','gun_data_h','gun_default_event_h','gun_event','gun_http','gun_http2','gun_pool','gun_pool_events_h','gun_pools_sup','gun_protocols','gun_public_suffix','gun_raw','gun_socks','gun_sse_h','gun_sup','gun_tcp','gun_tcp_proxy','gun_tls','gun_tls_proxy','gun_tls_proxy_cb','gun_tls_proxy_http2_connect','gun_tunnel','gun_ws','gun_ws_h','gun_ws_protocol']},
{registered, [gun_sup]},
{applications, [kernel,stdlib,ssl,cowlib]},
{mod, {gun_app, []}},
diff --git a/src/gun.erl b/src/gun.erl
index 880a2ae..b27ea6e 100644
--- a/src/gun.erl
+++ b/src/gun.erl
@@ -267,12 +267,14 @@
-type ws_opts() :: #{
closing_timeout => timeout(),
compress => boolean(),
+ default_protocol => module(),
flow => pos_integer(),
keepalive => timeout(),
protocols => [{binary(), module()}],
reply_to => pid(),
silence_pings => boolean(),
- tunnel => stream_ref()
+ tunnel => stream_ref(),
+ user_opts => any()
}.
-export_type([ws_opts/0]).
diff --git a/src/gun_ws_h.erl b/src/gun_ws_h.erl
index 2412122..fca3c9a 100644
--- a/src/gun_ws_h.erl
+++ b/src/gun_ws_h.erl
@@ -13,6 +13,7 @@
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(gun_ws_h).
+-behavior(gun_ws_protocol).
-export([init/4]).
-export([handle/2]).
diff --git a/src/gun_ws_protocol.erl b/src/gun_ws_protocol.erl
new file mode 100644
index 0000000..7b0cc5c
--- /dev/null
+++ b/src/gun_ws_protocol.erl
@@ -0,0 +1,25 @@
+%% Copyright (c) 2022, Loïc Hoguin <[email protected]>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(gun_ws_protocol).
+
+-type protocol_state() :: any().
+-export_type([protocol_state/0]).
+
+-callback init(pid(), reference(), cow_http:headers(), gun:ws_opts())
+ -> {ok, protocol_state()}.
+
+-callback handle(cow_ws:frame(), State)
+ -> {ok, non_neg_integer(), State}
+ when State::protocol_state().