0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 12:20:24 +00:00

Introduce the req_filter Websocket option

This option allows customizing the compacting of the Req object
when using Websocket. By default it will keep most public fields
excluding headers of course, since those can be large.
This commit is contained in:
Loïc Hoguin 2017-05-28 19:04:16 +02:00
parent 8cb125dbb7
commit 5f421f93bc
No known key found for this signature in database
GPG key ID: 71366FF21851DF03
4 changed files with 88 additions and 8 deletions

View file

@ -134,7 +134,8 @@ timeout::
---- ----
opts() :: #{ opts() :: #{
compress => boolean(), compress => boolean(),
idle_timeout => timeout() idle_timeout => timeout(),
req_filter => fun((cowboy_req:req()) -> map())
} }
---- ----
@ -162,6 +163,13 @@ idle_timeout (60000)::
connection open without receiving anything from connection open without receiving anything from
the client. the client.
req_filter::
A function applied to the Req to compact it and
only keep required information. The Req is only
given back in the `terminate/3` callback. By default
it keeps the method, version, URI components and peer
information.
== Changelog == Changelog
* *2.0*: The Req object is no longer passed to Websocket callbacks. * *2.0*: The Req object is no longer passed to Websocket callbacks.

View file

@ -47,14 +47,13 @@
-callback websocket_info(any(), State) -callback websocket_info(any(), State)
-> call_result(State) when State::any(). -> call_result(State) when State::any().
%% @todo OK this I am not sure what to do about it. We don't have a Req anymore.
%% We probably should have a websocket_terminate instead.
-callback terminate(any(), cowboy_req:req(), any()) -> ok. -callback terminate(any(), cowboy_req:req(), any()) -> ok.
-optional_callbacks([terminate/3]). -optional_callbacks([terminate/3]).
-type opts() :: #{ -type opts() :: #{
compress => boolean(),
idle_timeout => timeout(), idle_timeout => timeout(),
compress => boolean() req_filter => fun((cowboy_req:req()) -> map())
}. }.
-export_type([opts/0]). -export_type([opts/0]).
@ -71,7 +70,8 @@
frag_state = undefined :: cow_ws:frag_state(), frag_state = undefined :: cow_ws:frag_state(),
frag_buffer = <<>> :: binary(), frag_buffer = <<>> :: binary(),
utf8_state = 0 :: cow_ws:utf8_state(), utf8_state = 0 :: cow_ws:utf8_state(),
extensions = #{} :: map() extensions = #{} :: map(),
req = #{} :: map()
}). }).
%% Stream process. %% Stream process.
@ -90,7 +90,11 @@ upgrade(Req, Env, Handler, HandlerState) ->
upgrade(Req0, Env, Handler, HandlerState, Opts) -> upgrade(Req0, Env, Handler, HandlerState, Opts) ->
Timeout = maps:get(idle_timeout, Opts, 60000), Timeout = maps:get(idle_timeout, Opts, 60000),
Compress = maps:get(compress, Opts, false), Compress = maps:get(compress, Opts, false),
State0 = #state{handler=Handler, timeout=Timeout, compress=Compress}, FilteredReq = case maps:get(req_filter, Opts, undefined) of
undefined -> maps:with([method, version, scheme, host, port, path, qs, peer], Req0);
FilterFun -> FilterFun(Req0)
end,
State0 = #state{handler=Handler, timeout=Timeout, compress=Compress, req=FilteredReq},
try websocket_upgrade(State0, Req0) of try websocket_upgrade(State0, Req0) of
{ok, State, Req} -> {ok, State, Req} ->
websocket_handshake(State, Req, HandlerState, Env) websocket_handshake(State, Req, HandlerState, Env)
@ -417,5 +421,5 @@ terminate(State, HandlerState, Reason) ->
handler_terminate(State, HandlerState, Reason), handler_terminate(State, HandlerState, Reason),
exit(normal). exit(normal).
handler_terminate(#state{handler=Handler}, HandlerState, Reason) -> handler_terminate(#state{handler=Handler, req=Req}, HandlerState, Reason) ->
cowboy_handler:terminate(Reason, undefined, HandlerState, Handler). cowboy_handler:terminate(Reason, Req, HandlerState, Handler).

View file

@ -0,0 +1,34 @@
%% This module sends a message with terminate arguments to the test case process.
-module(ws_terminate_h).
-behavior(cowboy_websocket).
-export([init/2]).
-export([websocket_init/1]).
-export([websocket_handle/2]).
-export([websocket_info/2]).
-export([terminate/3]).
-record(state, {
pid
}).
init(Req, _) ->
Pid = list_to_pid(binary_to_list(cowboy_req:header(<<"x-test-pid">>, Req))),
Opts = case cowboy_req:qs(Req) of
<<"req_filter">> -> #{req_filter => fun(_) -> filtered end};
_ -> #{}
end,
{cowboy_websocket, Req, #state{pid=Pid}, Opts}.
websocket_init(State) ->
{ok, State}.
websocket_handle(_, State) ->
{ok, State}.
websocket_info(_, State) ->
{ok, State}.
terminate(Reason, Req, #state{pid=Pid}) ->
Pid ! {terminate, Reason, Req}.

View file

@ -79,6 +79,7 @@ init_dispatch() ->
{text, <<"won't be received">>}]} {text, <<"won't be received">>}]}
]}, ]},
{"/ws_subprotocol", ws_subprotocol, []}, {"/ws_subprotocol", ws_subprotocol, []},
{"/terminate", ws_terminate_h, []},
{"/ws_timeout_hibernate", ws_timeout_hibernate, []}, {"/ws_timeout_hibernate", ws_timeout_hibernate, []},
{"/ws_timeout_cancel", ws_timeout_cancel, []} {"/ws_timeout_cancel", ws_timeout_cancel, []}
]} ]}
@ -355,6 +356,39 @@ ws_subprotocol(Config) ->
{_, "foo"} = lists:keyfind("sec-websocket-protocol", 1, Headers), {_, "foo"} = lists:keyfind("sec-websocket-protocol", 1, Headers),
ok. ok.
ws_terminate(Config) ->
doc("The Req object is kept in a more compact form by default."),
{ok, Socket, _} = do_handshake("/terminate",
"x-test-pid: " ++ pid_to_list(self()) ++ "\r\n", Config),
%% Send a close frame.
ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>),
{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
{error, closed} = gen_tcp:recv(Socket, 0, 6000),
%% Confirm terminate/3 was called with a compacted Req.
receive {terminate, _, Req} ->
true = maps:is_key(path, Req),
false = maps:is_key(headers, Req),
ok
after 1000 ->
error(timeout)
end.
ws_terminate_fun(Config) ->
doc("A function can be given to filter the Req object."),
{ok, Socket, _} = do_handshake("/terminate?req_filter",
"x-test-pid: " ++ pid_to_list(self()) ++ "\r\n", Config),
%% Send a close frame.
ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>),
{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
{error, closed} = gen_tcp:recv(Socket, 0, 6000),
%% Confirm terminate/3 was called with a compacted Req.
receive {terminate, _, Req} ->
filtered = Req,
ok
after 1000 ->
error(timeout)
end.
ws_text_fragments(Config) -> ws_text_fragments(Config) ->
doc("Client sends fragmented text frames."), doc("Client sends fragmented text frames."),
{ok, Socket, _} = do_handshake("/ws_echo", Config), {ok, Socket, _} = do_handshake("/ws_echo", Config),