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

Add {set_options, #{metrics_user_data := Map}}

This allows giving custom metadata to the metrics stream handler.
This can be useful to for example provide the name of the
module handling the request which is only known after routing.
But any user data is allowed.

When called multiple times the user data maps are merged.
This commit is contained in:
Loïc Hoguin 2019-10-02 15:23:23 +02:00
parent a14ecf19c6
commit f673e191b3
No known key found for this signature in database
GPG key ID: 8A9DF795F6FED764
3 changed files with 43 additions and 14 deletions

View file

@ -102,7 +102,10 @@
%% Length of the request and response bodies. This does
%% not include the framing.
req_body_length => non_neg_integer(),
resp_body_length => non_neg_integer()
resp_body_length => non_neg_integer(),
%% Additional metadata set by the user.
user_data => map()
}.
-export_type([metrics/0]).
@ -126,7 +129,8 @@
procs = #{} :: proc_metrics(),
informational = [] :: [informational_metrics()],
req_body_length = 0 :: non_neg_integer(),
resp_body_length = 0 :: non_neg_integer()
resp_body_length = 0 :: non_neg_integer(),
user_data = #{} :: map()
}).
-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())
@ -255,6 +259,14 @@ fold([{data, fin, Data}|Tail], State=#state{resp_body_length=RespBodyLen}) ->
resp_end=RespEnd,
resp_body_length=RespBodyLen + resp_body_length(Data)
});
fold([{set_options, SetOpts}|Tail], State0=#state{user_data=OldUserData}) ->
State = case SetOpts of
#{metrics_user_data := NewUserData} ->
State0#state{user_data=maps:merge(OldUserData, NewUserData)};
_ ->
State0
end,
fold(Tail, State);
fold([_|Tail], State) ->
fold(Tail, State).
@ -263,7 +275,7 @@ terminate(StreamID, Reason, #state{next=Next, callback=Fun,
req=Req, resp_status=RespStatus, resp_headers=RespHeaders, ref=Ref,
req_start=ReqStart, req_body_start=ReqBodyStart,
req_body_end=ReqBodyEnd, resp_start=RespStart, resp_end=RespEnd,
procs=Procs, informational=Infos,
procs=Procs, informational=Infos, user_data=UserData,
req_body_length=ReqBodyLen, resp_body_length=RespBodyLen}) ->
Res = cowboy_stream:terminate(StreamID, Reason, Next),
ReqEnd = erlang:monotonic_time(),
@ -284,7 +296,8 @@ terminate(StreamID, Reason, #state{next=Next, callback=Fun,
procs => Procs,
informational => lists:reverse(Infos),
req_body_length => ReqBodyLen,
resp_body_length => RespBodyLen
resp_body_length => RespBodyLen,
user_data => UserData
},
Fun(Metrics),
Res.

View file

@ -32,4 +32,9 @@ set_options(<<"idle_timeout_long">>, Req0, State) ->
#{pid := Pid, streamid := StreamID} = Req0,
Pid ! {{Pid, StreamID}, {set_options, #{idle_timeout => 60000}}},
{_, Body, Req} = cowboy_req:read_body(Req0),
{ok, cowboy_req:reply(200, #{}, Body, Req), State}.
{ok, cowboy_req:reply(200, #{}, Body, Req), State};
set_options(<<"metrics_user_data">>, Req, State) ->
%% @todo This should be replaced by a cowboy_req:cast/cowboy_stream:cast.
#{pid := Pid, streamid := StreamID} = Req,
Pid ! {{Pid, StreamID}, {set_options, #{metrics_user_data => #{handler => ?MODULE}}}},
{ok, cowboy_req:reply(200, #{}, <<"Hello world!">>, Req), State}.

View file

@ -76,6 +76,7 @@ init_routes(_) -> [
{"/default", default_h, []},
{"/full/:key", echo_h, []},
{"/resp/:key[/:arg]", resp_h, []},
{"/set_options/:key", set_options_h, []},
{"/ws_echo", ws_echo, []}
]}
].
@ -98,9 +99,13 @@ do_metrics_callback() ->
hello_world(Config) ->
doc("Confirm metrics are correct for a normal GET request."),
do_get("/", Config).
do_get("/", #{}, Config).
do_get(Path, Config) ->
user_data(Config) ->
doc("Confirm user data can be attached to metrics."),
do_get("/set_options/metrics_user_data", #{handler => set_options_h}, Config).
do_get(Path, UserData, Config) ->
%% Perform a GET request.
ConnPid = gun_open(Config),
Ref = gun:get(ConnPid, Path, [
@ -153,7 +158,8 @@ do_get(Path, Config) ->
streamid := 1,
reason := normal,
req := #{},
informational := []
informational := [],
user_data := UserData
} = Metrics,
%% All good!
ok
@ -216,7 +222,8 @@ post_body(Config) ->
streamid := 1,
reason := normal,
req := #{},
informational := []
informational := [],
user_data := #{}
} = Metrics,
%% All good!
ok
@ -273,7 +280,8 @@ no_resp_body(Config) ->
streamid := 1,
reason := normal,
req := #{},
informational := []
informational := [],
user_data := #{}
} = Metrics,
%% All good!
ok
@ -361,7 +369,7 @@ do_early_error_request_line(Config) ->
%% This test is identical to normal GET except for the handler.
stream_reply(Config) ->
doc("Confirm metrics are correct for long polling."),
do_get("/resp/stream_reply2/200", Config).
do_get("/resp/stream_reply2/200", #{}, Config).
ws(Config) ->
case config(protocol, Config) of
@ -421,7 +429,8 @@ do_ws(Config) ->
<<"sec-websocket-accept">> := _
},
time := _
}]
}],
user_data := #{}
} = Metrics,
%% All good!
ok
@ -487,7 +496,8 @@ error_response(Config) ->
streamid := 1,
reason := {internal_error, {'EXIT', _Pid, {crash, _StackTrace}}, 'Stream process crashed.'},
req := #{},
informational := []
informational := [],
user_data := #{}
} = Metrics,
%% All good!
ok
@ -546,7 +556,8 @@ error_response_after_reply(Config) ->
streamid := 1,
reason := {internal_error, {'EXIT', _Pid, {crash, _StackTrace}}, 'Stream process crashed.'},
req := #{},
informational := []
informational := [],
user_data := #{}
} = Metrics,
%% All good!
ok