diff options
author | Jordan Bracco <href@random.sh> | 2022-04-17 03:25:09 +0000 |
---|---|---|
committer | Jordan Bracco <href@random.sh> | 2022-04-17 03:25:09 +0000 |
commit | 93d3514676cad95b94bbb3e483d02b7ea0076bba (patch) | |
tree | 0f7548c1cd274ec01873b5ebf39b6a39bea4e282 /apps/dreki_web/src/dreki_web_handler.erl | |
parent | guess it was time for an initial commit (diff) |
Diffstat (limited to 'apps/dreki_web/src/dreki_web_handler.erl')
-rw-r--r-- | apps/dreki_web/src/dreki_web_handler.erl | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/apps/dreki_web/src/dreki_web_handler.erl b/apps/dreki_web/src/dreki_web_handler.erl new file mode 100644 index 0000000..a30fd1d --- /dev/null +++ b/apps/dreki_web/src/dreki_web_handler.erl @@ -0,0 +1,99 @@ +%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu> +%% +%% 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. + +%% Handler middleware. +%% +%% Execute the handler given by the <em>handler</em> and <em>handler_opts</em> +%% environment values. The result of this execution is added to the +%% environment under the <em>result</em> value. +-module(dreki_web_handler). +-include_lib("opentelemetry_api/include/otel_tracer.hrl"). +-behaviour(cowboy_middleware). + +-export([execute/2]). +-export([terminate/4]). + +-callback init(Req, any()) + -> {ok | module(), Req, any()} + | {module(), Req, any(), any()} + when Req::cowboy_req:req(). + +-callback terminate(any(), map(), any()) -> ok. +-optional_callbacks([terminate/3]). + +-spec execute(Req, Env) -> {ok, Req, Env} + when Req::cowboy_req:req(), Env::cowboy_middleware:env(). +execute(Req, Env=#{handler := Handler}) -> + Headers = maps:get(headers, Req), + otel_propagator_text_map:extract(maps:to_list(Headers)), + HandlerB = atom_to_binary(Handler), + Method = maps:get(method, Req), + Attributes = [ + {'http.host', maps:get(host, Req)}, + {'http.host.port', maps:get(port, Req)}, + {'http.method', Method}, + {'http.scheme', maps:get(scheme, Req)}, + {'http.target', maps:get(path, Req)}, + {'http.user_agent', maps:get(<<"user-agent">>, Headers, <<"">>)} + ], + SpanName = iolist_to_binary([<<"HTTP ">>, Method, <<" ">>, HandlerB]), + logger:debug("SpanName: ~p", [SpanName]), + ?with_span(SpanName, #{}, fun(Ctx) -> execute_(Req, Env, Ctx) end). + +execute_(Req0, Env=#{handler := Handler, handler_opts := HandlerOpts}, Ctx) -> + TraceId = otel_span:hex_trace_id(otel_tracer:current_span_ctx()), + Req = Req0#{<<"trace-id">> => TraceId}, + try Handler:init(Req, HandlerOpts) of + {ok, Req2, State} -> + Headers0 = maps:get(headers, Req2), + Headers = otel_propagator_text_map:inject(Headers0, + fun(Headers, Key, Value) -> + maps:put(Key, Value, Headers) + end), + Req3 = maps:put(headers, Headers, Req2), + Result = terminate(normal, Req3, State, Handler), + {ok, Req3, Env#{result => Result, trace_id => TraceId}}; + {Mod, Req2, State} -> + ?add_event(<<"HTTP_UPGRADE">>, #{module => Mod}), + Mod:upgrade(Req2, Env, Handler, State); + {Mod, Req2, State, Opts} -> + ?add_event(<<"HTTP_UPGRADE">>, #{module => Mod}), + Mod:upgrade(Req2, Env, Handler, State, Opts) + catch Class:Reason:Stacktrace -> + ?set_status(error, iolist_to_binary([atom_to_binary(Class)])), + render_error(Class, Reason, Stacktrace, Handler, Req, Env, Ctx) + end. + +render_error(Class, Reason, Stacktrace, Handler, Req, Env, Ctx) -> + ReasonS = lists:flatten(io_lib:format("~p", [Reason])), + Assigns = [ + {dreki_node, node()}, + {site_title, "Dreki"}, + {class, atom_to_binary(Class)}, + {reason, ReasonS}, + {stacktrace, [lists:flatten(io_lib:format("~p", [Line])) || Line <- Stacktrace]}, + {trace_id, maps:get(<<"trace-id">>, Req, <<"no-trace">>)} + ], + logger:error(#{app => dreki_web, handler => Handler, error => {Class, Reason, Stacktrace}}), + {ok, Html} = crash_dtl:render(Assigns), + cowboy_req:reply(500, #{<<"content-type">> => <<"text/html">>}, Html, Req). + +-spec terminate(any(), Req | undefined, any(), module()) -> ok when Req::cowboy_req:req(). +terminate(Reason, Req, State, Handler) -> + case erlang:function_exported(Handler, terminate, 3) of + true -> + Handler:terminate(Reason, Req, State); + false -> + ok + end. |