Skip to content
This repository was archived by the owner on May 25, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/chttpd.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
couch,
ets_lru,
fabric,
cassim
cassim,
mem3,
global_changes
]},
{mod, {chttpd_app,[]}}
]}.
44 changes: 4 additions & 40 deletions src/chttpd.erl
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ start_link(https) ->
start_link(https, Options).

start_link(Name, Options) ->
chttpd_handler:build(),
Options1 = Options ++ [
{loop, fun ?MODULE:handle_request/1},
{name, Name},
Expand Down Expand Up @@ -187,8 +188,8 @@ handle_request(MochiReq) ->
method = Method,
path_parts = [list_to_binary(chttpd:unquote(Part))
|| Part <- string:tokens(Path, "/")],
db_url_handlers = db_url_handlers(),
design_url_handlers = design_url_handlers()
db_url_handlers = chttpd_handler:db_url_handlers(),
design_url_handlers = chttpd_handler:design_url_handlers()
},

% put small token on heap to keep requests synced to backend calls
Expand All @@ -204,7 +205,7 @@ handle_request(MochiReq) ->
#httpd{} ->
case authenticate_request(HttpReq, AuthenticationFuns) of
#httpd{} = Req ->
HandlerFun = url_handler(HandlerKey),
HandlerFun = chttpd_handler:url_handler(HandlerKey),
HandlerFun(chttpd_auth_request:authorize_request(possibly_hack(Req)));
Response ->
Response
Expand Down Expand Up @@ -357,43 +358,6 @@ authenticate_request(Response, _AuthFuns) ->
increment_method_stats(Method) ->
couch_stats:increment_counter([couchdb, httpd_request_methods, Method]).

url_handler("") -> fun chttpd_misc:handle_welcome_req/1;
url_handler("favicon.ico") -> fun chttpd_misc:handle_favicon_req/1;
url_handler("_utils") -> fun chttpd_misc:handle_utils_dir_req/1;
url_handler("_all_dbs") -> fun chttpd_misc:handle_all_dbs_req/1;
url_handler("_active_tasks") -> fun chttpd_misc:handle_task_status_req/1;
url_handler("_config") -> fun chttpd_misc:handle_config_req/1;
url_handler("_reload_query_servers") -> fun chttpd_misc:handle_reload_query_servers_req/1;
url_handler("_replicate") -> fun chttpd_misc:handle_replicate_req/1;
url_handler("_uuids") -> fun chttpd_misc:handle_uuids_req/1;
url_handler("_sleep") -> fun chttpd_misc:handle_sleep_req/1;
url_handler("_session") -> fun chttpd_auth:handle_session_req/1;
url_handler("_oauth") -> fun couch_httpd_oauth:handle_oauth_req/1;
url_handler("_up") -> fun chttpd_misc:handle_up_req/1;
url_handler("_membership") -> fun mem3_httpd:handle_membership_req/1;
url_handler("_db_updates") -> fun global_changes_httpd:handle_global_changes_req/1;
url_handler(_) -> fun chttpd_db:handle_request/1.

db_url_handlers() ->
[
{<<"_view_cleanup">>, fun chttpd_db:handle_view_cleanup_req/2},
{<<"_compact">>, fun chttpd_db:handle_compact_req/2},
{<<"_design">>, fun chttpd_db:handle_design_req/2},
{<<"_temp_view">>, fun chttpd_view:handle_temp_view_req/2},
{<<"_changes">>, fun chttpd_db:handle_changes_req/2},
{<<"_shards">>, fun mem3_httpd:handle_shards_req/2}
].

design_url_handlers() ->
[
{<<"_view">>, fun chttpd_view:handle_view_req/3},
{<<"_show">>, fun chttpd_show:handle_doc_show_req/3},
{<<"_list">>, fun chttpd_show:handle_view_list_req/3},
{<<"_update">>, fun chttpd_show:handle_doc_update_req/3},
{<<"_info">>, fun chttpd_db:handle_design_info_req/3},
{<<"_rewrite">>, fun chttpd_rewrite:handle_rewrite_req/3}
].

% Utilities

partition(Path) ->
Expand Down
202 changes: 202 additions & 0 deletions src/chttpd_handler.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
%% Licensed 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 Configurable, dynamic creation of endpoint handler callback indirections.

-module(chttpd_handler).

-export([build/0, build/1, url_handler/1, db_url_handlers/0,
design_url_handlers/0]).

-vsn(4).

%% @doc a complete configuration data set
-type config() :: [Function::{Name::atom(), clauses|list, [bind()]}].

%% @doc one essential pair of a pattern and the fun to be returned for it
-type bind() :: {Endpoint::term(), MFA::{atom(), atom(), integer()}}.

-spec url_handler(Endpoint::list()) -> Handler::fun().
%% @doc Dispatch endpoint to fun, wrapper to hide dynamic module.
url_handler(Endpoint) ->
chttpd_dyn_handler:url_handler(Endpoint).

-spec db_url_handlers() -> [{Endpoint::list(), Handler::fun()}].
%% @doc Get a list of endpoints and handler funs, wrapper to hide dyn module.
db_url_handlers() ->
chttpd_dyn_handler:db_url_handlers().

-spec design_url_handlers() -> [{Endpoint::list(), Handler::fun()}].
%% @doc Get a list of endpoints and handler funs, wrapper to hide dyn module.
design_url_handlers() ->
chttpd_dyn_handler:design_url_handlers().

-spec build() -> ok | [].
%% @doc Create the dynamic handler functions from ini file.
build() ->
build(load_defs()).

-spec build(HandlerCfg::config()) -> ok.
%% @doc Compile the complete syntax tree, purge and load the dynamic module
build(Cfg) when is_list(Cfg) ->
Opts = [verbose, report_errors],
{ok, Mod, Bin} = compile:forms(forms(chttpd_dyn_handler, Cfg), Opts),
% don't code:purge(Mod),
{module, Mod} = code:load_binary(Mod, atom_to_list(Mod) ++ ".erl", Bin),
ok.

-spec load_defs() -> CombinedHandlerCfg::config().
%% @doc assemble the configuration from the chttpd_handler.cfg of all apps.
load_defs() ->
{AllURLHandlers, AllDBHandlers, AllDesignHandlers} = lists:foldl(
fun(App, {URLHandlers, DBHandlers, DesignHandlers}) ->
Defs = load_defs(App),
{URLHandlers ++ [ B || {docs, clauses, B} <- Defs ],
DBHandlers ++ [ B || {db, list, B} <- Defs ],
DesignHandlers ++ [ B || {design, list, B} <- Defs ]}
end,
{[],[],[]},
[element(1, A) || A <- application:loaded_applications()]),
[{url_handler, clauses, lists:flatten(AllURLHandlers)},
{db_url_handlers, list, lists:flatten(AllDBHandlers)},
{design_url_handlers, list, lists:flatten(AllDesignHandlers)}].

-spec load_defs(AppName::atom()) -> OneAppsHandlerCfg::config().
%% @doc assemble the configuration from the chttpd_handler.cfg of all apps.
load_defs(App) ->
case code:priv_dir(App) of
{error, _Error} ->
[];
Dir ->
Path = Dir ++ "/chttpd_handler.cfg",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems I miss that moment. Why not to reuse config and have all the handlers defined there? We already keep httpd ones there and this gives us dynamic control over them with no server restart.

case file:consult(Path) of
{ok, Defs} ->
[ check_def(Def, Path) || Def <- Defs ];
{error, _Error} ->
[]
end
end.

check_def({_, _, []}=Def, Path) ->
throw({no_defs_error, Def, Path});
check_def({docs, clauses, B}, Path) ->
{docs, clauses, sort(check_dupes(check_bindings(B, list, 1, Path), Path))};
check_def({db, list, B}, Path) ->
{db, list, check_dupes(check_bindings(B, binary, 2, Path), Path)};
check_def({design, list, B}, Path) ->
{design, list, check_dupes(check_bindings(B, binary, 3, Path), Path)};
check_def(Def, Path) ->
throw({tag_error, Def, Path}).

check_bindings([{Endpoint, {M, F, Arity}}=Good | More], list, Arity, Path)
when is_list(Endpoint), is_atom(M), is_atom(F), is_integer(Arity) ->
[Good | check_bindings(More, list, Arity, Path)];
check_bindings([{Endpoint, {M, F, Arity}}=Good | More], binary, Arity, Path)
when is_binary(Endpoint), is_atom(M), is_atom(F), is_integer(Arity) ->
[Good | check_bindings(More, binary, Arity, Path)];
check_bindings([{'_', {M, F, Arity}}=Good | More], Type, Arity, Path)
when is_atom(M), is_atom(F), is_integer(Arity) ->
[Good | check_bindings(More, Type, Arity, Path)];
check_bindings([Bad | _], _, Arity, Path) ->
throw({syntax_or_arity_error, Bad, exptected_arity, Arity, Path});
check_bindings([], _, _, _) ->
[].

-spec sort(Cfg::config()) -> config().
%% @doc make sure that any _ is the last clause of a generated function
sort(Cfg) ->
lists:sort(
fun ({'_',_}, _) -> false;
(_, {'_',_}) -> true;
(_, _) -> true
end,
Cfg).

-spec check_dupes(Cfg::config(), Path::list()) -> config() | [].
%% @doc crash if an endpoint is defined twice
check_dupes(Cfg, Path) ->
lists:sort(
fun ({E,_}=Def1, {E, _}=Def2) ->
throw({duplicate_endpoint, E, Def1, Def2, Path});
(_, _) -> true
end,
Cfg).

-spec forms(Mod::atom(), Defs::[bind()]) -> erl_syntax:syntaxTree().
%% @doc The complete syntax tree of the dynamic module
forms(Mod, Defs) ->
Statements = [
module_stmt(Mod),
export_stmt(
[{Name, case Lay of clauses -> 1; list -> 0 end}
|| {Name, Lay, _} <- Defs ])
| [ binding_function(Name, Lay, Def) || {Name, Lay, Def} <- Defs ]],
[ erl_syntax:revert(X) || X <- Statements].

-spec module_stmt(ModuleName::atom()) -> erl_syntax:syntaxTree().
%% @doc Create syntax tree for the module statement of the dynamic module
module_stmt(Name) ->
erl_syntax:attribute(
erl_syntax:atom(module),
[erl_syntax:atom(Name)]).

-spec export_stmt([Exports::{Name::atom(), Arity::integer()}]) ->
erl_syntax:syntaxTree().
%% @doc Create syntax tree for the export statement of the dynamic module
export_stmt(Exports) ->
erl_syntax:attribute(
erl_syntax:atom(export),
[erl_syntax:list(
[ erl_syntax:arity_qualifier(
erl_syntax:atom(Name),
erl_syntax:integer(Arity))
|| {Name, Arity} <- Exports ]
)]).

-spec binding_function(Name::atom(), clauses | list, [bind()]) ->
erl_syntax:syntaxTree().
%% @doc Create syntax subtree for a function that either has multiple clauses
%% or returns a list of tuples of a tag and a fun.
binding_function(Name, list, Defs) ->
erl_syntax:function(
erl_syntax:atom(Name),
[erl_syntax:clause([], none,
[erl_syntax:list(
[erl_syntax:tuple([
erl_syntax:abstract(P),
create_fun(Def)])
|| {P, Def} <- Defs ])])]);
binding_function(Name, clauses, Defs) ->
erl_syntax:function(
erl_syntax:atom(Name),
[create_fun_clause(Def) || Def <- sort(Defs)]).

-spec create_fun_clause(bind()) -> erl_syntax:syntaxTree().
%% @doc Create syntax subtree for a function clause with one implicit fun call.
create_fun_clause({P, MFA}) ->
erl_syntax:clause(
[case P of
'_' -> erl_syntax:underscore();
_ -> erl_syntax:abstract(P)
end],
none,
[create_fun(MFA)]).

-spec create_fun(MFA::{Module::atom(), Function::atom(), Arity::integer()}) ->
erl_syntax:syntaxTree().
%% @doc Create syntax subtree for an implicit fun call.
create_fun({M, F, A}) ->
erl_syntax:implicit_fun(
erl_syntax:atom(M),
erl_syntax:atom(F),
erl_syntax:integer(A)).

Loading