summaryrefslogtreecommitdiff
path: root/deps/rabbitmq_web_dispatch/src
diff options
context:
space:
mode:
Diffstat (limited to 'deps/rabbitmq_web_dispatch/src')
-rw-r--r--deps/rabbitmq_web_dispatch/src/rabbit_cowboy_middleware.erl24
-rw-r--r--deps/rabbitmq_web_dispatch/src/rabbit_cowboy_redirect.erl15
-rw-r--r--deps/rabbitmq_web_dispatch/src/rabbit_cowboy_stream_h.erl63
-rw-r--r--deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch.erl88
-rw-r--r--deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_app.erl21
-rw-r--r--deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_listing_handler.erl27
-rw-r--r--deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_registry.erl208
-rw-r--r--deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_sup.erl131
-rw-r--r--deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_util.erl55
-rw-r--r--deps/rabbitmq_web_dispatch/src/webmachine_log.erl239
-rw-r--r--deps/rabbitmq_web_dispatch/src/webmachine_log_handler.erl128
-rw-r--r--deps/rabbitmq_web_dispatch/src/webmachine_logger.hrl16
12 files changed, 1015 insertions, 0 deletions
diff --git a/deps/rabbitmq_web_dispatch/src/rabbit_cowboy_middleware.erl b/deps/rabbitmq_web_dispatch/src/rabbit_cowboy_middleware.erl
new file mode 100644
index 0000000000..c8186619f6
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_cowboy_middleware.erl
@@ -0,0 +1,24 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_cowboy_middleware).
+-behavior(cowboy_middleware).
+
+-export([execute/2]).
+
+execute(Req, Env) ->
+ %% Find the correct dispatch list for this path.
+ Listener = maps:get(rabbit_listener, Env),
+ case rabbit_web_dispatch_registry:lookup(Listener, Req) of
+ {ok, Dispatch} ->
+ {ok, Req, maps:put(dispatch, Dispatch, Env)};
+ {error, Reason} ->
+ Req2 = cowboy_req:reply(500,
+ #{<<"content-type">> => <<"text/plain">>},
+ "Registry Error: " ++ io_lib:format("~p", [Reason]), Req),
+ {stop, Req2}
+ end.
diff --git a/deps/rabbitmq_web_dispatch/src/rabbit_cowboy_redirect.erl b/deps/rabbitmq_web_dispatch/src/rabbit_cowboy_redirect.erl
new file mode 100644
index 0000000000..9271567d91
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_cowboy_redirect.erl
@@ -0,0 +1,15 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_cowboy_redirect).
+
+-export([init/2]).
+
+init(Req0, RedirectPort) ->
+ URI = cowboy_req:uri(Req0, #{port => RedirectPort}),
+ Req = cowboy_req:reply(301, #{<<"location">> => URI}, Req0),
+ {ok, Req, RedirectPort}.
diff --git a/deps/rabbitmq_web_dispatch/src/rabbit_cowboy_stream_h.erl b/deps/rabbitmq_web_dispatch/src/rabbit_cowboy_stream_h.erl
new file mode 100644
index 0000000000..d51086a69d
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_cowboy_stream_h.erl
@@ -0,0 +1,63 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_cowboy_stream_h).
+-behavior(cowboy_stream).
+
+-export([init/3]).
+-export([data/4]).
+-export([info/3]).
+-export([terminate/3]).
+-export([early_error/5]).
+
+-record(state, {
+ next :: any(),
+ req :: cowboy_req:req()
+}).
+
+init(StreamId, Req, Opts) ->
+ {Commands, Next} = cowboy_stream:init(StreamId, Req, Opts),
+ {Commands, #state{next = Next, req = Req}}.
+
+data(StreamId, IsFin, Data, State = #state{next = Next}) ->
+ {Commands, Next1} = cowboy_stream:data(StreamId, IsFin, Data, Next),
+ {Commands, State#state{next = Next1}}.
+
+info(StreamId, Response, State = #state{next = Next, req = Req}) ->
+ Response1 = case Response of
+ {response, 404, Headers0, <<>>} ->
+ %% TODO: should we log the actual response?
+ log_response(Response, Req),
+ Json = rabbit_json:encode(#{
+ error => list_to_binary(httpd_util:reason_phrase(404)),
+ reason => <<"Not Found">>}),
+ Headers1 = maps:put(<<"content-length">>, integer_to_list(iolist_size(Json)), Headers0),
+ Headers = maps:put(<<"content-type">>, <<"application/json">>, Headers1),
+ {response, 404, Headers, Json};
+ {response, _, _, _} ->
+ log_response(Response, Req),
+ Response;
+ {headers, _, _} ->
+ log_stream_response(Response, Req),
+ Response;
+ _ ->
+ Response
+ end,
+ {Commands, Next1} = cowboy_stream:info(StreamId, Response1, Next),
+ {Commands, State#state{next = Next1}}.
+
+terminate(StreamId, Reason, #state{next = Next}) ->
+ cowboy_stream:terminate(StreamId, Reason, Next).
+
+early_error(StreamId, Reason, PartialReq, Resp, Opts) ->
+ cowboy_stream:early_error(StreamId, Reason, PartialReq, Resp, Opts).
+
+log_response({response, Status, _Headers, Body}, Req) ->
+ webmachine_log:log_access({Status, Body, Req}).
+
+log_stream_response({headers, Status, _Headers}, Req) ->
+ webmachine_log:log_access({Status, <<>>, Req}).
diff --git a/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch.erl b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch.erl
new file mode 100644
index 0000000000..24a81a986a
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch.erl
@@ -0,0 +1,88 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_web_dispatch).
+
+-export([register_context_handler/5, register_static_context/6]).
+-export([register_port_redirect/4]).
+-export([unregister_context/1]).
+
+%% Handler Registration
+
+%% Registers a dynamic selector and handler combination, with a link
+%% to display in lists.
+register_handler(Name, Listener, Selector, Handler, Link) ->
+ rabbit_web_dispatch_registry:add(Name, Listener, Selector, Handler, Link).
+
+%% Methods for standard use cases
+
+%% Registers a dynamic handler under a fixed context path, with link
+%% to display in the global context.
+register_context_handler(Name, Listener, Prefix, Handler, LinkText) ->
+ register_handler(
+ Name, Listener, context_selector(Prefix), Handler, {Prefix, LinkText}),
+ {ok, Prefix}.
+
+%% Convenience function registering a fully static context to serve
+%% content from a module-relative directory, with link to display in
+%% the global context.
+register_static_context(Name, Listener, Prefix, Module, FSPath, LinkText) ->
+ register_handler(Name, Listener,
+ context_selector(Prefix),
+ static_context_handler(Prefix, Module, FSPath),
+ {Prefix, LinkText}),
+ {ok, Prefix}.
+
+%% A context which just redirects the request to a different port.
+register_port_redirect(Name, Listener, Prefix, RedirectPort) ->
+ register_context_handler(
+ Name, Listener, Prefix,
+ cowboy_router:compile([{'_', [{'_', rabbit_cowboy_redirect, RedirectPort}]}]),
+ rabbit_misc:format("Redirect to port ~B", [RedirectPort])).
+
+context_selector("") ->
+ fun(_Req) -> true end;
+context_selector(Prefix) ->
+ Prefix1 = list_to_binary("/" ++ Prefix),
+ fun(Req) ->
+ Path = cowboy_req:path(Req),
+ (Path == Prefix1) orelse (binary:match(Path, << Prefix1/binary, $/ >>) =/= nomatch)
+ end.
+
+%% Produces a handler for use with register_handler that serves up
+%% static content from a directory specified relative to the application
+%% (owning the module) priv directory, or to the directory containing
+%% the ebin directory containing the named module's beam file.
+static_context_handler(Prefix, Module, FSPath) ->
+ FSPathInPriv = re:replace(FSPath, "^priv/?", "", [{return, list}]),
+ case application:get_application(Module) of
+ {ok, App} when FSPathInPriv =/= FSPath ->
+ %% The caller indicated a file in the application's `priv`
+ %% dir.
+ cowboy_router:compile([{'_', [
+ {"/" ++ Prefix, cowboy_static,
+ {priv_file, App, filename:join(FSPathInPriv, "index.html")}},
+ {"/" ++ Prefix ++ "/[...]", cowboy_static,
+ {priv_dir, App, FSPathInPriv}}
+ ]}]);
+ _ ->
+ %% The caller indicated a file we should access directly.
+ {file, Here} = code:is_loaded(Module),
+ ModuleRoot = filename:dirname(filename:dirname(Here)),
+ LocalPath = filename:join(ModuleRoot, FSPath),
+ cowboy_router:compile([{'_', [
+ {"/" ++ Prefix, cowboy_static,
+ {file, LocalPath ++ "/index.html"}},
+ {"/" ++ Prefix ++ "/[...]", cowboy_static,
+ {dir, LocalPath}}
+ ]}])
+ end.
+
+%% The opposite of all those register_* functions.
+unregister_context(Name) ->
+ rabbit_web_dispatch_registry:remove(Name).
+
diff --git a/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_app.erl b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_app.erl
new file mode 100644
index 0000000000..1de05c77d7
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_app.erl
@@ -0,0 +1,21 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_web_dispatch_app).
+
+-behaviour(application).
+-export([start/2,stop/1]).
+
+%% @spec start(_Type, _StartArgs) -> ServerRet
+%% @doc application start callback for rabbit_web_dispatch.
+start(_Type, _StartArgs) ->
+ rabbit_web_dispatch_sup:start_link().
+
+%% @spec stop(_State) -> ServerRet
+%% @doc application stop callback for rabbit_web_dispatch.
+stop(_State) ->
+ ok.
diff --git a/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_listing_handler.erl b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_listing_handler.erl
new file mode 100644
index 0000000000..93ee349af1
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_listing_handler.erl
@@ -0,0 +1,27 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_web_dispatch_listing_handler).
+
+-export([init/2]).
+
+init(Req0, Listener) ->
+ HTMLPrefix =
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">"
+ "<head><title>RabbitMQ Web Server</title></head>"
+ "<body><h1>RabbitMQ Web Server</h1><p>Contexts available:</p><ul>",
+ HTMLSuffix = "</ul></body></html>",
+ List =
+ case rabbit_web_dispatch_registry:list(Listener) of
+ [] ->
+ "<li>No contexts installed</li>";
+ Contexts ->
+ [["<li><a href=\"/", Path, "/\">", Desc, "</a></li>"]
+ || {Path, Desc} <- Contexts]
+ end,
+ Req = cowboy_req:reply(200, #{}, [HTMLPrefix, List, HTMLSuffix], Req0),
+ {ok, Req, Listener}.
diff --git a/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_registry.erl b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_registry.erl
new file mode 100644
index 0000000000..8d933ac668
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_registry.erl
@@ -0,0 +1,208 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_web_dispatch_registry).
+
+-behaviour(gen_server).
+
+-export([start_link/0]).
+-export([add/5, remove/1, set_fallback/2, lookup/2, list_all/0]).
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
+-export([list/1]).
+
+-import(rabbit_misc, [pget/2]).
+
+-define(ETS, rabbitmq_web_dispatch).
+
+%% This gen_server is merely to serialise modifications to the dispatch
+%% table for listeners.
+
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+add(Name, Listener, Selector, Handler, Link) ->
+ gen_server:call(?MODULE, {add, Name, Listener, Selector, Handler, Link},
+ infinity).
+
+remove(Name) ->
+ gen_server:call(?MODULE, {remove, Name}, infinity).
+
+%% @todo This needs to be dispatch instead of a fun too.
+%% But I'm not sure what code is using this.
+set_fallback(Listener, FallbackHandler) ->
+ gen_server:call(?MODULE, {set_fallback, Listener, FallbackHandler},
+ infinity).
+
+lookup(Listener, Req) ->
+ case lookup_dispatch(Listener) of
+ {ok, {Selectors, Fallback}} ->
+ case catch match_request(Selectors, Req) of
+ {'EXIT', Reason} -> {error, {lookup_failure, Reason}};
+ not_found -> {ok, Fallback};
+ Dispatch -> {ok, Dispatch}
+ end;
+ Err ->
+ Err
+ end.
+
+%% This is called in a somewhat obfuscated manner in
+%% rabbit_mgmt_external_stats:rabbit_web_dispatch_registry_list_all()
+list_all() ->
+ gen_server:call(?MODULE, list_all, infinity).
+
+%% Callback Methods
+
+init([]) ->
+ ?ETS = ets:new(?ETS, [named_table, public]),
+ {ok, undefined}.
+
+handle_call({add, Name, Listener, Selector, Handler, Link = {_, Desc}}, _From,
+ undefined) ->
+ Continue = case rabbit_web_dispatch_sup:ensure_listener(Listener) of
+ new -> set_dispatch(
+ Listener, [],
+ listing_fallback_handler(Listener)),
+ listener_started(Listener),
+ true;
+ existing -> true;
+ ignore -> false
+ end,
+ case Continue of
+ true -> case lookup_dispatch(Listener) of
+ {ok, {Selectors, Fallback}} ->
+ Selector2 = lists:keystore(
+ Name, 1, Selectors,
+ {Name, Selector, Handler, Link}),
+ set_dispatch(Listener, Selector2, Fallback);
+ {error, {different, Desc2, Listener2}} ->
+ exit({incompatible_listeners,
+ {Desc, Listener}, {Desc2, Listener2}})
+ end;
+ false -> ok
+ end,
+ {reply, ok, undefined};
+
+handle_call({remove, Name}, _From,
+ undefined) ->
+ case listener_by_name(Name) of
+ {error, not_found} ->
+ rabbit_log:warning("HTTP listener registry could not find context ~p",
+ [Name]),
+ {reply, ok, undefined};
+ {ok, Listener} ->
+ {ok, {Selectors, Fallback}} = lookup_dispatch(Listener),
+ Selectors1 = lists:keydelete(Name, 1, Selectors),
+ set_dispatch(Listener, Selectors1, Fallback),
+ case Selectors1 of
+ [] -> rabbit_web_dispatch_sup:stop_listener(Listener),
+ listener_stopped(Listener);
+ _ -> ok
+ end,
+ {reply, ok, undefined}
+ end;
+
+handle_call({set_fallback, Listener, FallbackHandler}, _From,
+ undefined) ->
+ {ok, {Selectors, _OldFallback}} = lookup_dispatch(Listener),
+ set_dispatch(Listener, Selectors, FallbackHandler),
+ {reply, ok, undefined};
+
+handle_call(list_all, _From, undefined) ->
+ {reply, list(), undefined};
+
+handle_call(Req, _From, State) ->
+ rabbit_log:error("Unexpected call to ~p: ~p~n", [?MODULE, Req]),
+ {stop, unknown_request, State}.
+
+handle_cast(_, State) ->
+ {noreply, State}.
+
+handle_info(_, State) ->
+ {noreply, State}.
+
+terminate(_, _) ->
+ true = ets:delete(?ETS),
+ ok.
+
+code_change(_, State, _) ->
+ {ok, State}.
+
+%%---------------------------------------------------------------------------
+
+%% Internal Methods
+
+listener_started(Listener) ->
+ [rabbit_networking:tcp_listener_started(Protocol, Listener, IPAddress, Port)
+ || {Protocol, IPAddress, Port} <- listener_info(Listener)],
+ ok.
+
+listener_stopped(Listener) ->
+ [rabbit_networking:tcp_listener_stopped(Protocol, Listener, IPAddress, Port)
+ || {Protocol, IPAddress, Port} <- listener_info(Listener)],
+ ok.
+
+listener_info(Listener) ->
+ Protocol = case pget(protocol, Listener) of
+ undefined ->
+ case pget(ssl, Listener) of
+ true -> https;
+ _ -> http
+ end;
+ P ->
+ P
+ end,
+ Port = pget(port, Listener),
+ [{Protocol, IPAddress, Port}
+ || {IPAddress, _Port, _Family}
+ <- rabbit_networking:tcp_listener_addresses(Port)].
+
+lookup_dispatch(Lsnr) ->
+ case ets:lookup(?ETS, pget(port, Lsnr)) of
+ [{_, Lsnr, S, F}] -> {ok, {S, F}};
+ [{_, Lsnr2, S, _F}] -> {error, {different, first_desc(S), Lsnr2}};
+ [] -> {error, {no_record_for_listener, Lsnr}}
+ end.
+
+first_desc([{_N, _S, _H, {_, Desc}} | _]) -> Desc.
+
+set_dispatch(Listener, Selectors, Fallback) ->
+ ets:insert(?ETS, {pget(port, Listener), Listener, Selectors, Fallback}).
+
+match_request([], _) ->
+ not_found;
+match_request([{_Name, Selector, Dispatch, _Link}|Rest], Req) ->
+ case Selector(Req) of
+ true -> Dispatch;
+ false -> match_request(Rest, Req)
+ end.
+
+list() ->
+ [{Path, Desc, Listener} ||
+ {_P, Listener, Selectors, _F} <- ets:tab2list(?ETS),
+ {_N, _S, _H, {Path, Desc}} <- Selectors].
+
+-spec listener_by_name(atom()) -> {ok, term()} | {error, not_found}.
+listener_by_name(Name) ->
+ case [L || {_P, L, S, _F} <- ets:tab2list(?ETS), contains_name(Name, S)] of
+ [Listener] -> {ok, Listener};
+ [] -> {error, not_found}
+ end.
+
+contains_name(Name, Selectors) ->
+ lists:member(Name, [N || {N, _S, _H, _L} <- Selectors]).
+
+list(Listener) ->
+ {ok, {Selectors, _Fallback}} = lookup_dispatch(Listener),
+ [{Path, Desc} || {_N, _S, _H, {Path, Desc}} <- Selectors].
+
+%%---------------------------------------------------------------------------
+
+listing_fallback_handler(Listener) ->
+ cowboy_router:compile([{'_', [
+ {"/", rabbit_web_dispatch_listing_handler, Listener}
+ ]}]).
diff --git a/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_sup.erl b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_sup.erl
new file mode 100644
index 0000000000..1d270550c0
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_sup.erl
@@ -0,0 +1,131 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_web_dispatch_sup).
+
+-behaviour(supervisor).
+
+-define(SUP, ?MODULE).
+
+%% External exports
+-export([start_link/0, ensure_listener/1, stop_listener/1]).
+
+%% supervisor callbacks
+-export([init/1]).
+
+%% @spec start_link() -> ServerRet
+%% @doc API for starting the supervisor.
+start_link() ->
+ supervisor:start_link({local, ?SUP}, ?MODULE, []).
+
+ensure_listener(Listener) ->
+ case proplists:get_value(port, Listener) of
+ undefined ->
+ {error, {no_port_given, Listener}};
+ _ ->
+ {Transport, TransportOpts, ProtoOpts} = preprocess_config(Listener),
+ ProtoOptsMap = maps:from_list(ProtoOpts),
+ StreamHandlers = stream_handlers_config(ProtoOpts),
+ rabbit_log:debug("Starting HTTP[S] listener with transport ~s, options ~p and protocol options ~p, stream handlers ~p",
+ [Transport, TransportOpts, ProtoOptsMap, StreamHandlers]),
+ CowboyOptsMap =
+ maps:merge(#{env =>
+ #{rabbit_listener => Listener},
+ middlewares =>
+ [rabbit_cowboy_middleware, cowboy_router, cowboy_handler],
+ stream_handlers => StreamHandlers},
+ ProtoOptsMap),
+ Child = ranch:child_spec(rabbit_networking:ranch_ref(Listener), 100,
+ Transport, TransportOpts,
+ cowboy_clear, CowboyOptsMap),
+ case supervisor:start_child(?SUP, Child) of
+ {ok, _} -> new;
+ {error, {already_started, _}} -> existing;
+ {error, {E, _}} -> check_error(Listener, E)
+ end
+ end.
+
+stop_listener(Listener) ->
+ Name = rabbit_networking:ranch_ref(Listener),
+ ok = supervisor:terminate_child(?SUP, {ranch_listener_sup, Name}),
+ ok = supervisor:delete_child(?SUP, {ranch_listener_sup, Name}).
+
+%% @spec init([[instance()]]) -> SupervisorTree
+%% @doc supervisor callback.
+init([]) ->
+ Registry = {rabbit_web_dispatch_registry,
+ {rabbit_web_dispatch_registry, start_link, []},
+ transient, 5000, worker, dynamic},
+ Log = {rabbit_mgmt_access_logger, {gen_event, start_link,
+ [{local, webmachine_log_event}]},
+ permanent, 5000, worker, [dynamic]},
+ {ok, {{one_for_one, 10, 10}, [Registry, Log]}}.
+
+%%
+%% Implementation
+%%
+
+preprocess_config(Options) ->
+ case proplists:get_value(ssl, Options) of
+ true -> _ = rabbit_networking:ensure_ssl(),
+ case rabbit_networking:poodle_check('HTTP') of
+ ok -> case proplists:get_value(ssl_opts, Options) of
+ undefined -> auto_ssl(Options);
+ _ -> fix_ssl(Options)
+ end;
+ danger -> {ranch_tcp, transport_config(Options), protocol_config(Options)}
+ end;
+ _ -> {ranch_tcp, transport_config(Options), protocol_config(Options)}
+ end.
+
+auto_ssl(Options) ->
+ {ok, ServerOpts} = application:get_env(rabbit, ssl_options),
+ Remove = [verify, fail_if_no_peer_cert],
+ SSLOpts = [{K, V} || {K, V} <- ServerOpts,
+ not lists:member(K, Remove)],
+ fix_ssl([{ssl_opts, SSLOpts} | Options]).
+
+fix_ssl(Options) ->
+ SSLOpts = proplists:get_value(ssl_opts, Options),
+ {ranch_ssl,
+ transport_config(Options ++ rabbit_networking:fix_ssl_options(SSLOpts)),
+ protocol_config(Options)}.
+
+transport_config(Options0) ->
+ Options = proplists:delete(protocol,
+ proplists:delete(ssl,
+ proplists:delete(ssl_opts,
+ proplists:delete(cowboy_opts,
+ Options0)))),
+ case proplists:get_value(ip, Options) of
+ undefined ->
+ Options;
+ IP when is_tuple(IP) ->
+ Options;
+ IP when is_list(IP) ->
+ {ok, ParsedIP} = inet_parse:address(IP),
+ [{ip, ParsedIP}|proplists:delete(ip, Options)]
+ end.
+
+protocol_config(Options) ->
+ proplists:get_value(cowboy_opts, Options, []).
+
+stream_handlers_config(Options) ->
+ case lists:keyfind(compress, 1, Options) of
+ {compress, false} -> [rabbit_cowboy_stream_h, cowboy_stream_h];
+ %% Compress by default. Since 2.0 the compress option in cowboys
+ %% has been replaced by the cowboy_compress_h handler
+ %% Compress is not applied if data < 300 bytes
+ _ -> [rabbit_cowboy_stream_h, cowboy_compress_h, cowboy_stream_h]
+ end.
+
+check_error(Listener, Error) ->
+ Ignore = proplists:get_value(ignore_in_use, Listener, false),
+ case {Error, Ignore} of
+ {eaddrinuse, true} -> ignore;
+ _ -> exit({could_not_start_listener, Listener, Error})
+ end.
diff --git a/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_util.erl b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_util.erl
new file mode 100644
index 0000000000..8aca673a48
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_util.erl
@@ -0,0 +1,55 @@
+%% This Source Code Form is subject to the terms of the Mozilla Public
+%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
+%%
+%% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved.
+%%
+
+-module(rabbit_web_dispatch_util).
+
+-export([parse_auth_header/1]).
+-export([relativise/2, unrelativise/2]).
+
+%% @todo remove
+parse_auth_header(Header) ->
+ case Header of
+ "Basic " ++ Base64 ->
+ Str = base64:mime_decode_to_string(Base64),
+ case string:chr(Str, $:) of
+ 0 -> invalid;
+ N -> [list_to_binary(string:sub_string(Str, 1, N - 1)),
+ list_to_binary(string:sub_string(Str, N + 1))]
+ end;
+ _ ->
+ invalid
+ end.
+
+relativise("/" ++ F, "/" ++ T) ->
+ From = string:tokens(F, "/"),
+ To = string:tokens(T, "/"),
+ string:join(relativise0(From, To), "/").
+
+relativise0([H], [H|_] = To) ->
+ To;
+relativise0([H|From], [H|To]) ->
+ relativise0(From, To);
+relativise0(From, []) ->
+ lists:duplicate(length(From), "..");
+relativise0([_|From], To) ->
+ lists:duplicate(length(From), "..") ++ To;
+relativise0([], To) ->
+ To.
+
+unrelativise(_, "/" ++ T) -> "/" ++ T;
+unrelativise(F, "./" ++ T) -> unrelativise(F, T);
+unrelativise(F, "../" ++ T) -> unrelativise(strip_tail(F), T);
+unrelativise(F, T) -> case string:str(F, "/") of
+ 0 -> T;
+ _ -> strip_tail(F) ++ "/" ++ T
+ end.
+
+strip_tail("") -> exit(not_enough_to_strip);
+strip_tail(S) -> case string:rstr(S, "/") of
+ 0 -> "";
+ I -> string:left(S, I - 1)
+ end.
diff --git a/deps/rabbitmq_web_dispatch/src/webmachine_log.erl b/deps/rabbitmq_web_dispatch/src/webmachine_log.erl
new file mode 100644
index 0000000000..dd4da91924
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/webmachine_log.erl
@@ -0,0 +1,239 @@
+%% Copyright (c) 2011-2012 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% https://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+%% @doc Helper functions for webmachine's default log handlers
+
+-module(webmachine_log).
+
+-include("webmachine_logger.hrl").
+
+-export([add_handler/2,
+ call/2,
+ call/3,
+ datehour/0,
+ datehour/1,
+ defer_refresh/1,
+ delete_handler/1,
+ fix_log/2,
+ fmt_ip/1,
+ fmtnow/0,
+ log_access/1,
+ log_close/3,
+ log_open/1,
+ log_open/2,
+ log_write/2,
+ maybe_rotate/3,
+ month/1,
+ refresh/2,
+ suffix/1,
+ zeropad/2,
+ zone/0]).
+
+-record(state, {hourstamp :: non_neg_integer(),
+ filename :: string(),
+ handle :: file:io_device()}).
+
+%% @doc Add a handler to receive log events
+-type add_handler_result() :: ok | {'EXIT', term()} | term().
+-spec add_handler(atom() | {atom(), term()}, term()) -> add_handler_result().
+add_handler(Mod, Args) ->
+ gen_event:add_handler(?EVENT_LOGGER, Mod, Args).
+
+%% @doc Make a synchronous call directly to a specific event handler
+%% module
+-type error() :: {error, bad_module} | {'EXIT', term()} | term().
+-spec call(atom(), term()) -> term() | error().
+call(Mod, Msg) ->
+ gen_event:call(?EVENT_LOGGER, Mod, Msg).
+
+%% @doc Make a synchronous call directly to a specific event handler
+%% module
+-spec call(atom(), term(), timeout()) -> term() | error().
+call(Mod, Msg, Timeout) ->
+ gen_event:call(?EVENT_LOGGER, Mod, Msg, Timeout).
+
+%% @doc Return a four-tuple containing year, month, day, and hour
+%% of the current time.
+-type datehour() :: {calendar:year(), calendar:month(), calendar:day(), calendar:hour()}.
+-spec datehour() -> datehour().
+datehour() ->
+ datehour(os:timestamp()).
+
+%% @doc Return a four-tuple containing year, month, day, and hour
+%% of the specified time.
+-spec datehour(erlang:timestamp()) -> datehour().
+datehour(TS) ->
+ {{Y, M, D}, {H, _, _}} = calendar:now_to_universal_time(TS),
+ {Y, M, D, H}.
+
+%% @doc Defer the refresh of a log file.
+-spec defer_refresh(atom()) -> {ok, timer:tref()} | {error, term()}.
+defer_refresh(Mod) ->
+ {_, {_, M, S}} = calendar:universal_time(),
+ Time = 1000 * (3600 - ((M * 60) + S)),
+ timer:apply_after(Time, ?MODULE, refresh, [Mod, os:timestamp()]).
+
+%% @doc Remove a log handler
+-type delete_handler_result() :: term() | {error, module_not_found} | {'EXIT', term()}.
+-spec delete_handler(atom() | {atom(), term()}) -> delete_handler_result().
+delete_handler(Mod) ->
+ gen_event:delete_handler(?EVENT_LOGGER, Mod, []).
+
+%% Seek backwards to the last valid log entry
+-spec fix_log(file:io_device(), non_neg_integer()) -> ok.
+fix_log(_FD, 0) ->
+ ok;
+fix_log(FD, 1) ->
+ {ok, 0} = file:position(FD, 0),
+ ok;
+fix_log(FD, Location) ->
+ case file:pread(FD, Location - 1, 1) of
+ {ok, [$\n | _]} ->
+ ok;
+ {ok, _} ->
+ fix_log(FD, Location - 1)
+ end.
+
+%% @doc Format an IP address or host name
+-spec fmt_ip(undefined | string() | inet:ip4_address() | inet:ip6_address()) -> string().
+fmt_ip(IP) when is_tuple(IP) ->
+ inet_parse:ntoa(IP);
+fmt_ip(undefined) ->
+ "0.0.0.0";
+fmt_ip(HostName) ->
+ HostName.
+
+%% @doc Format the current time into a string
+-spec fmtnow() -> string().
+fmtnow() ->
+ {{Year, Month, Date}, {Hour, Min, Sec}} = calendar:local_time(),
+ io_lib:format("[~2..0w/~s/~4..0w:~2..0w:~2..0w:~2..0w ~s]",
+ [Date,month(Month),Year, Hour, Min, Sec, zone()]).
+
+%% @doc Notify registered log event handler of an access event.
+-spec log_access(tuple()) -> ok.
+log_access({_, _, _}=LogData) ->
+ gen_event:sync_notify(?EVENT_LOGGER, {log_access, LogData}).
+
+%% @doc Close a log file.
+-spec log_close(atom(), string(), file:io_device()) -> ok | {error, term()}.
+log_close(Mod, Name, FD) ->
+ error_logger:info_msg("~p: closing log file: ~p~n", [Mod, Name]),
+ file:close(FD).
+
+%% @doc Open a new log file for writing
+-spec log_open(string()) -> {file:io_device(), non_neg_integer()}.
+log_open(FileName) ->
+ DateHour = datehour(),
+ {log_open(FileName, DateHour), DateHour}.
+
+%% @doc Open a new log file for writing
+-spec log_open(string(), non_neg_integer()) -> file:io_device().
+log_open(FileName, DateHour) ->
+ LogName = FileName ++ suffix(DateHour),
+ error_logger:info_msg("opening log file: ~p~n", [LogName]),
+ filelib:ensure_dir(LogName),
+ {ok, FD} = file:open(LogName, [read, write, raw]),
+ {ok, Location} = file:position(FD, eof),
+ fix_log(FD, Location),
+ file:truncate(FD),
+ FD.
+
+-spec log_write(file:io_device(), iolist()) -> ok | {error, term()}.
+log_write(FD, IoData) ->
+ file:write(FD, lists:flatten(IoData)).
+
+%% @doc Rotate a log file if the hour it represents
+%% has passed.
+-spec maybe_rotate(atom(), erlang:timestamp(), #state{}) -> #state{}.
+maybe_rotate(Mod, Time, State) ->
+ ThisHour = datehour(Time),
+ if ThisHour == State#state.hourstamp ->
+ State;
+ true ->
+ defer_refresh(Mod),
+ log_close(Mod, State#state.filename, State#state.handle),
+ Handle = log_open(State#state.filename, ThisHour),
+ State#state{hourstamp=ThisHour, handle=Handle}
+ end.
+
+%% @doc Convert numeric month value to the abbreviation
+-spec month(1..12) -> string().
+month(1) ->
+ "Jan";
+month(2) ->
+ "Feb";
+month(3) ->
+ "Mar";
+month(4) ->
+ "Apr";
+month(5) ->
+ "May";
+month(6) ->
+ "Jun";
+month(7) ->
+ "Jul";
+month(8) ->
+ "Aug";
+month(9) ->
+ "Sep";
+month(10) ->
+ "Oct";
+month(11) ->
+ "Nov";
+month(12) ->
+ "Dec".
+
+%% @doc Make a synchronous call to instruct a log handler to refresh
+%% itself.
+-spec refresh(atom(), erlang:timestamp()) -> ok | {error, term()}.
+refresh(Mod, Time) ->
+ call(Mod, {refresh, Time}, infinity).
+
+-spec suffix(datehour()) -> string().
+suffix({Y, M, D, H}) ->
+ YS = zeropad(Y, 4),
+ MS = zeropad(M, 2),
+ DS = zeropad(D, 2),
+ HS = zeropad(H, 2),
+ lists:flatten([$., YS, $_, MS, $_, DS, $_, HS]).
+
+-spec zeropad(integer(), integer()) -> string().
+zeropad(Num, MinLength) ->
+ NumStr = integer_to_list(Num),
+ zeropad_str(NumStr, MinLength - length(NumStr)).
+
+-spec zeropad_str(string(), integer()) -> string().
+zeropad_str(NumStr, Zeros) when Zeros > 0 ->
+ zeropad_str([$0 | NumStr], Zeros - 1);
+zeropad_str(NumStr, _) ->
+ NumStr.
+
+-spec zone() -> string().
+zone() ->
+ Time = erlang:universaltime(),
+ LocalTime = calendar:universal_time_to_local_time(Time),
+ DiffSecs = calendar:datetime_to_gregorian_seconds(LocalTime) -
+ calendar:datetime_to_gregorian_seconds(Time),
+ zone((DiffSecs/3600)*100).
+
+%% Ugly reformatting code to get times like +0000 and -1300
+
+-spec zone(integer()) -> string().
+zone(Val) when Val < 0 ->
+ io_lib:format("-~4..0w", [trunc(abs(Val))]);
+zone(Val) when Val >= 0 ->
+ io_lib:format("+~4..0w", [trunc(abs(Val))]).
diff --git a/deps/rabbitmq_web_dispatch/src/webmachine_log_handler.erl b/deps/rabbitmq_web_dispatch/src/webmachine_log_handler.erl
new file mode 100644
index 0000000000..e19b247d73
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/webmachine_log_handler.erl
@@ -0,0 +1,128 @@
+%% Copyright (c) 2011-2013 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% https://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+%% @doc Default log handler for webmachine
+
+-module(webmachine_log_handler).
+
+-behaviour(gen_event).
+
+%% gen_event callbacks
+-export([init/1,
+ handle_call/2,
+ handle_event/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+-include("webmachine_logger.hrl").
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+-record(state, {hourstamp, filename, handle}).
+
+-define(FILENAME, "access.log").
+
+%% ===================================================================
+%% gen_event callbacks
+%% ===================================================================
+
+%% @private
+init([BaseDir]) ->
+ webmachine_log:defer_refresh(?MODULE),
+ FileName = filename:join(BaseDir, ?FILENAME),
+ {Handle, DateHour} = webmachine_log:log_open(FileName),
+ {ok, #state{filename=FileName, handle=Handle, hourstamp=DateHour}}.
+
+%% @private
+handle_call({_Label, MRef, get_modules}, State) ->
+ {ok, {MRef, [?MODULE]}, State};
+handle_call({refresh, Time}, State) ->
+ {ok, ok, webmachine_log:maybe_rotate(?MODULE, Time, State)};
+handle_call(_Request, State) ->
+ {ok, ok, State}.
+
+%% @private
+handle_event({log_access, LogData}, State) ->
+ NewState = webmachine_log:maybe_rotate(?MODULE, os:timestamp(), State),
+ Msg = format_req(LogData),
+ webmachine_log:log_write(NewState#state.handle, Msg),
+ {ok, NewState};
+handle_event(_Event, State) ->
+ {ok, State}.
+
+%% @private
+handle_info(_Info, State) ->
+ {ok, State}.
+
+%% @private
+terminate(_Reason, _State) ->
+ ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% ===================================================================
+%% Internal functions
+%% ===================================================================
+
+%% We currently keep most of the Webmachine logging facility. But
+%% since we are now using Cowboy, a few small parts had to change.
+%% This is one such part. The code is however equivalent to Webmachine's.
+
+format_req({Status0, Body, Req}) ->
+ User = user_from_req(Req),
+ Time = webmachine_log:fmtnow(),
+ Status = integer_to_list(Status0),
+ Length1 = case Body of
+ {sendfile, _, Length0, _} -> Length0;
+ _ -> iolist_size(Body)
+ end,
+ Length = integer_to_list(Length1),
+ Method = cowboy_req:method(Req),
+ Path = cowboy_req:path(Req),
+ Peer = case cowboy_req:peer(Req) of
+ {Peer0, _Port} -> Peer0;
+ Other -> Other
+ end,
+ Version = cowboy_req:version(Req),
+ Referer = cowboy_req:header(<<"referer">>, Req, <<>>),
+ UserAgent = cowboy_req:header(<<"user-agent">>, Req, <<>>),
+ fmt_alog(Time, Peer, User, Method, Path, Version,
+ Status, Length, Referer, UserAgent).
+
+fmt_alog(Time, Ip, User, Method, Path, Version,
+ Status, Length, Referrer, UserAgent) ->
+ [webmachine_log:fmt_ip(Ip), " - ", User, [$\s], Time, [$\s, $"], Method, " ", Path,
+ " ", atom_to_list(Version), [$",$\s],
+ Status, [$\s], Length, [$\s,$"], Referrer,
+ [$",$\s,$"], UserAgent, [$",$\n]].
+
+user_from_req(Req) ->
+ try cowboy_req:parse_header(<<"authorization">>, Req) of
+ {basic, Username, _} ->
+ Username;
+ {bearer, _} ->
+ rabbit_data_coercion:to_binary(
+ application:get_env(rabbitmq_management, uaa_client_id, ""));
+ _ ->
+ "-"
+ catch _:_ ->
+ "-"
+ end.
diff --git a/deps/rabbitmq_web_dispatch/src/webmachine_logger.hrl b/deps/rabbitmq_web_dispatch/src/webmachine_logger.hrl
new file mode 100644
index 0000000000..c07068ae6a
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/webmachine_logger.hrl
@@ -0,0 +1,16 @@
+-record(wm_log_data,
+ {resource_module :: atom(),
+ start_time :: tuple(),
+ method :: atom(),
+ headers,
+ peer,
+ path :: string(),
+ version,
+ response_code,
+ response_length,
+ end_time :: tuple(),
+ finish_time :: tuple(),
+ notes}).
+-type wm_log_data() :: #wm_log_data{}.
+
+-define(EVENT_LOGGER, webmachine_log_event).