diff --git a/deps/rabbitmq_web_dispatch/Makefile b/deps/rabbitmq_web_dispatch/Makefile
index 39321893c3..41ef7d97a0 100644
--- a/deps/rabbitmq_web_dispatch/Makefile
+++ b/deps/rabbitmq_web_dispatch/Makefile
@@ -1,6 +1,8 @@
PROJECT = rabbitmq_web_dispatch
-DEPS = mochiweb webmachine
+DEPS = cowboy
+dep_cowboy_commit = 1.0.3
+
TEST_DEPS = rabbit amqp_client
DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk
diff --git a/deps/rabbitmq_web_dispatch/README.md b/deps/rabbitmq_web_dispatch/README.md
index 425ef6ddc9..94a5dd6417 100644
--- a/deps/rabbitmq_web_dispatch/README.md
+++ b/deps/rabbitmq_web_dispatch/README.md
@@ -1,25 +1,16 @@
rabbitmq-web-dispatch
---------------------
-rabbitmq-web-dispatch is a thin veneer around mochiweb that provides the
-ability for multiple applications to co-exist on mochiweb
+rabbitmq-web-dispatch is a thin veneer around Cowboy that provides the
+ability for multiple applications to co-exist on Cowboy
listeners. Applications can register static docroots or dynamic
handlers to be executed, dispatched by URL path prefix.
-See http://www.rabbitmq.com/mochiweb.html for information on
+See http://www.rabbitmq.com/web-dispatch.html for information on
configuring web plugins.
The most general registration procedure is
-`rabbit_web_dispatch:register_context_handler/5`. This takes a callback
-procedure of the form
+`rabbit_web_dispatch:register_context_handler/5`.
- loop(Request) ->
- ...
-
-The module `rabbit_webmachine` provides a means of running more than
-one webmachine in a VM, and understands rabbitmq-web-dispatch contexts. To
-use it, supply a dispatch table term of the kind usually given to
-webmachine in the file `priv/dispatch.conf`.
-
-`setup/{1,2}` in the same module allows some global configuration of
-webmachine logging and error handling.
+This takes a dispatch list of the kind usually given to Cowboy, in compiled
+form.
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..af0b7a4ef4
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_cowboy_middleware.erl
@@ -0,0 +1,52 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License
+%% at http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2010-2015 GoPivotal, Inc. All rights reserved.
+%%
+
+-module(rabbit_cowboy_middleware).
+-behavior(cowboy_middleware).
+
+-export([execute/2]).
+-export([onresponse/4]).
+
+execute(Req, Env) ->
+ %% Pre-parse the query string.
+ {_, Req1} = cowboy_req:qs_vals(Req),
+
+ %% Find the correct dispatch list for this path.
+ {_, Listener} = lists:keyfind(rabbit_listener, 1, Env),
+ case rabbit_web_dispatch_registry:lookup(Listener, Req1) of
+ {ok, Dispatch} ->
+ {ok, Req1, [{dispatch, Dispatch}|Env]};
+ {error, Reason} ->
+ {ok, Req2} = cowboy_req:reply(500,
+ [{<<"content-type">>, <<"text/plain">>}],
+ "Registry Error: " ++ io_lib:format("~p", [Reason]), Req1),
+ {halt, Req2}
+ end.
+
+onresponse(Status = 404, Headers0, Body = <<>>, Req0) ->
+ log_access(Status, Body, Req0),
+ Headers = [{<<"content-type">>, <<"application/json">>}|Headers0],
+ Json = {struct,
+ [{error, list_to_binary(httpd_util:reason_phrase(Status))},
+ {reason, <<"Not Found">>}]},
+ {ok, Req} = cowboy_req:reply(Status, Headers, mochijson2:encode(Json), Req0),
+ Req;
+onresponse(Status, _, Body, Req) ->
+ log_access(Status, Body, Req),
+ Req.
+
+log_access(Status, Body, Req) ->
+ webmachine_log:log_access({Status, Body, Req}).
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..17eba098f1
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_cowboy_redirect.erl
@@ -0,0 +1,34 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License
+%% at http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2010-2015 GoPivotal, Inc. All rights reserved.
+%%
+
+-module(rabbit_cowboy_redirect).
+
+-export([init/3]).
+-export([handle/2]).
+-export([terminate/3]).
+
+init(_, Req, RedirectPort) ->
+ {ok, Req, RedirectPort}.
+
+handle(Req0, RedirectPort) ->
+ %% Use a small trick to get a URL with the updated port.
+ RedReq = cowboy_req:set([{port, RedirectPort}], Req0),
+ {URL, _} = cowboy_req:url(RedReq),
+ {ok, Req} = cowboy_req:reply(301, [{<<"location">>, URL}], Req0),
+ {ok, Req, RedirectPort}.
+
+terminate(_, _, _) ->
+ ok.
diff --git a/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch.erl b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch.erl
index 5a7481d035..b3e1fbef16 100644
--- a/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch.erl
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch.erl
@@ -50,27 +50,16 @@ register_static_context(Name, Listener, Prefix, Module, FSPath, LinkText) ->
register_port_redirect(Name, Listener, Prefix, RedirectPort) ->
register_context_handler(
Name, Listener, Prefix,
- fun (Req) ->
- Host = case Req:get_header_value("host") of
- undefined -> {ok, {IP, _Port}} = rabbit_net:sockname(
- Req:get(socket)),
- rabbit_misc:ntoa(IP);
- Header -> hd(string:tokens(Header, ":"))
- end,
- URL = rabbit_misc:format(
- "~s://~s:~B~s",
- [Req:get(scheme), Host, RedirectPort, Req:get(raw_path)]),
- Req:respond({301, [{"Location", URL}], ""})
- end,
+ 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 = "/" ++ Prefix,
+ Prefix1 = list_to_binary("/" ++ Prefix),
fun(Req) ->
- Path = Req:get(raw_path),
- (Path == Prefix1) orelse (string:str(Path, Prefix1 ++ "/") == 1)
+ {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
@@ -81,32 +70,10 @@ static_context_handler(Prefix, Module, FSPath) ->
{file, Here} = code:is_loaded(Module),
ModuleRoot = filename:dirname(filename:dirname(Here)),
LocalPath = filename:join(ModuleRoot, FSPath),
- static_context_handler(Prefix, LocalPath).
-
-%% Produces a handler for use with register_handler that serves up
-%% static content from a specified directory.
-static_context_handler("", LocalPath) ->
- fun(Req) ->
- "/" ++ Path = Req:get(path),
- serve_file(Req, Path, LocalPath)
- end;
-static_context_handler(Prefix, LocalPath) ->
- fun(Req) ->
- "/" ++ Path = Req:get(path),
- case string:substr(Path, length(Prefix) + 1) of
- "" -> Req:respond({301, [{"Location", "/" ++ Prefix ++ "/"}], ""});
- "/" ++ P -> serve_file(Req, P, LocalPath)
- end
- end.
-
-serve_file(Req, Path, LocalPath) ->
- case Req:get(method) of
- Method when Method =:= 'GET'; Method =:= 'HEAD' ->
- Req:serve_file(Path, LocalPath);
- _ ->
- Req:respond({405, [{"Allow", "GET, HEAD"}],
- "Only GET or HEAD supported for static content"})
- end.
+ cowboy_router:compile([{'_', [
+ {"/" ++ Prefix, cowboy_static, {file, LocalPath ++ "/index.html"}},
+ {"/" ++ Prefix ++ "/[...]", cowboy_static, {dir, LocalPath}}
+ ]}]).
%% The opposite of all those register_* functions.
unregister_context(Name) ->
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..ec49dde916
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_listing_handler.erl
@@ -0,0 +1,44 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License
+%% at http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2010-2014 GoPivotal, Inc. All rights reserved.
+%%
+
+-module(rabbit_web_dispatch_listing_handler).
+
+-export([init/3]).
+-export([handle/2]).
+-export([terminate/3]).
+
+init(_, Req, Listener) ->
+ {ok, Req, Listener}.
+
+handle(Req0, Listener) ->
+ HTMLPrefix =
+ ""
+ "
RabbitMQ Web Server"
+ "RabbitMQ Web Server
Contexts available:
",
+ List =
+ case rabbit_web_dispatch_registry:list(Listener) of
+ [] ->
+ "No contexts installed";
+ Contexts ->
+ [["", Desc, ""]
+ || {Path, Desc} <- Contexts]
+ end,
+ {ok, Req} = cowboy_req:reply(200, [], [HTMLPrefix, List, HTMLSuffix], Req0),
+ {ok, Req, Listener}.
+
+terminate(_, _, _) ->
+ ok.
diff --git a/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_registry.erl b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_registry.erl
index ff651eec50..346b555c97 100644
--- a/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_registry.erl
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_registry.erl
@@ -22,6 +22,7 @@
-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]).
-define(ETS, rabbitmq_web_dispatch).
@@ -38,6 +39,8 @@ add(Name, Listener, Selector, Handler, Link) ->
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).
@@ -46,9 +49,9 @@ lookup(Listener, Req) ->
case lookup_dispatch(Listener) of
{ok, {Selectors, Fallback}} ->
case catch match_request(Selectors, Req) of
- {'EXIT', Reason} -> {lookup_failure, Reason};
- no_handler -> {handler, Fallback};
- Handler -> {handler, Handler}
+ {'EXIT', Reason} -> {error, {lookup_failure, Reason}};
+ not_found -> {ok, Fallback};
+ Dispatch -> {ok, Dispatch}
end;
Err ->
Err
@@ -147,10 +150,10 @@ set_dispatch(Listener, Selectors, Fallback) ->
ets:insert(?ETS, {port(Listener), Listener, Selectors, Fallback}).
match_request([], _) ->
- no_handler;
-match_request([{_Name, Selector, Handler, _Link}|Rest], Req) ->
+ not_found;
+match_request([{_Name, Selector, Dispatch, _Link}|Rest], Req) ->
case Selector(Req) of
- true -> Handler;
+ true -> Dispatch;
false -> match_request(Rest, Req)
end.
@@ -175,25 +178,6 @@ list(Listener) ->
%%---------------------------------------------------------------------------
listing_fallback_handler(Listener) ->
- fun(Req) ->
- HTMLPrefix =
- ""
- "RabbitMQ Web Server"
- "RabbitMQ Web Server
Contexts available:
",
- {ReqPath, _, _} = mochiweb_util:urlsplit_path(Req:get(raw_path)),
- List =
- case list(Listener) of
- [] ->
- "No contexts installed";
- Contexts ->
- [handler_listing(Path, ReqPath, Desc)
- || {Path, Desc} <- Contexts]
- end,
- Req:respond({200, [], HTMLPrefix ++ List ++ HTMLSuffix})
- end.
-
-handler_listing(Path, ReqPath, Desc) ->
- io_lib:format(
- "~s",
- [rabbit_web_dispatch_util:relativise(ReqPath, "/" ++ Path), Desc]).
+ 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
index 22a293f7e2..299f567876 100644
--- a/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_sup.erl
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_sup.erl
@@ -36,9 +36,14 @@ ensure_listener(Listener) ->
undefined ->
{error, {no_port_given, Listener}};
_ ->
- Child = {{rabbit_web_dispatch_web, name(Listener)},
- {mochiweb_http, start, [mochi_options(Listener)]},
- transient, 5000, worker, dynamic},
+ {Transport, TransportOpts} = preprocess_config(Listener),
+ Child = ranch:child_spec(name(Listener), 100,
+ Transport, TransportOpts,
+ cowboy_protocol, [
+ {env, [{rabbit_listener, Listener}]},
+ {middlewares, [rabbit_cowboy_middleware, cowboy_router, cowboy_handler]},
+ {onresponse, fun rabbit_cowboy_middleware:onresponse/4}
+ ]),
case supervisor:start_child(?SUP, Child) of
{ok, _} -> new;
{error, {already_started, _}} -> existing;
@@ -48,8 +53,8 @@ ensure_listener(Listener) ->
stop_listener(Listener) ->
Name = name(Listener),
- ok = supervisor:terminate_child(?SUP, {rabbit_web_dispatch_web, Name}),
- ok = supervisor:delete_child(?SUP, {rabbit_web_dispatch_web, Name}).
+ 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.
@@ -57,43 +62,28 @@ init([]) ->
Registry = {rabbit_web_dispatch_registry,
{rabbit_web_dispatch_registry, start_link, []},
transient, 5000, worker, dynamic},
- {ok, {{one_for_one, 10, 10}, [Registry]}}.
+ 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]}}.
%% ----------------------------------------------------------------------
-mochi_options(Listener) ->
- [{name, name(Listener)},
- {loop, loopfun(Listener)} |
- ssl_config(proplists:delete(
- name, proplists:delete(ignore_in_use, Listener)))].
-
-loopfun(Listener) ->
- fun (Req) ->
- case rabbit_web_dispatch_registry:lookup(Listener, Req) of
- no_handler ->
- Req:not_found();
- {error, Reason} ->
- Req:respond({500, [], "Registry Error: " ++ Reason});
- {handler, Handler} ->
- Handler(Req)
- end
- end.
-
name(Listener) ->
Port = proplists:get_value(port, Listener),
list_to_atom(atom_to_list(?MODULE) ++ "_" ++ integer_to_list(Port)).
-ssl_config(Options) ->
+preprocess_config(Options) ->
case proplists:get_value(ssl, Options) of
- true -> rabbit_networking:ensure_ssl(),
+ 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 -> proplists:delete(ssl, Options)
+ danger -> {ranch_tcp, proplists:delete(ssl, Options)}
end;
- _ -> Options
+ _ -> {ranch_tcp, Options}
end.
auto_ssl(Options) ->
@@ -105,8 +95,8 @@ auto_ssl(Options) ->
fix_ssl(Options) ->
SSLOpts = proplists:get_value(ssl_opts, Options),
- rabbit_misc:pset(ssl_opts,
- rabbit_networking:fix_ssl_options(SSLOpts), Options).
+ {ranch_ssl, proplists:delete(ssl, proplists:delete(ssl_opts,
+ Options ++ rabbit_networking:fix_ssl_options(SSLOpts)))}.
check_error(Listener, Error) ->
Ignore = proplists:get_value(ignore_in_use, Listener, false),
diff --git a/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_util.erl b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_util.erl
index f8c116f6c0..10c027c1e0 100644
--- a/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_util.erl
+++ b/deps/rabbitmq_web_dispatch/src/rabbit_web_dispatch_util.erl
@@ -19,6 +19,7 @@
-export([parse_auth_header/1]).
-export([relativise/2, unrelativise/2]).
+%% @todo remove
parse_auth_header(Header) ->
case Header of
"Basic " ++ Base64 ->
diff --git a/deps/rabbitmq_web_dispatch/src/rabbit_webmachine.erl b/deps/rabbitmq_web_dispatch/src/rabbit_webmachine.erl
deleted file mode 100644
index a199a31b94..0000000000
--- a/deps/rabbitmq_web_dispatch/src/rabbit_webmachine.erl
+++ /dev/null
@@ -1,68 +0,0 @@
-%% This file contains an adapted version of webmachine_mochiweb:loop/1
-%% from webmachine (revision 0c4b60ac68b4).
-
-%% All modifications are (C) 2011-2013 GoPivotal, Inc.
-
--module(rabbit_webmachine).
-
-%% An alternative to webmachine_mochiweb, which places the dispatch
-%% table (among other things) into the application env, and thereby
-%% makes it impossible to run more than one instance of
-%% webmachine. Since rabbit_web_dispatch is all about multi-tenanting
-%% webapps, clearly this won't do for us.
-
-%% Instead of using webmachine_mochiweb:start/1 or
-%% webmachine_mochiweb:loop/1, construct a loop procedure using
-%% makeloop/1 and supply it as the argument to
-%% rabbit_web_dispatch:register_context_handler or to mochiweb_http:start.
-
-%% We hardwire the "error handler" and use a "logging module" if
-%% supplied.
-
--export([makeloop/1, setup/0]).
-
-setup() ->
- application:set_env(
- webmachine, error_handler, rabbit_webmachine_error_handler).
-
-makeloop(Dispatch) ->
- fun (MochiReq) ->
- Req = webmachine:new_request(mochiweb, MochiReq),
- {Path, _} = Req:path(),
- {ReqData, _} = Req:get_reqdata(),
- %% webmachine_mochiweb:loop/1 uses dispatch/4 here;
- %% however, we don't need to dispatch by the host name.
- case webmachine_dispatcher:dispatch(Path, Dispatch, ReqData) of
- {no_dispatch_match, _Host, _PathElements} ->
- {ErrorBody, ReqState1} =
- rabbit_webmachine_error_handler:render_error(
- 404, Req, {none, none, []}),
- Req1 = {webmachine_request, ReqState1},
- {ok, ReqState2} = Req1:append_to_response_body(ErrorBody),
- Req2 = {webmachine_request, ReqState2},
- {ok, ReqState3} = Req2:send_response(404),
- maybe_log_access(ReqState3);
- {Mod, ModOpts, HostTokens, Port, PathTokens, Bindings,
- AppRoot, StringPath} ->
- BootstrapResource = webmachine_resource:new(x,x,x,x),
- {ok, Resource} = BootstrapResource:wrap(Mod, ModOpts),
- {ok, RS1} = Req:load_dispatch_data(Bindings, HostTokens, Port,
- PathTokens,
- AppRoot, StringPath),
- XReq1 = {webmachine_request, RS1},
- {ok, RS2} = XReq1:set_metadata('resource_module', Mod),
- try
- webmachine_decision_core:handle_request(Resource, RS2)
- catch
- error:_ ->
- FailReq = {webmachine_request, RS2},
- {ok, RS3} = FailReq:send_response(500),
- maybe_log_access(RS3)
- end
- end
- end.
-
-maybe_log_access(ReqState) ->
- Req = {webmachine_request, ReqState},
- {LogData, _ReqState1} = Req:log_data(),
- webmachine_log:log_access(LogData).
diff --git a/deps/rabbitmq_web_dispatch/src/rabbit_webmachine_error_handler.erl b/deps/rabbitmq_web_dispatch/src/rabbit_webmachine_error_handler.erl
deleted file mode 100644
index 3d11529bf0..0000000000
--- a/deps/rabbitmq_web_dispatch/src/rabbit_webmachine_error_handler.erl
+++ /dev/null
@@ -1,63 +0,0 @@
-%% The contents of this file are subject to the Mozilla Public License
-%% Version 1.1 (the "License"); you may not use this file except in
-%% compliance with the License. You may obtain a copy of the License
-%% at http://www.mozilla.org/MPL/
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and
-%% limitations under the License.
-%%
-%% The Original Code is RabbitMQ.
-%%
-%% The Initial Developer of the Original Code is GoPivotal, Inc.
-%% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
-%%
-
-%% We need to ensure all responses are application/json; anything
-%% coming back as text/html could constitute an XSS vector. Also I'm
-%% sure it's easier on our clients if they can always expect JSON
-%% responses.
-%%
-%% Based on webmachine_error_handler, but I'm not sure enough remains
-%% to be copyrightable.
-
--module(rabbit_webmachine_error_handler).
-
--export([render_error/3]).
-
-render_error(Code, Req, Reason) ->
- case Req:has_response_body() of
- {true, _} ->
- maybe_log(Req, Reason),
- {Body, ReqState0} = Req:response_body(),
- {ok, ReqState} =
- webmachine_request:remove_response_header("Content-Encoding",
- ReqState0),
- {Body, ReqState};
- {false, _} -> render_error_body(Code, Req:trim_state(), Reason)
- end.
-
-render_error_body(404, Req, _) -> error_body(404, Req, "Not Found");
-render_error_body(Code, Req, Reason) -> error_body(Code, Req, Reason).
-
-error_body(Code, Req, Reason) ->
- {ok, _ReqState0} = Req:add_response_header("Content-Type","application/json"),
- {ok, ReqState} = Req:remove_response_header("Content-Encoding"),
- case Code of
- 500 -> maybe_log(Req, Reason);
- _ -> ok
- end,
- Json = {struct,
- [{error, list_to_binary(httpd_util:reason_phrase(Code))},
- {reason, list_to_binary(rabbit_misc:format("~p~n", [Reason]))}]},
- {mochijson2:encode(Json), ReqState}.
-
-maybe_log(_Req, {error, {exit, normal, _Stack}}) ->
- %% webmachine_request did an exit(normal), so suppress this
- %% message. This usually happens when a chunked upload is
- %% interrupted by network failure.
- ok;
-maybe_log(Req, Reason) ->
- {Path, _} = Req:path(),
- error_logger:error_msg("webmachine error: path=~p~n~p~n", [Path, Reason]).
diff --git a/deps/rabbitmq_web_dispatch/src/rabbitmq_web_dispatch.app.src b/deps/rabbitmq_web_dispatch/src/rabbitmq_web_dispatch.app.src
index 0020a8fa49..fd2fa646f6 100644
--- a/deps/rabbitmq_web_dispatch/src/rabbitmq_web_dispatch.app.src
+++ b/deps/rabbitmq_web_dispatch/src/rabbitmq_web_dispatch.app.src
@@ -5,4 +5,4 @@
{registered, []},
{mod, {rabbit_web_dispatch_app, []}},
{env, []},
- {applications, [kernel, stdlib, mochiweb, webmachine]}]}.
+ {applications, [kernel, stdlib, cowboy]}]}.
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..95924fe8d6
--- /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
+%%
+%% http://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..018dd0cf3a
--- /dev/null
+++ b/deps/rabbitmq_web_dispatch/src/webmachine_log_handler.erl
@@ -0,0 +1,111 @@
+%% 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
+%%
+%% http://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 = "-",
+ Time = webmachine_log:fmtnow(),
+ Status = integer_to_list(Status0),
+ Length = integer_to_list(iolist_size(Body)),
+ {Method, _} = cowboy_req:method(Req),
+ {Path, _} = cowboy_req:path(Req),
+ {{Peer, _}, _} = cowboy_req:peer(Req),
+ Version = case cowboy_req:version(Req) of
+ {'HTTP/1.1', _} -> {1, 1};
+ {'HTTP/1.0', _} -> {1, 0}
+ end,
+ {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, {VM,Vm},
+ Status, Length, Referrer, UserAgent) ->
+ [webmachine_log:fmt_ip(Ip), " - ", User, [$\s], Time, [$\s, $"], Method, " ", Path,
+ " HTTP/", integer_to_list(VM), ".", integer_to_list(Vm), [$",$\s],
+ Status, [$\s], Length, [$\s,$"], Referrer,
+ [$",$\s,$"], UserAgent, [$",$\n]].
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).