You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2019/11/21 22:55:31 UTC

[couchdb] 01/01: Add operation names for all HTTP endpoints

This is an automated email from the ASF dual-hosted git repository.

davisp pushed a commit to branch opentracing-http-operations
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit ca57a7183f96b5868c130a3c7844cfd4a7e8f8bc
Author: Paul J. Davis <pa...@gmail.com>
AuthorDate: Thu Nov 21 16:54:44 2019 -0600

    Add operation names for all HTTP endpoints
    
    This adds operation names to all valid HTTP end points. This covers all
    of `make elixir` except for seven requests that are testing specific
    error conditions in URL and Methods for various endpoints.
---
 src/chttpd/src/chttpd.erl                          |   6 +-
 src/chttpd/src/chttpd_handlers.erl                 |  18 +-
 src/chttpd/src/chttpd_httpd_handlers.erl           | 454 ++++++++++++++++++++-
 .../src/global_changes_httpd_handlers.erl          |   8 +-
 src/mango/src/mango_httpd_handlers.erl             |  31 +-
 src/mem3/src/mem3_httpd_handlers.erl               |  38 +-
 src/setup/src/setup_httpd_handlers.erl             |  12 +-
 7 files changed, 540 insertions(+), 27 deletions(-)

diff --git a/src/chttpd/src/chttpd.erl b/src/chttpd/src/chttpd.erl
index 46b3b24..c4bfa60 100644
--- a/src/chttpd/src/chttpd.erl
+++ b/src/chttpd/src/chttpd.erl
@@ -1246,12 +1246,16 @@ start_span(Req) ->
         path_parts = PathParts
     } = Req,
     {OperationName, ExtraTags} = get_action(Req),
+    Path = case PathParts of
+        [] -> <<"">>;
+        [_ | _] -> filename:join(PathParts)
+    end,
     Tags = maps:merge(#{
         peer => Peer,
         'http.method' => Method,
         nonce => Nonce,
         'http.url' => MochiReq:get(raw_path),
-        path_parts => PathParts,
+        path_parts => Path,
         'span.kind' => <<"server">>,
         component => <<"couchdb.chttpd">>
     }, ExtraTags),
diff --git a/src/chttpd/src/chttpd_handlers.erl b/src/chttpd/src/chttpd_handlers.erl
index c07b209..17d2952 100644
--- a/src/chttpd/src/chttpd_handlers.erl
+++ b/src/chttpd/src/chttpd_handlers.erl
@@ -37,8 +37,24 @@ design_handler(HandlerKey, DefaultFun) ->
     select(collect(design_handler, [HandlerKey]), DefaultFun).
 
 handler_info(HttpReq) ->
+    #httpd{
+        method = Method,
+        path_parts = PathParts
+    } = HttpReq,
     Default = {'unknown.unknown', #{}},
-    select(collect(handler_info, [HttpReq]), Default).
+    try
+        select(collect(handler_info, [Method, PathParts, HttpReq]), Default)
+    catch Type:Reason ->
+        Stack = erlang:get_stacktrace(),
+        couch_log:error("~s :: handler_info failure for ~p : ~p:~p :: ~p", [
+                ?MODULE,
+                get(nonce),
+                Type,
+                Reason,
+                Stack
+            ]),
+        Default
+    end.
 
 %% ------------------------------------------------------------------
 %% Internal Function Definitions
diff --git a/src/chttpd/src/chttpd_httpd_handlers.erl b/src/chttpd/src/chttpd_httpd_handlers.erl
index de7f3a0..7c657f5 100644
--- a/src/chttpd/src/chttpd_httpd_handlers.erl
+++ b/src/chttpd/src/chttpd_httpd_handlers.erl
@@ -12,7 +12,7 @@
 
 -module(chttpd_httpd_handlers).
 
--export([url_handler/1, db_handler/1, design_handler/1, handler_info/1]).
+-export([url_handler/1, db_handler/1, design_handler/1, handler_info/3]).
 
 -include_lib("couch/include/couch_db.hrl").
 
@@ -48,27 +48,439 @@ design_handler(<<"_info">>)    -> fun chttpd_db:handle_design_info_req/3;
 design_handler(<<"_rewrite">>) -> fun chttpd_rewrite:handle_rewrite_req/3;
 design_handler(_) -> no_match.
 
-%% TODO Populate in another PR
-handler_info(#httpd{path_parts=[<<"_all_dbs">>], method=Method})
-        when Method =:= 'HEAD' orelse Method =:= 'GET' ->
-    {'all-dbs.read', #{}};
 
-handler_info(#httpd{path_parts=[<<"_session">>], method=Method})
-        when Method =:= 'HEAD' orelse Method =:= 'GET' ->
+handler_info('GET', [], _) ->
+    {'welcome_message.read', #{}};
+
+handler_info('GET', [<<"_active_tasks">>], _) ->
+    {'active_tasks.read', #{}};
+
+handler_info('GET', [<<"_all_dbs">>], _) ->
+    {'all_dbs.read', #{}};
+
+handler_info('POST', [<<"_dbs_info">>], _) ->
+    {'dbs_info.read', #{}};
+
+handler_info('GET', [<<"_node">>, <<"_local">>], _) ->
+    {'node.name.read', #{}};
+
+handler_info(Method, [<<"_node">>, <<"_local">> | Rest], HttpReq) ->
+    handler_info(Method, [<<"_node">>, node() | Rest], HttpReq);
+
+handler_info('GET', [<<"_node">>, Node, <<"_config">>], _) ->
+    {'node.config.all.read', #{node => Node}};
+
+handler_info('GET', [<<"_node">>, Node, <<"_config">>, Section], _) ->
+    {'node.config.section.read', #{node => Node, 'config.section' => Section}};
+
+handler_info('GET', [<<"_node">>, Node, <<"_config">>, Section, Key], _) ->
+    {'node.config.key.read', #{
+        node => Node,
+        'config.section' => Section,
+        'config.key' => Key
+    }};
+
+handler_info('PUT', [<<"_node">>, Node, <<"_config">>, Section, Key], _) ->
+    {'node.config.key.write', #{
+        node => Node,
+        'config.section' => Section,
+        'config.key' => Key
+    }};
+
+handler_info('DELETE', [<<"_node">>, Node, <<"_config">>, Section, Key], _) ->
+    {'node.config.key.delete', #{
+        node => Node,
+        'config.section' => Section,
+        'config.key' => Key
+    }};
+
+handler_info('GET', [<<"_node">>, Node, <<"_stats">> | Path], _) ->
+    {'node.stats.read', #{node => Node, 'stat.path' => Path}};
+
+handler_info('GET', [<<"_node">>, Node, <<"_system">>], _) ->
+    {'node.system.read', #{node => Node}};
+
+handler_info('POST', [<<"_node">>, Node, <<"_restart">>], _) ->
+    {'node.restart.execute', #{node => Node}};
+
+handler_info('POST', [<<"_reload_query_servers">>], _) ->
+    {'query_servers.reload', #{}};
+
+handler_info('POST', [<<"_replicate">>], _) ->
+    {'replication.create', #{}};
+
+handler_info('GET', [<<"_scheduler">>, <<"jobs">>], _) ->
+    {'replication.jobs.read', #{}};
+
+handler_info('GET', [<<"_scheduler">>, <<"jobs">>, JobId], _) ->
+    {'replication.job.read', #{'job.id' => JobId}};
+
+handler_info('GET', [<<"_scheduler">>, <<"docs">>], _) ->
+    {'replication.docs.read', #{'db.name' => <<"_replicator">>}};
+
+handler_info('GET', [<<"_scheduler">>, <<"docs">>, Db], _) ->
+    {'replication.docs.read', #{'db.name' => Db}};
+
+handler_info('GET', [<<"_scheduler">>, <<"docs">>, Db, DocId], _) ->
+    {'replication.doc.read', #{'db.name' => Db, 'doc.id' => DocId}};
+
+handler_info('GET', [<<"_scheduler">>, <<"docs">> | Path], _) ->
+    case lists:splitwith(fun(Elem) -> Elem /= <<"_replicator">> end, Path) of
+        {_, [<<"_replicator">>]} ->
+            {'replication.docs.read', #{
+                'db.name' => filename:join(Path)
+            }};
+        {DbParts, [<<"_replicator">>, DocId]} ->
+            {'replication.doc.read', #{
+                'db.name' => filename:join(DbParts ++ [<<"_replicator">>]),
+                'doc.id' => DocId
+            }};
+        _ ->
+            no_match
+    end;
+
+handler_info('GET', [<<"_session">>], _) ->
     {'session.read', #{}};
-handler_info(#httpd{path_parts=[<<"_session">>], method='POST'}) ->
-    {'session.write', #{}};
-handler_info(#httpd{path_parts=[<<"_session">>], method='DELETE'}) ->
+
+handler_info('POST', [<<"_session">>], _) ->
+    {'session.create', #{}};
+
+handler_info('DELETE', [<<"_session">>], _) ->
     {'session.delete', #{}};
 
-handler_info(#httpd{path_parts=[_Db], method=Method})
-        when Method =:= 'HEAD' orelse Method =:= 'GET' ->
-    {'database-info.read', #{}};
-handler_info(#httpd{path_parts=[_Db], method='POST'}) ->
-    {'document.write', #{}};
-handler_info(#httpd{path_parts=[_Db], method='PUT'}) ->
-    {'database.create', #{}};
-handler_info(#httpd{path_parts=[_Db], method='DELETE'}) ->
-    {'database.delete', #{}};
-
-handler_info(_) -> no_match.
+handler_info('GET', [<<"_up">>], _) ->
+    {'health.read', #{}};
+
+handler_info('GET', [<<"_utils">> | Path], _) ->
+    {'utils.read', #{'file.path' => filename:join(Path)}};
+
+handler_info('GET', [<<"_uuids">>], _) ->
+    {'uuids.read', #{}};
+
+handler_info('GET', [<<"favicon.ico">>], _) ->
+    {'favicon.ico.read', #{}};
+
+
+handler_info(Method, [<<"_", _/binary>> = Part| Rest], Req) ->
+    % Maybe bail here so that we don't trample over a
+    % different url_handler plugin. However, we continue
+    % on for known system databases.
+    DbName = case Part of
+        <<"_dbs">> -> '_dbs';
+        <<"_global_changes">> -> '_global_changes';
+        <<"_metadata">> -> '_metadata';
+        <<"_nodes">> -> '_nodes';
+        <<"_replicator">> -> '_replicator';
+        <<"_users">> -> '_users';
+        _ -> no_match
+    end,
+    if DbName == no_match -> no_match; true ->
+        handler_info(Method, [DbName | Rest], Req)
+    end;
+
+handler_info('GET', [Db], _) ->
+    {'db.info.read', #{'db.name' => Db}};
+
+handler_info('PUT', [Db], _) ->
+    {'db.create', #{'db.name' => Db}};
+
+handler_info('POST', [Db], _) ->
+    {'db.doc.write', #{'db.name' => Db}};
+
+handler_info('DELETE', [Db], _) ->
+    {'db.delete', #{'db.name' => Db}};
+
+handler_info(M, [Db, <<"_all_docs">>], _) when M == 'GET'; M == 'POST' ->
+    {'db.all_docs.read', #{'db.name' => Db}};
+
+handler_info('POST', [Db, <<"_all_docs">>, <<"queries">>], _) ->
+    {'db.all_docs.read', #{'db.name' => Db, multi => true}};
+
+handler_info('POST', [Db, <<"_bulk_docs">>], _) ->
+    {'db.docs.write', #{'db.name' => Db, bulk => true}};
+
+handler_info('POST', [Db, <<"_bulk_get">>], _) ->
+    {'db.docs.read', #{'db.name' => Db, bulk => true}};
+
+handler_info('GET', [Db, <<"_changes">>], _) ->
+    {'db.changes.read', #{'db.name' => Db}};
+
+handler_info('POST', [Db, <<"_changes">>], _) ->
+    {'db.changes.read', #{'db.name' => Db}};
+
+handler_info('POST', [Db, <<"_compact">>], _) ->
+    {'db.compact.execute', #{'db.name' => Db}};
+
+handler_info('GET', [Db, <<"_design">>, Name], _) ->
+    {'db.design.doc.read', #{'db.name' => Db, 'design.id' => Name}};
+
+handler_info('POST', [Db, <<"_design">>, Name], _) ->
+    {'db.design.doc.write', #{'db.name' => Db, 'design.id' => Name}};
+
+handler_info('PUT', [Db, <<"_design">>, Name], _) ->
+    {'db.design.doc.write', #{'db.name' => Db, 'design.id' => Name}};
+
+handler_info('COPY', [Db, <<"_design">>, Name], Req) ->
+    {'db.design.doc.write', #{
+        'db.name' => Db,
+        'design.id' => get_copy_destination(Req),
+        'copy.source.doc.id' => <<"_design/", Name/binary>>
+    }};
+
+handler_info('DELETE', [Db, <<"_design">>, Name], _) ->
+    {'db.design.doc.delete', #{'db.name' => Db, 'design.id' => Name}};
+
+handler_info('GET', [Db, <<"_design">>, Name, <<"_info">>], _) ->
+    {'db.design.info.read', #{'db.name' => Db, 'design.id' => Name}};
+
+handler_info(M, [Db, <<"_design">>, Name, <<"_list">>, List, View], _)
+        when M == 'GET'; M == 'POST', M == 'OPTIONS' ->
+    {'db.design.list.read', #{
+        'db.name' => Db,
+        'design.id' => Name,
+        'design.list.name' => List,
+        'design.view.name' => View
+    }};
+
+handler_info(M, [Db, <<"_design">>, Name, <<"_list">>, List, Design, View], _)
+        when M == 'GET'; M == 'POST', M == 'OPTIONS' ->
+    {'db.design.list.read', #{
+        'db.name' => Db,
+        'design.id' => Name,
+        'design.list.name' => List,
+        'design.view.source.id' => Design,
+        'design.view.name' => View
+    }};
+
+handler_info(_, [Db, <<"_design">>, Name, <<"_rewrite">> | Path], _) ->
+    {'db.design.rewrite.execute', #{
+        'db.name' => Db,
+        'design.id' => Name,
+        'rewrite.path' => filename:join(Path)
+    }};
+
+handler_info(_, [Db, <<"_design">>, Name, <<"_show">>, Show, DocId], _) ->
+    {'db.design.show.execute', #{
+        'db.name' => Db,
+        'design.id' => Name,
+        'design.show.name' => Show,
+        'design.show.doc.id' => DocId
+    }};
+
+handler_info(_, [Db, <<"_design">>, Name, <<"_update">>, Update | Rest], _) ->
+    BaseTags = #{
+        'db.name' => Db,
+        'design.id' => Name,
+        'design.update.name' => Update
+    },
+    Tags = case Rest of
+        [] ->
+            BaseTags;
+        _ ->
+            DocId = filename:join(Rest),
+            maps:put('design.update.doc.id', DocId, BaseTags)
+    end,
+    {'db.design.update.execute', Tags};
+
+handler_info('POST', [Db, <<"_design">>, Name, <<"_view">>, View, <<"queries">>], _) ->
+    {'db.design.view.multi.read', #{
+        'db.name' => Db,
+        'design.id' => Name,
+        'design.view.name' => View
+    }};
+
+handler_info(M, [Db, <<"_design">>, Name, <<"_view">>, View], _)
+        when M == 'GET'; M == 'POST' ->
+    {'db.design.view.read', #{
+        'db.name' => Db,
+        'design.id' => Name,
+        'design.view.name' => View
+    }};
+
+handler_info(_, [_Db, <<"_design">>, _Name, <<"_", _/binary>> | _], _) ->
+    % Bail here so that we don't treat a plugin
+    % design handler in place of a design attachment
+    no_match;
+
+handler_info('GET', [Db, <<"_design">>, Name | Path], _) ->
+    {'db.design.doc.attachment.read', #{
+        'db.name' => Db,
+        'design.id' => Name,
+        'attachment.name' => filename:join(Path)
+    }};
+
+handler_info('PUT', [Db, <<"_design">>, Name | Path], _) ->
+    {'db.design.doc.attachment.write', #{
+        'db.name' => Db,
+        'design.id' => Name,
+        'attachment.name' => filename:join(Path)
+    }};
+
+handler_info('DELETE', [Db, <<"_design">>, Name | Path], _) ->
+    {'db.design.doc.attachment.delete', #{
+        'db.name' => Db,
+        'design.id' => Name,
+        'attachment.name' => filename:join(Path)
+    }};
+
+handler_info(_, [Db, <<"_design/", Name/binary>> | Rest], Req) ->
+    % Recurse if someone sent us `_design%2Fname`
+    chttpd_handlers:handler_info(Req#httpd{
+        path_parts = [Db, <<"_design">>, Name | Rest]
+    });
+
+handler_info(M, [Db, <<"_design_docs">>], _) when M == 'GET'; M == 'POST' ->
+    {'db.design_docs.read', #{'db.name' => Db}};
+
+handler_info('POST', [Db, <<"_design_docs">>, <<"queries">>], _) ->
+    {'db.design_docs.read', #{'db.name' => Db, multi => true}};
+
+handler_info('POST', [Db, <<"_ensure_full_commit">>], _) ->
+    {'db.ensure_full_commit.execute', #{'db.name' => Db}};
+
+handler_info('GET', [Db, <<"_local">>, Name], _) ->
+    {'db.local.doc.read', #{'db.name' => Db, 'local.id' => Name}};
+
+handler_info('POST', [Db, <<"_local">>, Name], _) ->
+    {'db.local.doc.write', #{'db.name' => Db, 'local.id' => Name}};
+
+handler_info('PUT', [Db, <<"_local">>, Name], _) ->
+    {'db.local.doc.write', #{'db.name' => Db, 'local.id' => Name}};
+
+handler_info('COPY', [Db, <<"_local">>, Name], Req) ->
+    {'db.local.doc.write', #{
+        'db.name' => Db,
+        'local.id' => get_copy_destination(Req),
+        'copy.source.doc.id' => <<"_local/", Name/binary>>
+    }};
+
+handler_info('DELETE', [Db, <<"_local">>, Name], _) ->
+    {'db.local.doc.delete', #{'db.name' => Db, 'local.id' => Name}};
+
+handler_info(_, [Db, <<"_local">>, Name | _Path], _) ->
+    {'db.local.doc.invalid_attachment_req', #{
+        'db.name' => Db,
+        'local.id' => Name
+    }};
+
+handler_info(M, [Db, <<"_local_docs">>], _) when M == 'GET'; M == 'POST' ->
+    {'db.local_docs.read', #{'db.name' => Db}};
+
+handler_info('POST', [Db, <<"_local_docs">>, <<"queries">>], _) ->
+    {'db.local_docs.read', #{'db.name' => Db, multi => true}};
+
+handler_info('POST', [Db, <<"_missing_revs">>], _) ->
+    {'db.docs.missing_revs.execute', #{'db.name' => Db}};
+
+handler_info('GET', [Db, <<"_partition">>, Partition], _) ->
+    {'db.partition.info.read', #{'db.name' => Db, partition => Partition}};
+
+handler_info(_, [Db, <<"_partition">>, Partition | Rest], Req) ->
+    NewPath = case Rest of
+        [<<"_all_docs">> | _] ->
+            [Db | Rest];
+        [<<"_index">> | _] ->
+            [Db | Rest];
+        [<<"_find">> | _] ->
+            [Db | Rest];
+        [<<"_explain">> | _] ->
+            [Db | Rest];
+        [<<"_design">>, _Name, <<"_", _/binary>> | _] ->
+            [Db | Rest];
+        _ ->
+            no_match
+    end,
+    if NewPath == no_match -> no_match; true ->
+        {OpName, Tags} = chttpd_handlers:handler_info(Req#httpd{
+            path_parts = NewPath
+        }),
+        NewOpName = case atom_to_list(OpName) of
+            "db." ++ Name -> list_to_atom("db.partition." ++ Name);
+            Else -> list_to_atom(Else ++ ".partition")
+        end,
+        {NewOpName, maps:put(partition, Partition, Tags)}
+    end;
+
+handler_info('POST', [Db, <<"_purge">>], _) ->
+    {'db.docs.purge', #{'db.name' => Db}};
+
+handler_info('GET', [Db, <<"_purged_infos_limit">>], _) ->
+    {'db.purged_infos_limit.read', #{'db.name' => Db}};
+
+handler_info('PUT', [Db, <<"_purged_infos_limit">>], _) ->
+    {'db.purged_infos_limit.write', #{'db.name' => Db}};
+
+handler_info('POST', [Db, <<"_revs_diff">>], _) ->
+    {'db.docs.revs_diff.execute', #{'db.name' => Db}};
+
+handler_info('GET', [Db, <<"_revs_limit">>], _) ->
+    {'db.revs_limit.read', #{'db.name' => Db}};
+
+handler_info('PUT', [Db, <<"_revs_limit">>], _) ->
+    {'db.revs_limit.write', #{'db.name' => Db}};
+
+handler_info('GET', [Db, <<"_security">>], _) ->
+    {'db.security.read', #{'db.name' => Db}};
+
+handler_info('PUT', [Db, <<"_security">>], _) ->
+    {'db.security.write', #{'db.name' => Db}};
+
+handler_info(_, [Db, <<"_view_cleanup">>], _) ->
+    {'views.cleanup.execute', #{'db.name' => Db}};
+
+handler_info(_, [_Db, <<"_", _/binary>> | _], _) ->
+    % Bail here for other possible db_handleres
+    no_match;
+
+handler_info('GET', [Db, DocId], _) ->
+    {'db.doc.read', #{'db.name' => Db, 'doc.id' => DocId}};
+
+handler_info('POST', [Db, DocId], _) ->
+    {'db.doc.write', #{'db.name' => Db, 'design.id' => DocId}};
+
+handler_info('PUT', [Db, DocId], _) ->
+    {'db.doc.write', #{'db.name' => Db, 'design.id' => DocId}};
+
+handler_info('COPY', [Db, DocId], Req) ->
+    {'db.doc.write', #{
+        'db.name' => Db,
+        'doc.id' => get_copy_destination(Req),
+        'copy.source.doc.id' => DocId
+    }};
+
+handler_info('DELETE', [Db, DocId], _) ->
+    {'db.doc.delete', #{'db.name' => Db, 'doc.id' => DocId}};
+
+handler_info('GET', [Db, DocId | Path], _) ->
+    {'db.doc.attachment.read', #{
+        'db.name' => Db,
+        'doc.id' => DocId,
+        'attachment.name' => filename:join(Path)
+    }};
+
+handler_info('PUT', [Db, DocId | Path], _) ->
+    {'db.doc.attachment.write', #{
+        'db.name' => Db,
+        'doc.id' => DocId,
+        'attachment.name' => filename:join(Path)
+    }};
+
+handler_info('DELETE', [Db, DocId | Path], _) ->
+    {'db.doc.attachment.delete', #{
+        'db.name' => Db,
+        'doc.id' => DocId,
+        'attachment.name' => filename:join(Path)
+    }};
+
+handler_info(_, _, _) ->
+    no_match.
+
+
+get_copy_destination(Req) ->
+    try
+        {DocIdStr, _} = couch_httpd_db:parse_copy_destination_header(Req),
+        list_to_binary(mochiweb_util:unquote(DocIdStr))
+    catch _:_ ->
+        unknown
+    end.
+
diff --git a/src/global_changes/src/global_changes_httpd_handlers.erl b/src/global_changes/src/global_changes_httpd_handlers.erl
index b21a64b..94a50ab 100644
--- a/src/global_changes/src/global_changes_httpd_handlers.erl
+++ b/src/global_changes/src/global_changes_httpd_handlers.erl
@@ -12,7 +12,7 @@
 
 -module(global_changes_httpd_handlers).
 
--export([url_handler/1, db_handler/1, design_handler/1]).
+-export([url_handler/1, db_handler/1, design_handler/1, handler_info/3]).
 
 url_handler(<<"_db_updates">>) -> fun global_changes_httpd:handle_global_changes_req/1;
 url_handler(_) -> no_match.
@@ -20,3 +20,9 @@ url_handler(_) -> no_match.
 db_handler(_) -> no_match.
 
 design_handler(_) -> no_match.
+
+handler_info('GET', [<<"_db_updates">>], _) ->
+    {'db_updates.read', #{}};
+
+handler_info(_, _, _) ->
+    no_match.
\ No newline at end of file
diff --git a/src/mango/src/mango_httpd_handlers.erl b/src/mango/src/mango_httpd_handlers.erl
index 80e5e27..c1ddd6c 100644
--- a/src/mango/src/mango_httpd_handlers.erl
+++ b/src/mango/src/mango_httpd_handlers.erl
@@ -12,7 +12,7 @@
 
 -module(mango_httpd_handlers).
 
--export([url_handler/1, db_handler/1, design_handler/1]).
+-export([url_handler/1, db_handler/1, design_handler/1, handler_info/3]).
 
 url_handler(_) -> no_match.
 
@@ -22,3 +22,32 @@ db_handler(<<"_find">>)         -> fun mango_httpd:handle_req/2;
 db_handler(_) -> no_match.
 
 design_handler(_) -> no_match.
+
+handler_info('GET', [Db, <<"_index">>], _) ->
+    {'db.mango.index.read', #{'db.name' => Db}};
+
+handler_info('POST', [Db, <<"_index">>], _) ->
+    {'db.mango.index.create', #{'db.name' => Db}};
+
+handler_info('POST', [Db, <<"_index">>, <<"_bulk_delete">>], _) ->
+    {'db.mango.index.delete', #{'db.name' => Db, multi => true}};
+
+handler_info('DELETE', [Db, <<"_index">>, <<"_design">>, Name, Type, Idx], _) ->
+    {'db.mango.index.delete', #{
+        'db.name' => Db,
+        'design.id' => Name,
+        'index.type' => Type,
+        'index.name' => Idx
+    }};
+
+handler_info(M, [Db, <<"_index">>, <<"_design/", N/binary>>, T, I], R) ->
+    handler_info(M, [Db, <<"_index">>, <<"_design">>, N, T, I], R);
+
+handler_info('POST', [Db, <<"_explain">>], _) ->
+    {'db.mango.explain.execute', #{'db.name' => Db}};
+
+handler_info('POST', [Db, <<"_find">>], _) ->
+    {'db.mango.find.execute', #{'db.name' => Db}};
+
+handler_info(_, _, _) ->
+    no_match.
\ No newline at end of file
diff --git a/src/mem3/src/mem3_httpd_handlers.erl b/src/mem3/src/mem3_httpd_handlers.erl
index 7dd6ab0..eeec1ed 100644
--- a/src/mem3/src/mem3_httpd_handlers.erl
+++ b/src/mem3/src/mem3_httpd_handlers.erl
@@ -12,7 +12,7 @@
 
 -module(mem3_httpd_handlers).
 
--export([url_handler/1, db_handler/1, design_handler/1]).
+-export([url_handler/1, db_handler/1, design_handler/1, handler_info/3]).
 
 url_handler(<<"_membership">>) -> fun mem3_httpd:handle_membership_req/1;
 url_handler(<<"_reshard">>) -> fun mem3_reshard_httpd:handle_reshard_req/1;
@@ -23,3 +23,39 @@ db_handler(<<"_sync_shards">>)   -> fun mem3_httpd:handle_sync_req/2;
 db_handler(_) -> no_match.
 
 design_handler(_) -> no_match.
+
+handler_info('GET', [<<"_membership">>], _) ->
+    {'cluster.membership.read', #{}};
+
+handler_info('GET', [<<"_reshard">>], _) ->
+    {'reshard.summary.read', #{}};
+
+handler_info('GET', [<<"_reshard">>, <<"state">>], _) ->
+    {'reshard.state.read', #{}};
+
+handler_info('PUT', [<<"_reshard">>, <<"state">>], _) ->
+    {'reshard.state.write', #{}};
+
+handler_info('GET', [<<"_reshard">>, <<"jobs">>], _) ->
+    {'reshard.jobs.read', #{}};
+
+handler_info('POST', [<<"_reshard">>, <<"jobs">>], _) ->
+    {'reshard.jobs.create', #{}};
+
+handler_info('GET', [<<"_reshard">>, <<"jobs">>, JobId], _) ->
+    {'reshard.job.read', #{'job.id' => JobId}};
+
+handler_info('DELETE', [<<"_reshard">>, <<"jobs">>, JobId], _) ->
+    {'reshard.job.delete', #{'job.id' => JobId}};
+
+handler_info('GET', [DbName, <<"_shards">>], _) ->
+    {'db.shards.read', #{'db.name' => DbName}};
+
+handler_info('GET', [DbName, <<"_shards">>, DocId], _) ->
+    {'db.shards.read', #{'db.name' => DbName, 'doc.id' => DocId}};
+
+handler_info('POST', [DbName, <<"_sync_shards">>], _) ->
+    {'db.shards.sync', #{'db.name' => DbName}};
+
+handler_info(_, _, _) ->
+    no_match.
diff --git a/src/setup/src/setup_httpd_handlers.erl b/src/setup/src/setup_httpd_handlers.erl
index 994c217..e26fbc3 100644
--- a/src/setup/src/setup_httpd_handlers.erl
+++ b/src/setup/src/setup_httpd_handlers.erl
@@ -12,7 +12,7 @@
 
 -module(setup_httpd_handlers).
 
--export([url_handler/1, db_handler/1, design_handler/1]).
+-export([url_handler/1, db_handler/1, design_handler/1, handler_info/3]).
 
 url_handler(<<"_cluster_setup">>) -> fun setup_httpd:handle_setup_req/1;
 url_handler(_) -> no_match.
@@ -20,3 +20,13 @@ url_handler(_) -> no_match.
 db_handler(_) -> no_match.
 
 design_handler(_) -> no_match.
+
+
+handler_info('GET', [<<"_cluster_setup">>], _) ->
+    {'cluster_setup.read', #{}};
+
+handler_info('POST', [<<"_cluster_setup">>], _) ->
+    {'cluster_setup.write', #{}};
+
+handler_info(_, _, _) ->
+    no_match.
\ No newline at end of file