You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2019/11/21 18:22:52 UTC
[couchdb] branch 1523-bye-bye-5986-rnewson updated (48aa725 ->
3522f16)
This is an automated email from the ASF dual-hosted git repository.
rnewson pushed a change to branch 1523-bye-bye-5986-rnewson
in repository https://gitbox.apache.org/repos/asf/couchdb.git.
discard 48aa725 Add /_node//_all_dbs
discard 812a6ff add handle_request/1
discard 6eda0d0 extract get_httpd_handlers function
discard 06dab42 Move get_stats/0
discard 22fb510 Remove global _system handler
discard 5a9ec52 Move _node handler to new module
add 18db801 Show source and target proxies in _scheduler/docs output
add b9aa4e8 Do not mark replication jobs as failed if doc processor crashes
add 367d17a close LRU by database path
add d60551d Merge pull request #2130 from apache/close-lru
add ec23c34 Return detailed replication stats for running and pending jobs
new a7c86de [WIP] Deprecate port 5986
new 05815d1 enable tuple_calls
new ba6cd46 remove nesting
new 3522f16 WIP Add remaining endpoints
This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version. This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:
* -- * -- B -- O -- O -- O (48aa725)
\
N -- N -- N refs/heads/1523-bye-bye-5986-rnewson (3522f16)
You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.
Any revisions marked "omit" are not gone; other references still
refer to them. Any revisions marked "discard" are gone forever.
The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
src/chttpd/src/chttpd_misc.erl | 5 +-
src/chttpd/src/chttpd_node.erl | 184 +++++++++++++++++++--
src/couch/src/couch_httpd.erl | 5 +-
src/couch/src/couch_util.erl | 4 +-
.../src/couch_replicator_doc_processor.erl | 40 +++--
.../src/couch_replicator_scheduler.erl | 52 +++++-
.../src/couch_replicator_scheduler_job.erl | 16 +-
.../src/couch_replicator_stats.erl | 50 +++---
.../src/couch_replicator_utils.erl | 16 +-
...ch_replicator_retain_stats_between_job_runs.erl | 51 +++++-
src/dreyfus/src/clouseau_rpc.erl | 7 +-
src/dreyfus/src/dreyfus_index_manager.erl | 1 +
12 files changed, 345 insertions(+), 86 deletions(-)
[couchdb] 04/04: WIP Add remaining endpoints
Posted by rn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
rnewson pushed a commit to branch 1523-bye-bye-5986-rnewson
in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 3522f167ed7b29aafe6b4a7f51bbc15bc428add5
Author: Robert Newson <rn...@apache.org>
AuthorDate: Mon Nov 11 20:15:09 2019 +0000
WIP Add remaining endpoints
---
src/chttpd/src/chttpd_node.erl | 104 ++++++++++++++++++++++++++++++++++++-----
1 file changed, 93 insertions(+), 11 deletions(-)
diff --git a/src/chttpd/src/chttpd_node.erl b/src/chttpd/src/chttpd_node.erl
index c560e0e..63e0735 100644
--- a/src/chttpd/src/chttpd_node.erl
+++ b/src/chttpd/src/chttpd_node.erl
@@ -13,10 +13,19 @@
-module(chttpd_node).
-compile(tuple_calls).
+%% Public API
+-export([
+ handle_node_req/1
+]).
+
+%% for inter-node calls
-export([
- handle_node_req/1,
do_db_req/4,
- get_stats/0
+ do_db_req/5,
+ get_stats/0,
+ compact_view/2,
+ design_doc_info/2,
+ view_info/3
]).
-include_lib("couch/include/couch_db.hrl").
@@ -176,14 +185,63 @@ handle_node_db_req(#httpd{method='GET', path_parts=[DbName]}=Req, Node) ->
handle_node_db_req(#httpd{path_parts=[_DbName]}=Req, _Node) ->
send_method_not_allowed(Req, "GET");
-%{'GET', DbName, [<<"_all_docs">>]} ->
-% ...
-%{'POST', DbName, [<<"_compact">>]} ->
-% ...
-%{_, DbName, [DocName]} ->
-% %only support doc CRUD in _dbs/_nodes, and _info endpoint on all
-%{'GET', DbName, [<<"_design">>, DDoc, <<"_info">>]} ->
-% %individual view shard info stats
+handle_node_db_req(#httpd{path_parts=[_DbName, <<"_all_docs">>]}=Req, _Node) ->
+ send_json(Req, 200, {[]});
+
+handle_node_db_req(#httpd{method='POST', path_parts=[DbName, <<"_compact">>]}=Req, Node) ->
+ chttpd:validate_ctype(Req, "application/json"),
+ case call_node(Node, chttpd_node, do_db_req,
+ [Req#httpd.user_ctx, DbName, couch_db, start_compact]) of
+ {ok, _} ->
+ send_json(Req, 202, {[{ok, true}]});
+ {not_found, _} ->
+ send_error(Req, not_found)
+ end;
+
+handle_node_db_req(#httpd{method='POST', path_parts=[DbName, <<"_compact">>, DesignName]}=Req, Node) ->
+ chttpd:validate_ctype(Req, "application/json"),
+ DesignId = <<"_design/", DesignName/binary>>,
+ case call_node(Node, chttpd_node, do_db_req,
+ [Req#httpd.user_ctx, DbName, chttpd_node, compact_view, [DesignId]]) of
+ ok ->
+ send_json(Req, 202, {[{ok, true}]});
+ {not_found, _} ->
+ send_error(Req, not_found)
+ end;
+
+handle_node_db_req(#httpd{path_parts=[_DbName, <<"_", _/binary>> = EP]}=Req, _Node) ->
+ send_error(Req, {forbidden, <<EP/binary, " endpoint is not permitted.">>});
+
+handle_node_db_req(#httpd{path_parts=[<<"shards/", _/binary>>, _DocID]}=Req, _Node) ->
+ send_error(Req, {forbidden, <<"Document CRUD not permitted within shards.">>});
+
+handle_node_db_req(#httpd{path_parts=[_DbName, DocID]}=Req, _Node) ->
+ send_json(Req, 200, {[]});
+
+handle_node_db_req(#httpd{path_parts=[_DbName, <<"_design">>, DDocID]}=Req, _Node) ->
+ send_json(Req, 200, {[]});
+
+handle_node_db_req(#httpd{method='GET',
+ path_parts=[DbName, <<"_design">>, DesignName, <<"_info">>]}=Req, Node) ->
+ DesignId = <<"_design/", DesignName/binary>>,
+ case call_node(Node, chttpd_node, do_db_req,
+ [Req#httpd.user_ctx, DbName, chttpd_node, design_doc_info, [DesignId]]) of
+ {ok, Info} ->
+ send_json(Req, 200, {Info});
+ {not_found, _} ->
+ send_error(Req, not_found)
+ end;
+
+handle_node_db_req(#httpd{method='GET',
+ path_parts=[DbName, <<"_design">>, DesignName, <<"_view">>, VName, <<"_info">>]}=Req, Node) ->
+ DesignId = <<"_design/", DesignName/binary>>,
+ case call_node(Node, chttpd_node, do_db_req,
+ [Req#httpd.user_ctx, DbName, chttpd_node, view_info, [DesignId, VName]]) of
+ {ok, Info} ->
+ send_json(Req, 200, {Info});
+ {not_found, _} ->
+ send_error(Req, not_found)
+ end;
handle_node_db_req(Req, _Node) ->
send_error(Req, {bad_request, <<"invalid _node request">>}).
@@ -191,10 +249,13 @@ handle_node_db_req(Req, _Node) ->
% below adapted from old couch_httpd_db
% all of these run on the requested node
do_db_req(Ctx, DbName, Mod, Fun) ->
+ do_db_req(Ctx, DbName, Mod, Fun, []).
+
+do_db_req(Ctx, DbName, Mod, Fun, Args) ->
case couch_db:open(DbName, [{user_ctx, Ctx}]) of
{ok, Db} ->
try
- erlang:apply(Mod, Fun, [Db])
+ erlang:apply(Mod, Fun, [Db | Args])
after
catch couch_db:close(Db)
end;
@@ -221,6 +282,27 @@ call_node(Node, Mod, Fun, Args) when is_atom(Node) ->
Else
end.
+compact_view(Db, DesignId) ->
+ DDoc = couch_httpd_db:couch_doc_open(
+ Db, DesignId, nil, [ejson_body]),
+ couch_mrview:compact(Db, DDoc).
+
+
+design_doc_info(Db, DesignId) ->
+ DDoc = couch_httpd_db:couch_doc_open(
+ Db, DesignId, nil, [ejson_body]),
+ fabric:get_view_group_info(Db, DDoc).
+
+
+view_info(Db, DDocId, VName) ->
+ DbName = couch_db:name(Db),
+ {ok, Info} = couch_mrview:get_view_info(DbName, DDocId, VName),
+ FinalInfo = [{db_name, DbName},
+ {ddoc, DDocId},
+ {view, VName} | Info],
+ {ok, FinalInfo}.
+
+
flush(Node, Req) ->
case couch_util:get_value("flush", chttpd:qs(Req)) of
"true" ->
[couchdb] 01/04: [WIP] Deprecate port 5986
Posted by rn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
rnewson pushed a commit to branch 1523-bye-bye-5986-rnewson
in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit a7c86deb7b9419c80b73ce7b8ea0b5559b6103f8
Author: Joan Touzet <jo...@atypical.net>
AuthorDate: Mon Jul 29 19:08:02 2019 -0400
[WIP] Deprecate port 5986
This introduces a new chttpd_node module, under which all unclustered
functions now live. System DB and shard-level requests bypass mem3
and fabric, using rpc:call to call into couch_db, couch_server, etc.
---
src/chttpd/src/chttpd_httpd_handlers.erl | 2 +-
src/chttpd/src/chttpd_misc.erl | 229 +---------------------
src/chttpd/src/chttpd_node.erl | 321 +++++++++++++++++++++++++++++++
src/couch/src/couch.app.src | 3 +-
src/couch/src/couch_httpd.erl | 67 ++++---
5 files changed, 365 insertions(+), 257 deletions(-)
diff --git a/src/chttpd/src/chttpd_httpd_handlers.erl b/src/chttpd/src/chttpd_httpd_handlers.erl
index 000f29b..5e86ea8 100644
--- a/src/chttpd/src/chttpd_httpd_handlers.erl
+++ b/src/chttpd/src/chttpd_httpd_handlers.erl
@@ -21,7 +21,7 @@ url_handler(<<"_all_dbs">>) -> fun chttpd_misc:handle_all_dbs_req/1;
url_handler(<<"_dbs_info">>) -> fun chttpd_misc:handle_dbs_info_req/1;
url_handler(<<"_active_tasks">>) -> fun chttpd_misc:handle_task_status_req/1;
url_handler(<<"_scheduler">>) -> fun couch_replicator_httpd:handle_scheduler_req/1;
-url_handler(<<"_node">>) -> fun chttpd_misc:handle_node_req/1;
+url_handler(<<"_node">>) -> fun chttpd_node:handle_node_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;
diff --git a/src/chttpd/src/chttpd_misc.erl b/src/chttpd/src/chttpd_misc.erl
index 17122bf..bfd17fd 100644
--- a/src/chttpd/src/chttpd_misc.erl
+++ b/src/chttpd/src/chttpd_misc.erl
@@ -15,27 +15,24 @@
-export([
handle_all_dbs_req/1,
handle_dbs_info_req/1,
- handle_node_req/1,
handle_favicon_req/1,
handle_favicon_req/2,
handle_replicate_req/1,
handle_reload_query_servers_req/1,
- handle_system_req/1,
handle_task_status_req/1,
handle_up_req/1,
handle_utils_dir_req/1,
handle_utils_dir_req/2,
handle_uuids_req/1,
handle_welcome_req/1,
- handle_welcome_req/2,
- get_stats/0
+ handle_welcome_req/2
]).
-include_lib("couch/include/couch_db.hrl").
-include_lib("couch_mrview/include/couch_mrview.hrl").
-import(chttpd,
- [send_json/2,send_json/3,send_method_not_allowed/2,
+ [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
send_chunk/2,start_chunked_response/3]).
-define(MAX_DB_NUM_FOR_DBS_INFO, 100).
@@ -274,219 +271,6 @@ handle_reload_query_servers_req(Req) ->
handle_uuids_req(Req) ->
couch_httpd_misc_handlers:handle_uuids_req(Req).
-
-% Node-specific request handler (_config and _stats)
-% Support _local meaning this node
-handle_node_req(#httpd{path_parts=[_, <<"_local">>]}=Req) ->
- send_json(Req, 200, {[{name, node()}]});
-handle_node_req(#httpd{path_parts=[A, <<"_local">>|Rest]}=Req) ->
- handle_node_req(Req#httpd{path_parts=[A, node()] ++ Rest});
-% GET /_node/$node/_config
-handle_node_req(#httpd{method='GET', path_parts=[_, Node, <<"_config">>]}=Req) ->
- Grouped = lists:foldl(fun({{Section, Key}, Value}, Acc) ->
- case dict:is_key(Section, Acc) of
- true ->
- dict:append(Section, {list_to_binary(Key), list_to_binary(Value)}, Acc);
- false ->
- dict:store(Section, [{list_to_binary(Key), list_to_binary(Value)}], Acc)
- end
- end, dict:new(), call_node(Node, config, all, [])),
- KVs = dict:fold(fun(Section, Values, Acc) ->
- [{list_to_binary(Section), {Values}} | Acc]
- end, [], Grouped),
- send_json(Req, 200, {KVs});
-handle_node_req(#httpd{path_parts=[_, _Node, <<"_config">>]}=Req) ->
- send_method_not_allowed(Req, "GET");
-% GET /_node/$node/_config/Section
-handle_node_req(#httpd{method='GET', path_parts=[_, Node, <<"_config">>, Section]}=Req) ->
- KVs = [{list_to_binary(Key), list_to_binary(Value)}
- || {Key, Value} <- call_node(Node, config, get, [Section])],
- send_json(Req, 200, {KVs});
-handle_node_req(#httpd{path_parts=[_, _Node, <<"_config">>, _Section]}=Req) ->
- send_method_not_allowed(Req, "GET");
-% PUT /_node/$node/_config/Section/Key
-% "value"
-handle_node_req(#httpd{method='PUT', path_parts=[_, Node, <<"_config">>, Section, Key]}=Req) ->
- couch_util:check_config_blacklist(Section),
- Value = couch_util:trim(chttpd:json_body(Req)),
- Persist = chttpd:header_value(Req, "X-Couch-Persist") /= "false",
- OldValue = call_node(Node, config, get, [Section, Key, ""]),
- case call_node(Node, config, set, [Section, Key, ?b2l(Value), Persist]) of
- ok ->
- send_json(Req, 200, list_to_binary(OldValue));
- {error, Reason} ->
- chttpd:send_error(Req, {bad_request, Reason})
- end;
-% GET /_node/$node/_config/Section/Key
-handle_node_req(#httpd{method='GET', path_parts=[_, Node, <<"_config">>, Section, Key]}=Req) ->
- case call_node(Node, config, get, [Section, Key, undefined]) of
- undefined ->
- throw({not_found, unknown_config_value});
- Value ->
- send_json(Req, 200, list_to_binary(Value))
- end;
-% DELETE /_node/$node/_config/Section/Key
-handle_node_req(#httpd{method='DELETE',path_parts=[_, Node, <<"_config">>, Section, Key]}=Req) ->
- couch_util:check_config_blacklist(Section),
- Persist = chttpd:header_value(Req, "X-Couch-Persist") /= "false",
- case call_node(Node, config, get, [Section, Key, undefined]) of
- undefined ->
- throw({not_found, unknown_config_value});
- OldValue ->
- case call_node(Node, config, delete, [Section, Key, Persist]) of
- ok ->
- send_json(Req, 200, list_to_binary(OldValue));
- {error, Reason} ->
- chttpd:send_error(Req, {bad_request, Reason})
- end
- end;
-handle_node_req(#httpd{path_parts=[_, _Node, <<"_config">>, _Section, _Key]}=Req) ->
- send_method_not_allowed(Req, "GET,PUT,DELETE");
-handle_node_req(#httpd{path_parts=[_, _Node, <<"_config">>, _Section, _Key | _]}=Req) ->
- chttpd:send_error(Req, not_found);
-% GET /_node/$node/_stats
-handle_node_req(#httpd{method='GET', path_parts=[_, Node, <<"_stats">> | Path]}=Req) ->
- flush(Node, Req),
- Stats0 = call_node(Node, couch_stats, fetch, []),
- Stats = couch_stats_httpd:transform_stats(Stats0),
- Nested = couch_stats_httpd:nest(Stats),
- EJSON0 = couch_stats_httpd:to_ejson(Nested),
- EJSON1 = couch_stats_httpd:extract_path(Path, EJSON0),
- chttpd:send_json(Req, EJSON1);
-handle_node_req(#httpd{path_parts=[_, _Node, <<"_stats">>]}=Req) ->
- send_method_not_allowed(Req, "GET");
-% GET /_node/$node/_system
-handle_node_req(#httpd{method='GET', path_parts=[_, Node, <<"_system">>]}=Req) ->
- Stats = call_node(Node, chttpd_misc, get_stats, []),
- EJSON = couch_stats_httpd:to_ejson(Stats),
- send_json(Req, EJSON);
-handle_node_req(#httpd{path_parts=[_, _Node, <<"_system">>]}=Req) ->
- send_method_not_allowed(Req, "GET");
-% POST /_node/$node/_restart
-handle_node_req(#httpd{method='POST', path_parts=[_, Node, <<"_restart">>]}=Req) ->
- call_node(Node, init, restart, []),
- send_json(Req, 200, {[{ok, true}]});
-handle_node_req(#httpd{path_parts=[_, _Node, <<"_restart">>]}=Req) ->
- send_method_not_allowed(Req, "POST");
-handle_node_req(#httpd{path_parts=[_]}=Req) ->
- chttpd:send_error(Req, {bad_request, <<"Incomplete path to _node request">>});
-handle_node_req(#httpd{path_parts=[_, _Node]}=Req) ->
- chttpd:send_error(Req, {bad_request, <<"Incomplete path to _node request">>});
-handle_node_req(Req) ->
- chttpd:send_error(Req, not_found).
-
-
-call_node(Node0, Mod, Fun, Args) when is_binary(Node0) ->
- Node1 = try
- list_to_existing_atom(?b2l(Node0))
- catch
- error:badarg ->
- throw({not_found, <<"no such node: ", Node0/binary>>})
- end,
- call_node(Node1, Mod, Fun, Args);
-call_node(Node, Mod, Fun, Args) when is_atom(Node) ->
- case rpc:call(Node, Mod, Fun, Args) of
- {badrpc, nodedown} ->
- Reason = ?l2b(io_lib:format("~s is down", [Node])),
- throw({error, {nodedown, Reason}});
- Else ->
- Else
- end.
-
-flush(Node, Req) ->
- case couch_util:get_value("flush", chttpd:qs(Req)) of
- "true" ->
- call_node(Node, couch_stats_aggregator, flush, []);
- _Else ->
- ok
- end.
-
-% Note: this resource is exposed on the backdoor interface, but it's in chttpd
-% because it's not couch trunk
-handle_system_req(Req) ->
- Stats = get_stats(),
- EJSON = couch_stats_httpd:to_ejson(Stats),
- send_json(Req, EJSON).
-
-get_stats() ->
- Other = erlang:memory(system) - lists:sum([X || {_,X} <-
- erlang:memory([atom, code, binary, ets])]),
- Memory = [{other, Other} | erlang:memory([atom, atom_used, processes,
- processes_used, binary, code, ets])],
- {NumberOfGCs, WordsReclaimed, _} = statistics(garbage_collection),
- {{input, Input}, {output, Output}} = statistics(io),
- {CF, CDU} = db_pid_stats(),
- MessageQueues0 = [{couch_file, {CF}}, {couch_db_updater, {CDU}}],
- MessageQueues = MessageQueues0 ++ message_queues(registered()),
- [
- {uptime, couch_app:uptime() div 1000},
- {memory, {Memory}},
- {run_queue, statistics(run_queue)},
- {ets_table_count, length(ets:all())},
- {context_switches, element(1, statistics(context_switches))},
- {reductions, element(1, statistics(reductions))},
- {garbage_collection_count, NumberOfGCs},
- {words_reclaimed, WordsReclaimed},
- {io_input, Input},
- {io_output, Output},
- {os_proc_count, couch_proc_manager:get_proc_count()},
- {stale_proc_count, couch_proc_manager:get_stale_proc_count()},
- {process_count, erlang:system_info(process_count)},
- {process_limit, erlang:system_info(process_limit)},
- {message_queues, {MessageQueues}},
- {internal_replication_jobs, mem3_sync:get_backlog()},
- {distribution, {get_distribution_stats()}}
- ].
-
-db_pid_stats() ->
- {monitors, M} = process_info(whereis(couch_stats_process_tracker), monitors),
- Candidates = [Pid || {process, Pid} <- M],
- CouchFiles = db_pid_stats(couch_file, Candidates),
- CouchDbUpdaters = db_pid_stats(couch_db_updater, Candidates),
- {CouchFiles, CouchDbUpdaters}.
-
-db_pid_stats(Mod, Candidates) ->
- Mailboxes = lists:foldl(
- fun(Pid, Acc) ->
- case process_info(Pid, [message_queue_len, dictionary]) of
- undefined ->
- Acc;
- PI ->
- Dictionary = proplists:get_value(dictionary, PI, []),
- case proplists:get_value('$initial_call', Dictionary) of
- {Mod, init, 1} ->
- case proplists:get_value(message_queue_len, PI) of
- undefined -> Acc;
- Len -> [Len|Acc]
- end;
- _ ->
- Acc
- end
- end
- end, [], Candidates
- ),
- format_pid_stats(Mailboxes).
-
-format_pid_stats([]) ->
- [];
-format_pid_stats(Mailboxes) ->
- Sorted = lists:sort(Mailboxes),
- Count = length(Sorted),
- [
- {count, Count},
- {min, hd(Sorted)},
- {max, lists:nth(Count, Sorted)},
- {'50', lists:nth(round(Count * 0.5), Sorted)},
- {'90', lists:nth(round(Count * 0.9), Sorted)},
- {'99', lists:nth(round(Count * 0.99), Sorted)}
- ].
-
-get_distribution_stats() ->
- lists:map(fun({Node, Socket}) ->
- {ok, Stats} = inet:getstat(Socket),
- {Node, {Stats}}
- end, erlang:system_info(dist_ctrl)).
-
handle_up_req(#httpd{method='GET'} = Req) ->
case config:get("couchdb", "maintenance_mode") of
"true" ->
@@ -506,14 +290,7 @@ handle_up_req(#httpd{method='GET'} = Req) ->
handle_up_req(Req) ->
send_method_not_allowed(Req, "GET,HEAD").
-message_queues(Registered) ->
- lists:map(fun(Name) ->
- Type = message_queue_len,
- {Type, Length} = process_info(whereis(Name), Type),
- {Name, Length}
- end, Registered).
-
get_docroot() ->
- % if the env var isn’t set, let’s not throw an error, but
+ % if the env var isn't set, let's not throw an error, but
% assume the current working dir is what we want
os:getenv("COUCHDB_FAUXTON_DOCROOT", "").
diff --git a/src/chttpd/src/chttpd_node.erl b/src/chttpd/src/chttpd_node.erl
new file mode 100644
index 0000000..74ff99b
--- /dev/null
+++ b/src/chttpd/src/chttpd_node.erl
@@ -0,0 +1,321 @@
+% 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.
+
+-module(chttpd_node).
+
+-export([
+ handle_node_req/1,
+ do_db_req/4,
+ get_stats/0
+]).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch_mrview/include/couch_mrview.hrl").
+
+-import(chttpd,
+ [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
+ send_error/2,send_chunk/2,start_chunked_response/3]).
+
+% Node-specific (unclustered) request handlers
+
+% Support _local meaning this node
+handle_node_req(#httpd{path_parts=[_, <<"_local">>]}=Req) ->
+ send_json(Req, 200, {[{name, node()}]});
+handle_node_req(#httpd{path_parts=[A, <<"_local">>|Rest]}=Req) ->
+ handle_node_req(Req#httpd{path_parts=[A, node()] ++ Rest});
+
+% GET /_node/$node/_config
+handle_node_req(#httpd{method='GET', path_parts=[_, Node, <<"_config">>]}=Req) ->
+ Grouped = lists:foldl(fun({{Section, Key}, Value}, Acc) ->
+ case dict:is_key(Section, Acc) of
+ true ->
+ dict:append(Section, {list_to_binary(Key), list_to_binary(Value)}, Acc);
+ false ->
+ dict:store(Section, [{list_to_binary(Key), list_to_binary(Value)}], Acc)
+ end
+ end, dict:new(), call_node(Node, config, all, [])),
+ KVs = dict:fold(fun(Section, Values, Acc) ->
+ [{list_to_binary(Section), {Values}} | Acc]
+ end, [], Grouped),
+ send_json(Req, 200, {KVs});
+handle_node_req(#httpd{path_parts=[_, _Node, <<"_config">>]}=Req) ->
+ send_method_not_allowed(Req, "GET");
+% GET /_node/$node/_config/Section
+handle_node_req(#httpd{method='GET', path_parts=[_, Node, <<"_config">>, Section]}=Req) ->
+ KVs = [{list_to_binary(Key), list_to_binary(Value)}
+ || {Key, Value} <- call_node(Node, config, get, [Section])],
+ send_json(Req, 200, {KVs});
+handle_node_req(#httpd{path_parts=[_, _Node, <<"_config">>, _Section]}=Req) ->
+ send_method_not_allowed(Req, "GET");
+% PUT /_node/$node/_config/Section/Key
+% "value"
+handle_node_req(#httpd{method='PUT', path_parts=[_, Node, <<"_config">>, Section, Key]}=Req) ->
+ couch_util:check_config_blacklist(Section),
+ Value = couch_util:trim(chttpd:json_body(Req)),
+ Persist = chttpd:header_value(Req, "X-Couch-Persist") /= "false",
+ OldValue = call_node(Node, config, get, [Section, Key, ""]),
+ case call_node(Node, config, set, [Section, Key, ?b2l(Value), Persist]) of
+ ok ->
+ send_json(Req, 200, list_to_binary(OldValue));
+ {error, Reason} ->
+ send_error(Req, {bad_request, Reason})
+ end;
+% GET /_node/$node/_config/Section/Key
+handle_node_req(#httpd{method='GET', path_parts=[_, Node, <<"_config">>, Section, Key]}=Req) ->
+ case call_node(Node, config, get, [Section, Key, undefined]) of
+ undefined ->
+ throw({not_found, unknown_config_value});
+ Value ->
+ send_json(Req, 200, list_to_binary(Value))
+ end;
+% DELETE /_node/$node/_config/Section/Key
+handle_node_req(#httpd{method='DELETE',path_parts=[_, Node, <<"_config">>, Section, Key]}=Req) ->
+ couch_util:check_config_blacklist(Section),
+ Persist = chttpd:header_value(Req, "X-Couch-Persist") /= "false",
+ case call_node(Node, config, get, [Section, Key, undefined]) of
+ undefined ->
+ throw({not_found, unknown_config_value});
+ OldValue ->
+ case call_node(Node, config, delete, [Section, Key, Persist]) of
+ ok ->
+ send_json(Req, 200, list_to_binary(OldValue));
+ {error, Reason} ->
+ send_error(Req, {bad_request, Reason})
+ end
+ end;
+handle_node_req(#httpd{path_parts=[_, _Node, <<"_config">>, _Section, _Key]}=Req) ->
+ send_method_not_allowed(Req, "GET,PUT,DELETE");
+handle_node_req(#httpd{path_parts=[_, _Node, <<"_config">>, _Section, _Key | _]}=Req) ->
+ send_error(Req, not_found);
+
+% GET /_node/$node/_stats
+handle_node_req(#httpd{method='GET', path_parts=[_, Node, <<"_stats">> | Path]}=Req) ->
+ flush(Node, Req),
+ Stats0 = call_node(Node, couch_stats, fetch, []),
+ Stats = couch_stats_httpd:transform_stats(Stats0),
+ Nested = couch_stats_httpd:nest(Stats),
+ EJSON0 = couch_stats_httpd:to_ejson(Nested),
+ EJSON1 = couch_stats_httpd:extract_path(Path, EJSON0),
+ send_json(Req, EJSON1);
+handle_node_req(#httpd{path_parts=[_, _Node, <<"_stats">>]}=Req) ->
+ send_method_not_allowed(Req, "GET");
+
+% GET /_node/$node/_system
+handle_node_req(#httpd{method='GET', path_parts=[_, Node, <<"_system">>]}=Req) ->
+ Stats = call_node(Node, chttpd_node, get_stats, []),
+ EJSON = couch_stats_httpd:to_ejson(Stats),
+ send_json(Req, EJSON);
+handle_node_req(#httpd{path_parts=[_, _Node, <<"_system">>]}=Req) ->
+ send_method_not_allowed(Req, "GET");
+
+% POST /_node/$node/_restart
+handle_node_req(#httpd{method='POST', path_parts=[_, Node, <<"_restart">>]}=Req) ->
+ call_node(Node, init, restart, []),
+ send_json(Req, 200, {[{ok, true}]});
+handle_node_req(#httpd{path_parts=[_, _Node, <<"_restart">>]}=Req) ->
+ send_method_not_allowed(Req, "POST");
+
+% GET /_node/$node/_all_dbs
+handle_node_req(#httpd{method='GET', path_parts=[_, Node, <<"_all_dbs">>]}=Req) ->
+ {ok, DbNames} = call_node(Node, couch_server, all_databases, []),
+ send_json(Req, DbNames);
+handle_node_req(#httpd{path_parts=[_, _Node, <<"_all_dbs">>]}=Req) ->
+ send_method_not_allowed(Req, "GET");
+
+% /_node/$node/{db} and /_node/$node/{db}/...
+handle_node_req(#httpd{path_parts=[_, _Node]}=Req) ->
+ send_error(Req, {bad_request, <<"Incomplete path to _node request">>});
+handle_node_req(#httpd{path_parts=[_, Node | PathParts],
+ mochi_req=MochiReq0}=Req0) ->
+ % strip /_node/{node} from Req0 before descending further
+ RawUri = MochiReq0:get(raw_path),
+ {_, Query, Fragment} = mochiweb_util:urlsplit_path(RawUri),
+ NewPath0 = ?l2b("/" ++ string:join([?b2l(P) || P <- PathParts], [$\/])),
+ NewRawPath = mochiweb_util:urlunsplit_path({NewPath0, Query, Fragment}),
+ MochiReq = mochiweb_request:new(MochiReq0:get(socket),
+ % MochiReq:get(opts),
+ MochiReq0:get(method),
+ NewRawPath,
+ MochiReq0:get(version),
+ MochiReq0:get(headers)),
+ Req = Req0#httpd{
+ mochi_req = MochiReq,
+ path_parts = PathParts
+ },
+ handle_node_db_req(Req, Node);
+
+% Abnormal _node requests follow
+handle_node_req(#httpd{path_parts=[_]}=Req) ->
+ send_error(Req, {bad_request, <<"Incomplete path to _node request">>});
+handle_node_req(Req) ->
+ send_error(Req, not_found).
+
+
+% Unclustered system db or shard requests
+handle_node_db_req(#httpd{method=Method,
+ path_parts=[DbName|RestParts],
+ user_ctx=Ctx}=Req,
+ Node) ->
+ chttpd:verify_is_server_admin(Req),
+ %DbsDbName = config:get("mem3", "shards_db", "_dbs"),
+ %NodesDbName = config:get("mem3", "nodes_db", "_nodes"),
+ case {Method, DbName, RestParts} of
+ {'GET', DbName, []} ->
+ case call_node(Node, chttpd_node, do_db_req,
+ [Ctx, DbName, couch_db, get_db_info]) of
+ {ok, DbInfo} ->
+ send_json(Req, {DbInfo});
+ {error, {_, _}=Error} ->
+ send_error(Req, Error);
+ {_, _}=Error ->
+ send_error(Req, Error)
+ end;
+ {_, DbName, []} ->
+ send_method_not_allowed(Req, "GET");
+ %{'GET', DbName, [<<"_all_docs">>]} ->
+ % ...
+ %{'POST', DbName, [<<"_compact">>]} ->
+ % ...
+ %{_, DbName, [DocName]} ->
+ % %only support doc CRUD in _dbs/_nodes, and _info endpoint on all
+ %{'GET', DbName, [<<"_design">>, DDoc, <<"_info">>]} ->
+ % %individual view shard info stats
+ {_, _, _} ->
+ send_error(Req, {bad_request, <<"invalid _node request">>})
+ end.
+
+% below adapted from old couch_httpd_db
+% all of these run on the requested node
+do_db_req(Ctx, DbName, Mod, Fun) ->
+ case couch_db:open(DbName, [{user_ctx, Ctx}]) of
+ {ok, Db} ->
+ try
+ erlang:apply(Mod, Fun, [Db])
+ after
+ catch couch_db:close(Db)
+ end;
+ Error ->
+ throw(Error)
+ end.
+
+
+call_node(Node0, Mod, Fun, Args) when is_binary(Node0) ->
+ Node1 = try
+ list_to_existing_atom(?b2l(Node0))
+ catch
+ error:badarg ->
+ throw({not_found, <<"no such node: ", Node0/binary>>})
+ end,
+ call_node(Node1, Mod, Fun, Args);
+call_node(Node, Mod, Fun, Args) when is_atom(Node) ->
+ couch_log:error("In call_node ~p ~p ~p ~p\n", [Node, Mod, Fun, Args]),
+ case rpc:call(Node, Mod, Fun, Args) of
+ {badrpc, nodedown} ->
+ Reason = ?l2b(io_lib:format("~s is down", [Node])),
+ throw({error, {nodedown, Reason}});
+ Else ->
+ Else
+ end.
+
+flush(Node, Req) ->
+ case couch_util:get_value("flush", chttpd:qs(Req)) of
+ "true" ->
+ call_node(Node, couch_stats_aggregator, flush, []);
+ _Else ->
+ ok
+ end.
+
+get_stats() ->
+ Other = erlang:memory(system) - lists:sum([X || {_,X} <-
+ erlang:memory([atom, code, binary, ets])]),
+ Memory = [{other, Other} | erlang:memory([atom, atom_used, processes,
+ processes_used, binary, code, ets])],
+ {NumberOfGCs, WordsReclaimed, _} = statistics(garbage_collection),
+ {{input, Input}, {output, Output}} = statistics(io),
+ {CF, CDU} = db_pid_stats(),
+ MessageQueues0 = [{couch_file, {CF}}, {couch_db_updater, {CDU}}],
+ MessageQueues = MessageQueues0 ++ message_queues(registered()),
+ [
+ {uptime, couch_app:uptime() div 1000},
+ {memory, {Memory}},
+ {run_queue, statistics(run_queue)},
+ {ets_table_count, length(ets:all())},
+ {context_switches, element(1, statistics(context_switches))},
+ {reductions, element(1, statistics(reductions))},
+ {garbage_collection_count, NumberOfGCs},
+ {words_reclaimed, WordsReclaimed},
+ {io_input, Input},
+ {io_output, Output},
+ {os_proc_count, couch_proc_manager:get_proc_count()},
+ {stale_proc_count, couch_proc_manager:get_stale_proc_count()},
+ {process_count, erlang:system_info(process_count)},
+ {process_limit, erlang:system_info(process_limit)},
+ {message_queues, {MessageQueues}},
+ {internal_replication_jobs, mem3_sync:get_backlog()},
+ {distribution, {get_distribution_stats()}}
+ ].
+
+db_pid_stats() ->
+ {monitors, M} = process_info(whereis(couch_stats_process_tracker), monitors),
+ Candidates = [Pid || {process, Pid} <- M],
+ CouchFiles = db_pid_stats(couch_file, Candidates),
+ CouchDbUpdaters = db_pid_stats(couch_db_updater, Candidates),
+ {CouchFiles, CouchDbUpdaters}.
+
+db_pid_stats(Mod, Candidates) ->
+ Mailboxes = lists:foldl(
+ fun(Pid, Acc) ->
+ case process_info(Pid, [message_queue_len, dictionary]) of
+ undefined ->
+ Acc;
+ PI ->
+ Dictionary = proplists:get_value(dictionary, PI, []),
+ case proplists:get_value('$initial_call', Dictionary) of
+ {Mod, init, 1} ->
+ case proplists:get_value(message_queue_len, PI) of
+ undefined -> Acc;
+ Len -> [Len|Acc]
+ end;
+ _ ->
+ Acc
+ end
+ end
+ end, [], Candidates
+ ),
+ format_pid_stats(Mailboxes).
+
+format_pid_stats([]) ->
+ [];
+format_pid_stats(Mailboxes) ->
+ Sorted = lists:sort(Mailboxes),
+ Count = length(Sorted),
+ [
+ {count, Count},
+ {min, hd(Sorted)},
+ {max, lists:nth(Count, Sorted)},
+ {'50', lists:nth(round(Count * 0.5), Sorted)},
+ {'90', lists:nth(round(Count * 0.9), Sorted)},
+ {'99', lists:nth(round(Count * 0.99), Sorted)}
+ ].
+
+get_distribution_stats() ->
+ lists:map(fun({Node, Socket}) ->
+ {ok, Stats} = inet:getstat(Socket),
+ {Node, {Stats}}
+ end, erlang:system_info(dist_ctrl)).
+
+message_queues(Registered) ->
+ lists:map(fun(Name) ->
+ Type = message_queue_len,
+ {Type, Length} = process_info(whereis(Name), Type),
+ {Name, Length}
+ end, Registered).
diff --git a/src/couch/src/couch.app.src b/src/couch/src/couch.app.src
index 706b439..2b642c0 100644
--- a/src/couch/src/couch.app.src
+++ b/src/couch/src/couch.app.src
@@ -60,8 +60,7 @@
{"_uuids", "{couch_httpd_misc_handlers, handle_uuids_req}"},
{"_stats", "{couch_stats_httpd, handle_stats_req}"},
{"_session", "{couch_httpd_auth, handle_session_req}"},
- {"_plugins", "{couch_plugins_httpd, handle_req}"},
- {"_system", "{chttpd_misc, handle_system_req}"}
+ {"_plugins", "{couch_plugins_httpd, handle_req}"}
]},
{ httpd_db_handlers, [
{"_all_docs", "{couch_mrview_http, handle_all_docs_req}"},
diff --git a/src/couch/src/couch_httpd.erl b/src/couch/src/couch_httpd.erl
index 10b44d1..8024756 100644
--- a/src/couch/src/couch_httpd.erl
+++ b/src/couch/src/couch_httpd.erl
@@ -37,6 +37,7 @@
-export([validate_host/1]).
-export([validate_bind_address/1]).
-export([check_max_request_length/1]).
+-export([handle_request/1]).
-define(HANDLER_NAME_IN_MODULE_POS, 6).
@@ -104,38 +105,14 @@ start_link(Name, Options) ->
Else -> Else
end,
ok = validate_bind_address(BindAddress),
- DefaultFun = make_arity_1_fun("{couch_httpd_db, handle_request}"),
-
- {ok, HttpdGlobalHandlers} = application:get_env(httpd_global_handlers),
-
- UrlHandlersList = lists:map(
- fun({UrlKey, SpecStr}) ->
- {?l2b(UrlKey), make_arity_1_fun(SpecStr)}
- end, HttpdGlobalHandlers),
-
- {ok, HttpdDbHandlers} = application:get_env(httpd_db_handlers),
- DbUrlHandlersList = lists:map(
- fun({UrlKey, SpecStr}) ->
- {?l2b(UrlKey), make_arity_2_fun(SpecStr)}
- end, HttpdDbHandlers),
-
- {ok, HttpdDesignHandlers} = application:get_env(httpd_design_handlers),
-
- DesignUrlHandlersList = lists:map(
- fun({UrlKey, SpecStr}) ->
- {?l2b(UrlKey), make_arity_3_fun(SpecStr)}
- end, HttpdDesignHandlers),
-
- UrlHandlers = dict:from_list(UrlHandlersList),
- DbUrlHandlers = dict:from_list(DbUrlHandlersList),
- DesignUrlHandlers = dict:from_list(DesignUrlHandlersList),
{ok, ServerOptions} = couch_util:parse_term(
config:get("httpd", "server_options", "[]")),
{ok, SocketOptions} = couch_util:parse_term(
config:get("httpd", "socket_options", "[]")),
set_auth_handlers(),
+ Handlers = get_httpd_handlers(),
% ensure uuid is set so that concurrent replications
% get the same value.
@@ -148,9 +125,7 @@ start_link(Name, Options) ->
_ ->
ok = mochiweb_socket:setopts(Req:get(socket), SocketOptions)
end,
- apply(?MODULE, handle_request, [
- Req, DefaultFun, UrlHandlers, DbUrlHandlers, DesignUrlHandlers
- ])
+ apply(?MODULE, handle_request, [Req] ++ Handlers)
end,
% set mochiweb options
@@ -187,6 +162,34 @@ set_auth_handlers() ->
auth_handler_name(SpecStr) ->
lists:nth(?HANDLER_NAME_IN_MODULE_POS, re:split(SpecStr, "[\\W_]", [])).
+get_httpd_handlers() ->
+ {ok, HttpdGlobalHandlers} = application:get_env(httpd_global_handlers),
+
+ UrlHandlersList = lists:map(
+ fun({UrlKey, SpecStr}) ->
+ {?l2b(UrlKey), make_arity_1_fun(SpecStr)}
+ end, HttpdGlobalHandlers),
+
+ {ok, HttpdDbHandlers} = application:get_env(httpd_db_handlers),
+
+ DbUrlHandlersList = lists:map(
+ fun({UrlKey, SpecStr}) ->
+ {?l2b(UrlKey), make_arity_2_fun(SpecStr)}
+ end, HttpdDbHandlers),
+
+ {ok, HttpdDesignHandlers} = application:get_env(httpd_design_handlers),
+
+ DesignUrlHandlersList = lists:map(
+ fun({UrlKey, SpecStr}) ->
+ {?l2b(UrlKey), make_arity_3_fun(SpecStr)}
+ end, HttpdDesignHandlers),
+
+ UrlHandlers = dict:from_list(UrlHandlersList),
+ DbUrlHandlers = dict:from_list(DbUrlHandlersList),
+ DesignUrlHandlers = dict:from_list(DesignUrlHandlersList),
+ DefaultFun = make_arity_1_fun("{couch_httpd_db, handle_request}"),
+ [DefaultFun, UrlHandlers, DbUrlHandlers, DesignUrlHandlers].
+
% SpecStr is a string like "{my_module, my_fun}"
% or "{my_module, my_fun, <<"my_arg">>}"
make_arity_1_fun(SpecStr) ->
@@ -217,6 +220,14 @@ make_arity_3_fun(SpecStr) ->
make_fun_spec_strs(SpecStr) ->
re:split(SpecStr, "(?<=})\\s*,\\s*(?={)", [{return, list}]).
+handle_request(MochiReq) ->
+ %[DefaultFun, UrlHandlers, DbUrlHandlers, DesignUrlHandlers] = get_httpd_handlers(),
+ DefaultFun = make_arity_1_fun("{couch_httpd_db, handle_request}"),
+ EmptyDict = dict:new(),
+% handle_request(MochiReq, DefaultFun, UrlHandlers, DbUrlHandlers,
+% DesignUrlHandlers).
+ handle_request(MochiReq, DefaultFun, EmptyDict, EmptyDict, EmptyDict).
+
handle_request(MochiReq, DefaultFun, UrlHandlers, DbUrlHandlers,
DesignUrlHandlers) ->
%% reset rewrite count for new request
[couchdb] 02/04: enable tuple_calls
Posted by rn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
rnewson pushed a commit to branch 1523-bye-bye-5986-rnewson
in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 05815d1262d1b85db68e2df5ce63b966c9bb6c7b
Author: Robert Newson <rn...@apache.org>
AuthorDate: Mon Nov 11 19:58:58 2019 +0000
enable tuple_calls
---
src/chttpd/src/chttpd_node.erl | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/chttpd/src/chttpd_node.erl b/src/chttpd/src/chttpd_node.erl
index 74ff99b..711969f 100644
--- a/src/chttpd/src/chttpd_node.erl
+++ b/src/chttpd/src/chttpd_node.erl
@@ -11,6 +11,7 @@
% the License.
-module(chttpd_node).
+-compile(tuple_calls).
-export([
handle_node_req/1,
[couchdb] 03/04: remove nesting
Posted by rn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
rnewson pushed a commit to branch 1523-bye-bye-5986-rnewson
in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit ba6cd467f5f812d785039b6a5bedf0d135e4fc5b
Author: Robert Newson <rn...@apache.org>
AuthorDate: Mon Nov 11 20:04:25 2019 +0000
remove nesting
---
src/chttpd/src/chttpd_node.erl | 42 ++++++++++++++++++------------------------
1 file changed, 18 insertions(+), 24 deletions(-)
diff --git a/src/chttpd/src/chttpd_node.erl b/src/chttpd/src/chttpd_node.erl
index 711969f..c560e0e 100644
--- a/src/chttpd/src/chttpd_node.erl
+++ b/src/chttpd/src/chttpd_node.erl
@@ -162,17 +162,9 @@ handle_node_req(Req) ->
% Unclustered system db or shard requests
-handle_node_db_req(#httpd{method=Method,
- path_parts=[DbName|RestParts],
- user_ctx=Ctx}=Req,
- Node) ->
- chttpd:verify_is_server_admin(Req),
- %DbsDbName = config:get("mem3", "shards_db", "_dbs"),
- %NodesDbName = config:get("mem3", "nodes_db", "_nodes"),
- case {Method, DbName, RestParts} of
- {'GET', DbName, []} ->
- case call_node(Node, chttpd_node, do_db_req,
- [Ctx, DbName, couch_db, get_db_info]) of
+handle_node_db_req(#httpd{method='GET', path_parts=[DbName]}=Req, Node) ->
+ case call_node(Node, chttpd_node, do_db_req,
+ [Req#httpd.user_ctx, DbName, couch_db, get_db_info]) of
{ok, DbInfo} ->
send_json(Req, {DbInfo});
{error, {_, _}=Error} ->
@@ -180,19 +172,21 @@ handle_node_db_req(#httpd{method=Method,
{_, _}=Error ->
send_error(Req, Error)
end;
- {_, DbName, []} ->
- send_method_not_allowed(Req, "GET");
- %{'GET', DbName, [<<"_all_docs">>]} ->
- % ...
- %{'POST', DbName, [<<"_compact">>]} ->
- % ...
- %{_, DbName, [DocName]} ->
- % %only support doc CRUD in _dbs/_nodes, and _info endpoint on all
- %{'GET', DbName, [<<"_design">>, DDoc, <<"_info">>]} ->
- % %individual view shard info stats
- {_, _, _} ->
- send_error(Req, {bad_request, <<"invalid _node request">>})
- end.
+
+handle_node_db_req(#httpd{path_parts=[_DbName]}=Req, _Node) ->
+ send_method_not_allowed(Req, "GET");
+
+%{'GET', DbName, [<<"_all_docs">>]} ->
+% ...
+%{'POST', DbName, [<<"_compact">>]} ->
+% ...
+%{_, DbName, [DocName]} ->
+% %only support doc CRUD in _dbs/_nodes, and _info endpoint on all
+%{'GET', DbName, [<<"_design">>, DDoc, <<"_info">>]} ->
+% %individual view shard info stats
+
+handle_node_db_req(Req, _Node) ->
+ send_error(Req, {bad_request, <<"invalid _node request">>}).
% below adapted from old couch_httpd_db
% all of these run on the requested node