aboutsummaryrefslogtreecommitdiff
path: root/apps/dreki_web/src/dreki_web_handler.erl
diff options
context:
space:
mode:
Diffstat (limited to 'apps/dreki_web/src/dreki_web_handler.erl')
-rw-r--r--apps/dreki_web/src/dreki_web_handler.erl99
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.