You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2021/04/16 21:45:08 UTC

[couchdb] 03/24: Close backend port and clean up url handlers

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

vatamane pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit e05a6bfc03e100e998de89cd99c8c9e0000d7acf
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Tue Apr 13 23:27:10 2021 -0400

    Close backend port and clean up url handlers
    
    Backend (5986) port is closed. Requests to `/_node/_local/_config` and
    a few other `_*` endpoints continues to work. Backend db access now
    returns error code 410 (not_supported). Previously, it was accessing
    couch_server and couch_files to create 3.x style local dbs.
    
    Url handlers are updated to return `not_supported` for features which are not
    coming back, and `not_implemented` for features which haven't been implemented
    yet.
    
    `parse_copy_destination_header/1` is the only function from `couch_httpd_db`
    that's still needed, so it was moved to `chttpd_util` module.
    
    `couch_httpd_db` handled `/_uuid` requests and that handler was moved to
    `chttpd_misc` module.
    
    "Welcome" endpoint (/) was updated to not call `clouseau_rpc:connected/0`.
    
    Request handling in `couch_httpd` was removed, so most of the file is
    now a bunch of utility functions mostly.
---
 src/chttpd/src/chttpd.erl                         |   7 +-
 src/chttpd/src/chttpd_db.erl                      | 146 +--------
 src/chttpd/src/chttpd_httpd_handlers.erl          |  25 +-
 src/chttpd/src/chttpd_misc.erl                    |  37 ++-
 src/chttpd/src/chttpd_node.erl                    |  46 +--
 src/chttpd/src/chttpd_show.erl                    | 150 +---------
 src/chttpd/src/chttpd_util.erl                    |  41 +++
 src/couch/src/couch.app.src                       |  34 ---
 src/couch/src/couch_httpd.erl                     | 347 +---------------------
 src/couch/src/couch_secondary_sup.erl             |  10 +-
 src/couch/test/eunit/chttpd_endpoints_tests.erl   |  18 +-
 src/couch_replicator/src/couch_replicator_ids.erl |   2 +-
 12 files changed, 112 insertions(+), 751 deletions(-)

diff --git a/src/chttpd/src/chttpd.erl b/src/chttpd/src/chttpd.erl
index b124375..8567ada 100644
--- a/src/chttpd/src/chttpd.erl
+++ b/src/chttpd/src/chttpd.erl
@@ -123,6 +123,12 @@ start_link(Name, Options) ->
          end,
     ok = couch_httpd:validate_bind_address(IP),
 
+    % Ensure uuid is set so that concurrent replications
+    % get the same value. This used to in the backend (:5986) httpd
+    % start_link and was moved here for now. Ideally this should be set
+    % in FDB or coordinated across all the nodes
+    couch_server:get_uuid(),
+
     set_auth_handlers(),
 
     Options1 = Options ++ [
@@ -153,7 +159,6 @@ stop() ->
     mochiweb_http:stop(?MODULE).
 
 handle_request(MochiReq0) ->
-    erlang:put(?REWRITE_COUNT, 0),
     MochiReq = couch_httpd_vhost:dispatch_host(MochiReq0),
     handle_request_int(MochiReq).
 
diff --git a/src/chttpd/src/chttpd_db.erl b/src/chttpd/src/chttpd_db.erl
index ac3d3b1..8b99059 100644
--- a/src/chttpd/src/chttpd_db.erl
+++ b/src/chttpd/src/chttpd_db.erl
@@ -23,8 +23,7 @@
     db_req/2, couch_doc_open/4,handle_changes_req/2,
     update_doc_result_to_json/1, update_doc_result_to_json/2,
     handle_design_info_req/3, handle_view_cleanup_req/2,
-    update_doc/4, http_code_from_status/1,
-    handle_partition_req/2]).
+    update_doc/4, http_code_from_status/1]).
 
 -import(chttpd,
     [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
@@ -275,80 +274,6 @@ handle_view_cleanup_req(Req, Db) ->
     ok = fabric2_index:cleanup(Db),
     send_json(Req, 202, {[{ok, true}]}).
 
-
-handle_partition_req(#httpd{path_parts=[_,_]}=_Req, _Db) ->
-    throw({bad_request, invalid_partition_req});
-
-handle_partition_req(#httpd{method='GET', path_parts=[_,_,PartId]}=Req, Db) ->
-    couch_partition:validate_partition(PartId),
-    case couch_db:is_partitioned(Db) of
-        true ->
-            {ok, PartitionInfo} = fabric:get_partition_info(Db, PartId),
-            send_json(Req, {PartitionInfo});
-        false ->
-            throw({bad_request, <<"database is not partitioned">>})
-    end;
-
-handle_partition_req(#httpd{method='POST',
-    path_parts=[_, <<"_partition">>, <<"_", _/binary>>]}, _Db) ->
-    Msg = <<"Partition must not start with an underscore">>,
-    throw({illegal_partition, Msg});
-
-handle_partition_req(#httpd{path_parts = [_, _, _]}=Req, _Db) ->
-    send_method_not_allowed(Req, "GET");
-
-handle_partition_req(#httpd{path_parts=[DbName, _, PartId | Rest]}=Req, Db) ->
-    case couch_db:is_partitioned(Db) of
-        true ->
-            couch_partition:validate_partition(PartId),
-            QS = chttpd:qs(Req),
-            PartIdStr = ?b2l(PartId),
-            QSPartIdStr = couch_util:get_value("partition", QS, PartIdStr),
-            if QSPartIdStr == PartIdStr -> ok; true ->
-                Msg = <<"Conflicting value for `partition` in query string">>,
-                throw({bad_request, Msg})
-            end,
-            NewQS = lists:ukeysort(1, [{"partition", PartIdStr} | QS]),
-            NewReq = Req#httpd{
-                path_parts = [DbName | Rest],
-                qs = NewQS
-            },
-            update_partition_stats(Rest),
-            case Rest of
-                [OP | _] when OP == <<"_all_docs">> orelse ?IS_MANGO(OP) ->
-                    case chttpd_handlers:db_handler(OP, fun db_req/2) of
-                        Handler when is_function(Handler, 2) ->
-                            Handler(NewReq, Db);
-                        _ ->
-                            chttpd:send_error(Req, not_found)
-                    end;
-                [<<"_design">>, _Name, <<"_", _/binary>> | _] ->
-                    handle_design_req(NewReq, Db);
-                _ ->
-                    chttpd:send_error(Req, not_found)
-            end;
-        false ->
-            throw({bad_request, <<"database is not partitioned">>})
-    end;
-
-handle_partition_req(Req, _Db) ->
-    chttpd:send_error(Req, not_found).
-
-update_partition_stats(PathParts) ->
-    case PathParts of
-            [<<"_design">> | _] ->
-                couch_stats:increment_counter([couchdb, httpd, partition_view_requests]);
-            [<<"_all_docs">> | _] ->
-                couch_stats:increment_counter([couchdb, httpd, partition_all_docs_requests]);
-            [<<"_find">> | _] ->
-                couch_stats:increment_counter([couchdb, httpd, partition_find_requests]);
-            [<<"_explain">> | _] ->
-                couch_stats:increment_counter([couchdb, httpd, partition_explain_requests]);
-            _ ->
-                ok % ignore path that do not match
-        end.
-
-
 handle_design_req(#httpd{
         path_parts=[_DbName, _Design, Name, <<"_",_/binary>> = Action | _Rest]
     }=Req, Db) ->
@@ -635,41 +560,6 @@ db_req(#httpd{method='POST', path_parts=[_, <<"_bulk_get">>],
 db_req(#httpd{path_parts=[_, <<"_bulk_get">>]}=Req, _Db) ->
     send_method_not_allowed(Req, "POST");
 
-
-db_req(#httpd{method='POST',path_parts=[_,<<"_purge">>]}=Req, Db) ->
-    couch_stats:increment_counter([couchdb, httpd, purge_requests]),
-    chttpd:validate_ctype(Req, "application/json"),
-    {IdsRevs} = chttpd:json_body_obj(Req),
-    IdsRevs2 = [{Id, couch_doc:parse_revs(Revs)} || {Id, Revs} <- IdsRevs],
-    MaxIds = config:get_integer("purge", "max_document_id_number", 100),
-    case length(IdsRevs2) =< MaxIds of
-        false -> throw({bad_request, "Exceeded maximum number of documents."});
-        true -> ok
-    end,
-    RevsLen = lists:foldl(fun({_Id, Revs}, Acc) ->
-        length(Revs) + Acc
-    end, 0, IdsRevs2),
-    MaxRevs = config:get_integer("purge", "max_revisions_number", 1000),
-    case RevsLen =< MaxRevs of
-        false -> throw({bad_request, "Exceeded maximum number of revisions."});
-        true -> ok
-    end,
-    couch_stats:increment_counter([couchdb, document_purges, total], length(IdsRevs2)),
-    Results2 = case fabric:purge_docs(Db, IdsRevs2, []) of
-        {ok, Results} ->
-            chttpd_stats:incr_writes(length(Results)),
-            Results;
-        {accepted, Results} ->
-            chttpd_stats:incr_writes(length(Results)),
-            Results
-    end,
-    {Code, Json} = purge_results_to_json(IdsRevs2, Results2),
-    send_json(Req, Code, {[{<<"purge_seq">>, null}, {<<"purged">>, {Json}}]});
-
-db_req(#httpd{path_parts=[_,<<"_purge">>]}=Req, _Db) ->
-    send_method_not_allowed(Req, "POST");
-
-
 db_req(#httpd{method='GET',path_parts=[_,OP]}=Req, Db) when ?IS_ALL_DOCS(OP) ->
     case chttpd:qs_json_value(Req, "keys", nil) of
     Keys when is_list(Keys) ->
@@ -778,22 +668,6 @@ db_req(#httpd{method='GET',path_parts=[_,<<"_revs_limit">>]}=Req, Db) ->
 db_req(#httpd{path_parts=[_,<<"_revs_limit">>]}=Req, _Db) ->
     send_method_not_allowed(Req, "PUT,GET");
 
-db_req(#httpd{method='PUT',path_parts=[_,<<"_purged_infos_limit">>]}=Req, Db) ->
-    case chttpd:json_body(Req) of
-        Limit when is_integer(Limit), Limit > 0 ->
-            case fabric:set_purge_infos_limit(Db, Limit, []) of
-                ok ->
-                    send_json(Req, {[{<<"ok">>, true}]});
-                Error ->
-                    throw(Error)
-            end;
-        _->
-            throw({bad_request, "`purge_infos_limit` must be positive integer"})
-    end;
-
-db_req(#httpd{method='GET',path_parts=[_,<<"_purged_infos_limit">>]}=Req, Db) ->
-    send_json(Req, fabric:get_purge_infos_limit(Db));
-
 % Special case to enable using an unencoded slash in the URL of design docs,
 % as slashes in document IDs must otherwise be URL encoded.
 db_req(#httpd{method='GET', mochi_req=MochiReq, path_parts=[_DbName, <<"_design/", _/binary>> | _]}=Req, _Db) ->
@@ -1444,24 +1318,6 @@ update_doc_result_to_json(DocId, Error) ->
     {_Code, ErrorStr, Reason} = chttpd:error_info(Error),
     {[{id, DocId}, {error, ErrorStr}, {reason, Reason}]}.
 
-purge_results_to_json([], []) ->
-    {201, []};
-purge_results_to_json([{DocId, _Revs} | RIn], [{ok, PRevs} | ROut]) ->
-    {Code, Results} = purge_results_to_json(RIn, ROut),
-    couch_stats:increment_counter([couchdb, document_purges, success]),
-    {Code, [{DocId, couch_doc:revs_to_strs(PRevs)} | Results]};
-purge_results_to_json([{DocId, _Revs} | RIn], [{accepted, PRevs} | ROut]) ->
-    {Code, Results} = purge_results_to_json(RIn, ROut),
-    couch_stats:increment_counter([couchdb, document_purges, success]),
-    NewResults = [{DocId, couch_doc:revs_to_strs(PRevs)} | Results],
-    {erlang:max(Code, 202), NewResults};
-purge_results_to_json([{DocId, _Revs} | RIn], [Error | ROut]) ->
-    {Code, Results} = purge_results_to_json(RIn, ROut),
-    {NewCode, ErrorStr, Reason} = chttpd:error_info(Error),
-    couch_stats:increment_counter([couchdb, document_purges, failure]),
-    NewResults = [{DocId, {[{error, ErrorStr}, {reason, Reason}]}} | Results],
-    {erlang:max(NewCode, Code), NewResults}.
-
 send_updated_doc(Req, Db, DocId, Json) ->
     send_updated_doc(Req, Db, DocId, Json, []).
 
diff --git a/src/chttpd/src/chttpd_httpd_handlers.erl b/src/chttpd/src/chttpd_httpd_handlers.erl
index d501159..e5374b1 100644
--- a/src/chttpd/src/chttpd_httpd_handlers.erl
+++ b/src/chttpd/src/chttpd_httpd_handlers.erl
@@ -15,9 +15,11 @@
 -export([url_handler/1, db_handler/1, design_handler/1, handler_info/3]).
 
 -export([
-    not_supported/2,
     not_supported/3,
-    not_implemented/2
+    not_supported/2,
+    not_supported/1,
+    not_implemented/2,
+    not_implemented/1
 ]).
 
 
@@ -38,16 +40,22 @@ url_handler(<<"_replicate">>)      -> fun chttpd_misc:handle_replicate_req/1;
 url_handler(<<"_uuids">>)          -> fun chttpd_misc:handle_uuids_req/1;
 url_handler(<<"_session">>)        -> fun chttpd_auth:handle_session_req/1;
 url_handler(<<"_up">>)             -> fun chttpd_misc:handle_up_req/1;
+url_handler(<<"_membership">>)     -> fun ?MODULE:not_supported/1;
+url_handler(<<"_reshard">>)        -> fun ?MODULE:not_supported/1;
+url_handler(<<"_db_updates">>)     -> fun ?MODULE:not_implemented/1;
+url_handler(<<"_cluster_setup">>)  -> fun ?MODULE:not_implemented/1;
 url_handler(_) -> no_match.
 
 db_handler(<<"_view_cleanup">>) -> fun chttpd_db:handle_view_cleanup_req/2;
 db_handler(<<"_compact">>)      -> fun chttpd_db:handle_compact_req/2;
 db_handler(<<"_design">>)       -> fun chttpd_db:handle_design_req/2;
-db_handler(<<"_partition">>)    -> fun chttpd_db:handle_partition_req/2;
+db_handler(<<"_partition">>)    -> fun ?MODULE:not_implemented/2;
 db_handler(<<"_temp_view">>)    -> fun ?MODULE:not_supported/2;
 db_handler(<<"_changes">>)      -> fun chttpd_db:handle_changes_req/2;
 db_handler(<<"_purge">>)        -> fun ?MODULE:not_implemented/2;
 db_handler(<<"_purged_infos_limit">>) -> fun ?MODULE:not_implemented/2;
+db_handler(<<"_shards">>)       -> fun ?MODULE:not_supported/2;
+db_handler(<<"_sync_shards">>)  -> fun ?MODULE:not_supported/2;
 db_handler(_) -> no_match.
 
 design_handler(<<"_view">>)    -> fun chttpd_view:handle_view_req/3;
@@ -186,7 +194,6 @@ handler_info(Method, [<<"_", _/binary>> = Part| Rest], Req) ->
     % on for known system databases.
     DbName = case Part of
         <<"_dbs">> -> '_dbs';
-        <<"_global_changes">> -> '_global_changes';
         <<"_metadata">> -> '_metadata';
         <<"_nodes">> -> '_nodes';
         <<"_replicator">> -> '_replicator';
@@ -497,7 +504,7 @@ handler_info(_, _, _) ->
 
 get_copy_destination(Req) ->
     try
-        {DocIdStr, _} = couch_httpd_db:parse_copy_destination_header(Req),
+        {DocIdStr, _} = chttpd_util:parse_copy_destination_header(Req),
         list_to_binary(mochiweb_util:unquote(DocIdStr))
     catch _:_ ->
         unknown
@@ -509,10 +516,18 @@ not_supported(#httpd{} = Req, Db, _DDoc) ->
 
 
 not_supported(#httpd{} = Req, _Db) ->
+    not_supported(Req).
+
+
+not_supported(#httpd{} = Req) ->
     Msg = <<"resource is not supported in CouchDB >= 4.x">>,
     chttpd:send_error(Req, 410, gone, Msg).
 
 
 not_implemented(#httpd{} = Req, _Db) ->
+    not_implemented(Req).
+
+
+not_implemented(#httpd{} = Req) ->
     Msg = <<"resource is not implemented">>,
     chttpd:send_error(Req, 501, not_implemented, Msg).
diff --git a/src/chttpd/src/chttpd_misc.erl b/src/chttpd/src/chttpd_misc.erl
index 5cfd0f7..5d9706a 100644
--- a/src/chttpd/src/chttpd_misc.erl
+++ b/src/chttpd/src/chttpd_misc.erl
@@ -33,7 +33,7 @@
 -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).
@@ -61,12 +61,7 @@ handle_welcome_req(Req, _) ->
     send_method_not_allowed(Req, "GET,HEAD").
 
 get_features() ->
-    case clouseau_rpc:connected() of
-        true ->
-            [search | config:features()];
-        false ->
-            config:features()
-    end.
+    config:features().
 
 handle_favicon_req(Req) ->
     handle_favicon_req(Req, get_docroot()).
@@ -334,9 +329,33 @@ handle_reload_query_servers_req(#httpd{method='POST'}=Req) ->
 handle_reload_query_servers_req(Req) ->
     send_method_not_allowed(Req, "POST").
 
+handle_uuids_req(#httpd{method='GET'}=Req) ->
+    Max = list_to_integer(config:get("uuids","max_count","1000")),
+    Count = try list_to_integer(couch_httpd:qs_value(Req, "count", "1")) of
+        N when N > Max ->
+            throw({bad_request, <<"count parameter too large">>});
+        N when N < 0 ->
+            throw({bad_request, <<"count must be a positive integer">>});
+        N -> N
+    catch
+        error:badarg ->
+            throw({bad_request, <<"count must be a positive integer">>})
+    end,
+    UUIDs = [couch_uuids:new() || _ <- lists:seq(1, Count)],
+    Etag = couch_httpd:make_etag(UUIDs),
+    couch_httpd:etag_respond(Req, Etag, fun() ->
+        CacheBustingHeaders = [
+            {"Date", couch_util:rfc1123_date()},
+            {"Cache-Control", "no-cache"},
+            % Past date, ON PURPOSE!
+            {"Expires", "Mon, 01 Jan 1990 00:00:00 GMT"},
+            {"Pragma", "no-cache"},
+            {"ETag", Etag}
+        ],
+        send_json(Req, 200, CacheBustingHeaders, {[{<<"uuids">>, UUIDs}]})
+    end);
 handle_uuids_req(Req) ->
-    couch_httpd_misc_handlers:handle_uuids_req(Req).
-
+    send_method_not_allowed(Req, "GET").
 
 handle_up_req(#httpd{method='GET'} = Req) ->
     case config:get("couchdb", "maintenance_mode") of
diff --git a/src/chttpd/src/chttpd_node.erl b/src/chttpd/src/chttpd_node.erl
index b6c4fac..e36380a 100644
--- a/src/chttpd/src/chttpd_node.erl
+++ b/src/chttpd/src/chttpd_node.erl
@@ -138,54 +138,14 @@ handle_node_req(#httpd{method='POST', path_parts=[_, Node, <<"_restart">>]}=Req)
     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=[_, Node | PathParts],
-                       mochi_req=MochiReq0}) ->
-    % strip /_node/{node} from Req0 before descending further
-    RawUri = MochiReq0:get(raw_path),
-    {_, Query, Fragment} = mochiweb_util:urlsplit_path(RawUri),
-    NewPath0 = "/" ++ lists:join("/", [couch_util:url_encode(P) || P <- PathParts]),
-    NewRawPath = mochiweb_util:urlunsplit_path({NewPath0, Query, Fragment}),
-    MaxSize =  config:get_integer("httpd", "max_http_request_size", 4294967296),
-    NewOpts = [{body, MochiReq0:recv_body(MaxSize)} | MochiReq0:get(opts)],
-    Ref = erlang:make_ref(),
-    MochiReq = mochiweb_request:new({remote, self(), Ref},
-                               NewOpts,
-                               MochiReq0:get(method),
-                               NewRawPath,
-                               MochiReq0:get(version),
-                               MochiReq0:get(headers)),
-    call_node(Node, couch_httpd, handle_request, [MochiReq]),
-    recv_loop(Ref, MochiReq0);
+handle_node_req(#httpd{path_parts=[_, _Node | _PathParts]}=Req) ->
+    % Local (backend) dbs are not support any more
+    chttpd_httpd_handlers:not_supported(Req);
 handle_node_req(#httpd{path_parts=[_]}=Req) ->
     chttpd:send_error(Req, {bad_request, <<"Incomplete path to _node request">>});
 handle_node_req(Req) ->
     chttpd:send_error(Req, not_found).
 
-recv_loop(Ref, ReqResp) ->
-    receive
-        {Ref, Code, Headers, _Args, start_response} ->
-            recv_loop(Ref, ReqResp:start({Code, Headers}));
-        {Ref, Code, Headers, Len, start_response_length} ->
-            recv_loop(Ref, ReqResp:start_response_length({Code, Headers, Len}));
-        {Ref, Code, Headers, chunked, respond} ->
-            Resp = ReqResp:respond({Code, Headers, chunked}),
-            recv_loop(Ref, Resp);
-        {Ref, Code, Headers, Args, respond} ->
-            Resp = ReqResp:respond({Code, Headers, Args}),
-            {ok, Resp};
-        {Ref, send, Data} ->
-            ReqResp:send(Data),
-            {ok, ReqResp};
-        {Ref, chunk, <<>>} ->
-            ReqResp:write_chunk(<<>>),
-            {ok, ReqResp};
-        {Ref, chunk, Data} ->
-            ReqResp:write_chunk(Data),
-            recv_loop(Ref, ReqResp);
-        _Else ->
-            recv_loop(Ref, ReqResp)
-    end.
-
 call_node(Node0, Mod, Fun, Args) when is_binary(Node0) ->
     Node1 = try
                 list_to_existing_atom(?b2l(Node0))
diff --git a/src/chttpd/src/chttpd_show.erl b/src/chttpd/src/chttpd_show.erl
index 8a15bdc..295d753 100644
--- a/src/chttpd/src/chttpd_show.erl
+++ b/src/chttpd/src/chttpd_show.erl
@@ -12,15 +12,11 @@
 
 -module(chttpd_show).
 
--export([handle_doc_show_req/3, handle_doc_update_req/3, handle_view_list_req/3]).
+-export([handle_doc_update_req/3]).
 
 -include_lib("couch/include/couch_db.hrl").
 -include_lib("couch_mrview/include/couch_mrview.hrl").
 
-% /db/_design/foo/_show/bar/docid
-% show converts a json doc to a response of any content-type.
-% it looks up the doc an then passes it to the query server.
-% then it sends the response from the query server to the http client.
 
 maybe_open_doc(Db, DocId, Options) ->
     case fabric:open_doc(Db, DocId, Options) of
@@ -31,70 +27,6 @@ maybe_open_doc(Db, DocId, Options) ->
         nil
     end.
 
-handle_doc_show_req(#httpd{
-        path_parts=[_, _, _, _, ShowName, DocId]
-    }=Req, Db, DDoc) ->
-
-    % open the doc
-    Options = [conflicts, {user_ctx, Req#httpd.user_ctx}],
-    Doc = maybe_open_doc(Db, DocId, Options),
-
-    % we don't handle revs here b/c they are an internal api
-    % returns 404 if there is no doc with DocId
-    handle_doc_show(Req, Db, DDoc, ShowName, Doc, DocId);
-
-handle_doc_show_req(#httpd{
-        path_parts=[_, _, _, _, ShowName, DocId|Rest]
-    }=Req, Db, DDoc) ->
-
-    DocParts = [DocId|Rest],
-    DocId1 = ?l2b(string:join([?b2l(P)|| P <- DocParts], "/")),
-
-    % open the doc
-    Options = [conflicts, {user_ctx, Req#httpd.user_ctx}],
-    Doc = maybe_open_doc(Db, DocId1, Options),
-
-    % we don't handle revs here b/c they are an internal api
-    % pass 404 docs to the show function
-    handle_doc_show(Req, Db, DDoc, ShowName, Doc, DocId1);
-
-handle_doc_show_req(#httpd{
-        path_parts=[_, _, _, _, ShowName]
-    }=Req, Db, DDoc) ->
-    % with no docid the doc is nil
-    handle_doc_show(Req, Db, DDoc, ShowName, nil);
-
-handle_doc_show_req(Req, _Db, _DDoc) ->
-    chttpd:send_error(Req, 404, <<"show_error">>, <<"Invalid path.">>).
-
-handle_doc_show(Req, Db, DDoc, ShowName, Doc) ->
-    handle_doc_show(Req, Db, DDoc, ShowName, Doc, null).
-
-handle_doc_show(Req, Db, DDoc, ShowName, Doc, DocId) ->
-    %% Will throw an exception if the _show handler is missing
-    couch_util:get_nested_json_value(DDoc#doc.body, [<<"shows">>, ShowName]),
-    % get responder for ddoc/showname
-    CurrentEtag = show_etag(Req, Doc, DDoc, []),
-    chttpd:etag_respond(Req, CurrentEtag, fun() ->
-        JsonReq = chttpd_external:json_req_obj(Req, Db, DocId),
-        JsonDoc = couch_query_servers:json_doc(Doc),
-        [<<"resp">>, ExternalResp] =
-            couch_query_servers:ddoc_prompt(DDoc, [<<"shows">>, ShowName],
-                [JsonDoc, JsonReq]),
-        JsonResp = apply_etag(ExternalResp, CurrentEtag),
-        chttpd_external:send_external_response(Req, JsonResp)
-    end).
-
-
-show_etag(#httpd{user_ctx=UserCtx}=Req, Doc, DDoc, More) ->
-    Accept = chttpd:header_value(Req, "Accept"),
-    DocPart = case Doc of
-        nil -> nil;
-        Doc -> chttpd:doc_etag(Doc)
-    end,
-    couch_httpd:make_etag({couch_httpd:doc_etag(DDoc), DocPart, Accept,
-        UserCtx#user_ctx.roles, More}).
-
 % /db/_design/foo/update/bar/docid
 % updates a doc based on a request
 % handle_doc_update_req(#httpd{method = 'GET'}=Req, _Db, _DDoc) ->
@@ -154,86 +86,6 @@ send_doc_update_response(Req, Db, DDoc, UpdateName, Doc, DocId) ->
     % todo set location field
     chttpd_external:send_external_response(Req, JsonResp).
 
-
-% view-list request with view and list from same design doc.
-handle_view_list_req(#httpd{method=Method,
-        path_parts=[_, _, DesignName, _, ListName, ViewName]}=Req, Db, DDoc)
-        when Method =:= 'GET' orelse Method =:= 'OPTIONS' ->
-    Keys = chttpd:qs_json_value(Req, "keys", undefined),
-    handle_view_list(Req, Db, DDoc, ListName, {DesignName, ViewName}, Keys);
-
-% view-list request with view and list from different design docs.
-handle_view_list_req(#httpd{method=Method,
-        path_parts=[_, _, _, _, ListName, DesignName, ViewName]}=Req, Db, DDoc)
-        when Method =:= 'GET' orelse Method =:= 'OPTIONS' ->
-    Keys = chttpd:qs_json_value(Req, "keys", undefined),
-    handle_view_list(Req, Db, DDoc, ListName, {DesignName, ViewName}, Keys);
-
-handle_view_list_req(#httpd{method=Method}=Req, _Db, _DDoc)
-        when Method =:= 'GET' orelse Method =:= 'OPTIONS' ->
-    chttpd:send_error(Req, 404, <<"list_error">>, <<"Invalid path.">>);
-
-handle_view_list_req(#httpd{method='POST',
-        path_parts=[_, _, DesignName, _, ListName, ViewName]}=Req, Db, DDoc) ->
-    chttpd:validate_ctype(Req, "application/json"),
-    ReqBody = chttpd:body(Req),
-    {Props2} = ?JSON_DECODE(ReqBody),
-    Keys = proplists:get_value(<<"keys">>, Props2, undefined),
-    handle_view_list(Req#httpd{req_body=ReqBody}, Db, DDoc, ListName,
-        {DesignName, ViewName}, Keys);
-
-handle_view_list_req(#httpd{method='POST',
-        path_parts=[_, _, _, _, ListName, DesignName, ViewName]}=Req, Db, DDoc) ->
-    chttpd:validate_ctype(Req, "application/json"),
-    ReqBody = chttpd:body(Req),
-    {Props2} = ?JSON_DECODE(ReqBody),
-    Keys = proplists:get_value(<<"keys">>, Props2, undefined),
-    handle_view_list(Req#httpd{req_body=ReqBody}, Db, DDoc, ListName,
-        {DesignName, ViewName}, Keys);
-
-handle_view_list_req(#httpd{method='POST'}=Req, _Db, _DDoc) ->
-    chttpd:send_error(Req, 404, <<"list_error">>, <<"Invalid path.">>);
-
-handle_view_list_req(Req, _Db, _DDoc) ->
-    chttpd:send_method_not_allowed(Req, "GET,POST,HEAD").
-
-handle_view_list(Req, Db, DDoc, LName, {ViewDesignName, ViewName}, Keys) ->
-    %% Will throw an exception if the _list handler is missing
-    couch_util:get_nested_json_value(DDoc#doc.body, [<<"lists">>, LName]),
-    DbName = couch_db:name(Db),
-    {ok, VDoc} = ddoc_cache:open(DbName, <<"_design/", ViewDesignName/binary>>),
-    CB = fun list_cb/2,
-    QueryArgs = couch_mrview_http:parse_body_and_query(Req, Keys),
-    Options = [{user_ctx, Req#httpd.user_ctx}],
-    couch_query_servers:with_ddoc_proc(DDoc, fun(QServer) ->
-        Acc = #lacc{
-            lname = LName,
-            req = Req,
-            qserver = QServer,
-            db = Db
-        },
-        case ViewName of
-            <<"_all_docs">> ->
-                fabric:all_docs(Db, Options, CB, Acc, QueryArgs);
-            _ ->
-                fabric:query_view(Db, Options, VDoc, ViewName,
-                    CB, Acc, QueryArgs)
-        end
-    end).
-
-
-list_cb({row, Row} = Msg, Acc) ->
-    case lists:keymember(doc, 1, Row) of
-        true -> chttpd_stats:incr_reads();
-        false -> ok
-    end,
-    chttpd_stats:incr_rows(),
-    couch_mrview_show:list_cb(Msg, Acc);
-
-list_cb(Msg, Acc) ->
-    couch_mrview_show:list_cb(Msg, Acc).
-
-
 % Maybe this is in the proplists API
 % todo move to couch_util
 json_apply_field(H, {L}) ->
diff --git a/src/chttpd/src/chttpd_util.erl b/src/chttpd/src/chttpd_util.erl
new file mode 100644
index 0000000..fcaa09d
--- /dev/null
+++ b/src/chttpd/src/chttpd_util.erl
@@ -0,0 +1,41 @@
+% 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_util).
+
+
+-export([
+    parse_copy_destination_header/1
+]).
+
+
+parse_copy_destination_header(Req) ->
+    case couch_httpd:header_value(Req, "Destination") of
+    undefined ->
+        throw({bad_request, "Destination header is mandatory for COPY."});
+    Destination ->
+        case re:run(Destination, "^https?://", [{capture, none}]) of
+        match ->
+            throw({bad_request, "Destination URL must be relative."});
+        nomatch ->
+            % see if ?rev=revid got appended to the Destination header
+            case re:run(Destination, "\\?", [{capture, none}]) of
+            nomatch ->
+                {list_to_binary(Destination), {0, []}};
+            match ->
+                [DocId, RevQs] = re:split(Destination, "\\?", [{return, list}]),
+                [_RevQueryKey, Rev] = re:split(RevQs, "=", [{return, list}]),
+                {Pos, RevId} = couch_doc:parse_rev(Rev),
+                {list_to_binary(DocId), {Pos, [RevId]}}
+            end
+        end
+    end.
diff --git a/src/couch/src/couch.app.src b/src/couch/src/couch.app.src
index e411b5e..af277c1 100644
--- a/src/couch/src/couch.app.src
+++ b/src/couch/src/couch.app.src
@@ -42,39 +42,5 @@
         couch_stats,
         hyper,
         couch_prometheus
-    ]},
-    {env, [
-        { httpd_global_handlers, [
-            {"/", "{couch_httpd_misc_handlers, handle_welcome_req, <<\"Welcome\">>}"},
-            {"favicon.ico", "{couch_httpd_misc_handlers, handle_favicon_req, \"{{prefix}}/share/www\"}"},
-            {"_utils", "{couch_httpd_misc_handlers, handle_utils_dir_req, \"{{prefix}}/share/www\"}"},
-            {"_all_dbs", "{couch_httpd_misc_handlers, handle_all_dbs_req}"},
-            {"_active_tasks", "{couch_httpd_misc_handlers, handle_task_status_req}"},
-            {"_config", "{couch_httpd_misc_handlers, handle_config_req}"},
-            {"_replicate", "{couch_replicator_httpd, handle_req}"},
-            {"_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}"}
-        ]},
-          { httpd_db_handlers, [
-            {"_all_docs", "{couch_mrview_http, handle_all_docs_req}"},
-            {"_local_docs", "{couch_mrview_http, handle_local_docs_req}"},
-            {"_design_docs", "{couch_mrview_http, handle_design_docs_req}"},
-            {"_changes", "{couch_httpd_db, handle_db_changes_req}"},
-            {"_compact", "{couch_httpd_db, handle_compact_req}"},
-            {"_design", "{couch_httpd_db, handle_design_req}"},
-            {"_temp_view", "{couch_mrview_http, handle_temp_view_req}"},
-            {"_view_cleanup", "{couch_mrview_http, handle_cleanup_req}"}
-        ]},
-        { httpd_design_handlers, [
-            {"_compact", "{couch_mrview_http, handle_compact_req}"},
-            {"_info", "{couch_mrview_http, handle_info_req}"},
-            {"_list", "{couch_mrview_show, handle_view_list_req}"},
-            {"_rewrite", "{couch_httpd_rewrite, handle_rewrite_req}"},
-            {"_show", "{couch_mrview_show, handle_doc_show_req}"},
-            {"_update", "{couch_mrview_show, handle_doc_update_req}"},
-            {"_view", "{couch_mrview_http, handle_view_req}"}
-        ]}
     ]}
 ]}.
diff --git a/src/couch/src/couch_httpd.erl b/src/couch/src/couch_httpd.erl
index d89c749..fd83c25 100644
--- a/src/couch/src/couch_httpd.erl
+++ b/src/couch/src/couch_httpd.erl
@@ -16,8 +16,6 @@
 
 -include_lib("couch/include/couch_db.hrl").
 
--export([start_link/0, start_link/1, stop/0, handle_request/5]).
-
 -export([header_value/2,header_value/3,qs_value/2,qs_value/3,qs/1,qs_json_value/3]).
 -export([path/1,absolute_uri/2,body_length/1]).
 -export([verify_is_server_admin/1,unquote/1,quote/1,recv/2,recv_chunked/4,error_info/1]).
@@ -32,164 +30,17 @@
 -export([send_response/4,send_response_no_cors/4,send_method_not_allowed/2,
     send_error/2,send_error/4, send_redirect/2,send_chunked_error/2]).
 -export([send_json/2,send_json/3,send_json/4,last_chunk/1,parse_multipart_request/3]).
--export([accepted_encodings/1,handle_request_int/5,validate_referer/1,validate_ctype/2]).
+-export([accepted_encodings/1,validate_referer/1,validate_ctype/2]).
 -export([http_1_0_keep_alive/2]).
 -export([validate_host/1]).
 -export([validate_bind_address/1]).
 -export([check_max_request_length/1]).
--export([handle_request/1]).
--export([set_auth_handlers/0]).
 -export([maybe_decompress/2]).
 
 -define(HANDLER_NAME_IN_MODULE_POS, 6).
 -define(MAX_DRAIN_BYTES, 1048576).
 -define(MAX_DRAIN_TIME_MSEC, 1000).
 
-start_link() ->
-    start_link(http).
-start_link(http) ->
-    Port = config:get("httpd", "port", "5984"),
-    start_link(?MODULE, [{port, Port}]);
-start_link(https) ->
-    Port = config:get("ssl", "port", "6984"),
-    {ok, Ciphers} = couch_util:parse_term(config:get("ssl", "ciphers", undefined)),
-    {ok, Versions} = couch_util:parse_term(config:get("ssl", "tls_versions", undefined)),
-    {ok, SecureRenegotiate} = couch_util:parse_term(config:get("ssl", "secure_renegotiate", undefined)),
-    ServerOpts0 =
-        [{cacertfile, config:get("ssl", "cacert_file", undefined)},
-         {keyfile, config:get("ssl", "key_file", undefined)},
-         {certfile, config:get("ssl", "cert_file", undefined)},
-         {password, config:get("ssl", "password", undefined)},
-         {secure_renegotiate, SecureRenegotiate},
-         {versions, Versions},
-         {ciphers, Ciphers}],
-
-    case (couch_util:get_value(keyfile, ServerOpts0) == undefined orelse
-        couch_util:get_value(certfile, ServerOpts0) == undefined) of
-        true ->
-            couch_log:error("SSL enabled but PEM certificates are missing", []),
-            throw({error, missing_certs});
-        false ->
-            ok
-    end,
-
-    ServerOpts = [Opt || {_, V}=Opt <- ServerOpts0, V /= undefined],
-
-    ClientOpts = case config:get("ssl", "verify_ssl_certificates", "false") of
-        "false" ->
-            [];
-        "true" ->
-            FailIfNoPeerCert = case config:get("ssl", "fail_if_no_peer_cert", "false") of
-            "false" -> false;
-            "true" -> true
-            end,
-            [{depth, list_to_integer(config:get("ssl",
-                "ssl_certificate_max_depth", "1"))},
-             {fail_if_no_peer_cert, FailIfNoPeerCert},
-             {verify, verify_peer}] ++
-            case config:get("ssl", "verify_fun", undefined) of
-                undefined -> [];
-                SpecStr ->
-                    [{verify_fun, make_arity_3_fun(SpecStr)}]
-            end
-    end,
-    SslOpts = ServerOpts ++ ClientOpts,
-
-    Options =
-        [{port, Port},
-         {ssl, true},
-         {ssl_opts, SslOpts}],
-    start_link(https, Options).
-start_link(Name, Options) ->
-    BindAddress = case config:get("httpd", "bind_address", "any") of
-                      "any" -> any;
-                      Else -> Else
-                  end,
-    ok = validate_bind_address(BindAddress),
-
-    {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.
-    couch_server:get_uuid(),
-
-    Loop = fun(Req)->
-        case SocketOptions of
-        [] ->
-            ok;
-        _ ->
-            ok = mochiweb_socket:setopts(Req:get(socket), SocketOptions)
-        end,
-        apply(?MODULE, handle_request, [Req | Handlers])
-    end,
-
-    % set mochiweb options
-    FinalOptions = lists:append([Options, ServerOptions, [
-            {loop, Loop},
-            {name, Name},
-            {ip, BindAddress}]]),
-
-    % launch mochiweb
-    case mochiweb_http:start(FinalOptions) of
-        {ok, MochiPid} ->
-            {ok, MochiPid};
-        {error, Reason} ->
-            couch_log:error("Failure to start Mochiweb: ~s~n", [Reason]),
-            throw({error, Reason})
-    end.
-
-
-stop() ->
-    mochiweb_http:stop(couch_httpd),
-    catch mochiweb_http:stop(https).
-
-
-set_auth_handlers() ->
-    AuthenticationSrcs = make_fun_spec_strs(
-        config:get("httpd", "authentication_handlers", "")),
-    AuthHandlers = lists:map(
-        fun(A) -> {auth_handler_name(A), make_arity_1_fun(A)} end, AuthenticationSrcs),
-    AuthenticationFuns = AuthHandlers ++ [
-        fun couch_httpd_auth:party_mode_handler/1 %% must be last
-    ],
-    ok = application:set_env(couch, auth_handlers, AuthenticationFuns).
-
-auth_handler_name(SpecStr) ->
-    lists:nth(?HANDLER_NAME_IN_MODULE_POS, re:split(SpecStr, "[\\W_]", [])).
-
-get_httpd_handlers() ->
-    {ok, HttpdGlobalHandlers} = application:get_env(couch, httpd_global_handlers),
-
-    UrlHandlersList = lists:map(
-        fun({UrlKey, SpecStr}) ->
-            {?l2b(UrlKey), make_arity_1_fun(SpecStr)}
-        end, HttpdGlobalHandlers),
-
-    {ok, HttpdDbHandlers} = application:get_env(couch, httpd_db_handlers),
-
-    DbUrlHandlersList = lists:map(
-        fun({UrlKey, SpecStr}) ->
-            {?l2b(UrlKey), make_arity_2_fun(SpecStr)}
-        end, HttpdDbHandlers),
-
-    {ok, HttpdDesignHandlers} = application:get_env(couch, 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">>}"
@@ -221,175 +72,6 @@ make_arity_3_fun(SpecStr) ->
 make_fun_spec_strs(SpecStr) ->
     re:split(SpecStr, "(?<=})\\s*,\\s*(?={)", [{return, list}]).
 
-handle_request(MochiReq) ->
-    Body = proplists:get_value(body, MochiReq:get(opts)),
-    erlang:put(mochiweb_request_body, Body),
-    apply(?MODULE, handle_request, [MochiReq | get_httpd_handlers()]).
-
-handle_request(MochiReq, DefaultFun, UrlHandlers, DbUrlHandlers,
-    DesignUrlHandlers) ->
-    %% reset rewrite count for new request
-    erlang:put(?REWRITE_COUNT, 0),
-
-    MochiReq1 = couch_httpd_vhost:dispatch_host(MochiReq),
-
-    handle_request_int(MochiReq1, DefaultFun,
-                UrlHandlers, DbUrlHandlers, DesignUrlHandlers).
-
-handle_request_int(MochiReq, DefaultFun,
-            UrlHandlers, DbUrlHandlers, DesignUrlHandlers) ->
-    Begin = os:timestamp(),
-    % for the path, use the raw path with the query string and fragment
-    % removed, but URL quoting left intact
-    RawUri = MochiReq:get(raw_path),
-    {"/" ++ Path, _, _} = mochiweb_util:urlsplit_path(RawUri),
-
-    % get requested path
-    RequestedPath = case MochiReq:get_header_value("x-couchdb-vhost-path") of
-        undefined ->
-            case MochiReq:get_header_value("x-couchdb-requested-path") of
-                undefined -> RawUri;
-                R -> R
-            end;
-        P -> P
-    end,
-
-    HandlerKey =
-    case mochiweb_util:partition(Path, "/") of
-    {"", "", ""} ->
-        <<"/">>; % Special case the root url handler
-    {FirstPart, _, _} ->
-        list_to_binary(FirstPart)
-    end,
-    couch_log:debug("~p ~s ~p from ~p~nHeaders: ~p", [
-        MochiReq:get(method),
-        RawUri,
-        MochiReq:get(version),
-        peer(MochiReq),
-        mochiweb_headers:to_list(MochiReq:get(headers))
-    ]),
-
-    Method1 =
-    case MochiReq:get(method) of
-        % already an atom
-        Meth when is_atom(Meth) -> Meth;
-
-        % Non standard HTTP verbs aren't atoms (COPY, MOVE etc) so convert when
-        % possible (if any module references the atom, then it's existing).
-        Meth -> couch_util:to_existing_atom(Meth)
-    end,
-    increment_method_stats(Method1),
-
-    % allow broken HTTP clients to fake a full method vocabulary with an X-HTTP-METHOD-OVERRIDE header
-    MethodOverride = MochiReq:get_primary_header_value("X-HTTP-Method-Override"),
-    Method2 = case lists:member(MethodOverride, ["GET", "HEAD", "POST",
-                                                 "PUT", "DELETE",
-                                                 "TRACE", "CONNECT",
-                                                 "COPY"]) of
-    true ->
-        couch_log:info("MethodOverride: ~s (real method was ~s)",
-                       [MethodOverride, Method1]),
-        case Method1 of
-        'POST' -> couch_util:to_existing_atom(MethodOverride);
-        _ ->
-            % Ignore X-HTTP-Method-Override when the original verb isn't POST.
-            % I'd like to send a 406 error to the client, but that'd require a nasty refactor.
-            % throw({not_acceptable, <<"X-HTTP-Method-Override may only be used with POST requests.">>})
-            Method1
-        end;
-    _ -> Method1
-    end,
-
-    % alias HEAD to GET as mochiweb takes care of stripping the body
-    Method = case Method2 of
-        'HEAD' -> 'GET';
-        Other -> Other
-    end,
-
-    HttpReq = #httpd{
-        mochi_req = MochiReq,
-        peer = peer(MochiReq),
-        method = Method,
-        requested_path_parts =
-            [?l2b(unquote(Part)) || Part <- string:tokens(RequestedPath, "/")],
-        path_parts = [?l2b(unquote(Part)) || Part <- string:tokens(Path, "/")],
-        db_url_handlers = DbUrlHandlers,
-        design_url_handlers = DesignUrlHandlers,
-        default_fun = DefaultFun,
-        url_handlers = UrlHandlers,
-        user_ctx = erlang:erase(pre_rewrite_user_ctx),
-        auth = erlang:erase(pre_rewrite_auth)
-    },
-
-    HandlerFun = couch_util:dict_find(HandlerKey, UrlHandlers, DefaultFun),
-
-    {ok, Resp} =
-    try
-        validate_host(HttpReq),
-        check_request_uri_length(RawUri),
-        case chttpd_cors:maybe_handle_preflight_request(HttpReq) of
-        not_preflight ->
-            case authenticate_request(HttpReq) of
-            #httpd{} = Req ->
-                HandlerFun(Req);
-            Response ->
-                Response
-            end;
-        Response ->
-            Response
-        end
-    catch
-        throw:{http_head_abort, Resp0} ->
-            {ok, Resp0};
-        throw:{invalid_json, S} ->
-            couch_log:error("attempted upload of invalid JSON"
-                            " (set log_level to debug to log it)", []),
-            couch_log:debug("Invalid JSON: ~p",[S]),
-            send_error(HttpReq, {bad_request, invalid_json});
-        throw:unacceptable_encoding ->
-            couch_log:error("unsupported encoding method for the response", []),
-            send_error(HttpReq, {not_acceptable, "unsupported encoding"});
-        throw:bad_accept_encoding_value ->
-            couch_log:error("received invalid Accept-Encoding header", []),
-            send_error(HttpReq, bad_request);
-        exit:normal ->
-            exit(normal);
-        exit:snappy_nif_not_loaded ->
-            ErrorReason = "To access the database or view index, Apache CouchDB"
-                          " must be built with Erlang OTP R13B04 or higher.",
-            couch_log:error("~s", [ErrorReason]),
-            send_error(HttpReq, {bad_otp_release, ErrorReason});
-        exit:{body_too_large, _} ->
-            send_error(HttpReq, request_entity_too_large);
-        exit:{uri_too_long, _} ->
-            send_error(HttpReq, request_uri_too_long);
-        throw:Error ->
-            Stack = erlang:get_stacktrace(),
-            couch_log:debug("Minor error in HTTP request: ~p",[Error]),
-            couch_log:debug("Stacktrace: ~p",[Stack]),
-            send_error(HttpReq, Error);
-        error:badarg ->
-            Stack = erlang:get_stacktrace(),
-            couch_log:error("Badarg error in HTTP request",[]),
-            couch_log:info("Stacktrace: ~p",[Stack]),
-            send_error(HttpReq, badarg);
-        error:function_clause ->
-            Stack = erlang:get_stacktrace(),
-            couch_log:error("function_clause error in HTTP request",[]),
-            couch_log:info("Stacktrace: ~p",[Stack]),
-            send_error(HttpReq, function_clause);
-        Tag:Error ->
-            Stack = erlang:get_stacktrace(),
-            couch_log:error("Uncaught error in HTTP request: ~p",
-                            [{Tag, Error}]),
-            couch_log:info("Stacktrace: ~p",[Stack]),
-            send_error(HttpReq, Error)
-    end,
-    RequestTime = round(timer:now_diff(os:timestamp(), Begin)/1000),
-    couch_stats:update_histogram([couchdb, request_time], RequestTime),
-    couch_stats:increment_counter([couchdb, httpd, requests]),
-    {ok, Resp}.
-
 validate_host(#httpd{} = Req) ->
     case config:get_boolean("httpd", "validate_host", false) of
         true ->
@@ -418,26 +100,6 @@ valid_hosts() ->
     List = config:get("httpd", "valid_hosts", ""),
     re:split(List, ",", [{return, list}]).
 
-check_request_uri_length(Uri) ->
-    check_request_uri_length(Uri, config:get("httpd", "max_uri_length")).
-
-check_request_uri_length(_Uri, undefined) ->
-    ok;
-check_request_uri_length(Uri, MaxUriLen) when is_list(MaxUriLen) ->
-    case length(Uri) > list_to_integer(MaxUriLen) of
-        true ->
-            throw(request_uri_too_long);
-        false ->
-            ok
-    end.
-
-authenticate_request(Req) ->
-    {ok, AuthenticationFuns} = application:get_env(couch, auth_handlers),
-    chttpd:authenticate_request(Req, couch_auth_cache, AuthenticationFuns).
-
-increment_method_stats(Method) ->
-    couch_stats:increment_counter([couchdb, httpd_request_methods, Method]).
-
 validate_referer(Req) ->
     Host = host_for_request(Req),
     Referer = header_value(Req, "Referer", fail),
@@ -1225,13 +887,6 @@ http_respond_(#httpd{mochi_req = MochiReq}, 413, Headers, Args, Type) ->
 http_respond_(#httpd{mochi_req = MochiReq}, Code, Headers, Args, Type) ->
     MochiReq:Type({Code, Headers, Args}).
 
-peer(MochiReq) ->
-    case MochiReq:get(socket) of
-        {remote, Pid, _} ->
-            node(Pid);
-        _ ->
-            MochiReq:get(peer)
-    end.
 
 %%%%%%%% module tests below %%%%%%%%
 
diff --git a/src/couch/src/couch_secondary_sup.erl b/src/couch/src/couch_secondary_sup.erl
index bb78215..4ccd0c9 100644
--- a/src/couch/src/couch_secondary_sup.erl
+++ b/src/couch/src/couch_secondary_sup.erl
@@ -33,11 +33,6 @@ init([]) ->
         {uuids, {couch_uuids, start, []}}
     ],
 
-    MaybeHttp = case http_enabled() of
-        true -> [{httpd, {couch_httpd, start_link, []}}];
-        false -> couch_httpd:set_auth_handlers(), []
-    end,
-
     MaybeHttps = case https_enabled() of
         true -> [{httpsd, {chttpd, start_link, [https]}}];
         false -> []
@@ -55,13 +50,10 @@ init([]) ->
                 [Module]}
         end
         || {Name, Spec}
-        <- Daemons ++ MaybeHttp ++ MaybeHttps, Spec /= ""],
+        <- Daemons ++ MaybeHttps, Spec /= ""],
     {ok, {{one_for_one, 50, 3600},
         couch_epi:register_service(couch_db_epi, Children)}}.
 
-http_enabled() ->
-    config:get_boolean("httpd", "enable", false).
-
 https_enabled() ->
     % 1. [ssl] enable = true | false
     % 2. if [daemons] httpsd == {chttpd, start_link, [https]} -> pretend true as well
diff --git a/src/couch/test/eunit/chttpd_endpoints_tests.erl b/src/couch/test/eunit/chttpd_endpoints_tests.erl
index 3c8586a..f164ae6 100644
--- a/src/couch/test/eunit/chttpd_endpoints_tests.erl
+++ b/src/couch/test/eunit/chttpd_endpoints_tests.erl
@@ -47,10 +47,10 @@ url_handlers() ->
         {<<"_replicate">>, chttpd_misc, handle_replicate_req},
         {<<"_uuids">>, chttpd_misc, handle_uuids_req},
         {<<"_session">>, chttpd_auth, handle_session_req},
-        {<<"_up">>, chttpd_misc, handle_up_req},
-        {<<"_membership">>, mem3_httpd, handle_membership_req},
-        {<<"_db_updates">>, global_changes_httpd, handle_global_changes_req},
-        {<<"_cluster_setup">>, setup_httpd, handle_setup_req}
+        {<<"_membership">>, chttpd_httpd_handlers, not_supported},
+        {<<"_db_updates">>, chttpd_httpd_handlers, not_implemented},
+        {<<"_cluster_setup">>, chttpd_httpd_handlers, not_implemented},
+        {<<"_up">>, chttpd_misc, handle_up_req}
     ],
 
     lists:foreach(fun({Path, Mod, Fun}) ->
@@ -67,9 +67,9 @@ db_handlers() ->
         {<<"_view_cleanup">>, chttpd_db, handle_view_cleanup_req},
         {<<"_compact">>, chttpd_db, handle_compact_req},
         {<<"_design">>, chttpd_db, handle_design_req},
-        {<<"_temp_view">>, chttpd_view, handle_temp_view_req},
+        {<<"_temp_view">>, chttpd_httpd_handlers, not_supported},
         {<<"_changes">>, chttpd_db, handle_changes_req},
-        {<<"_shards">>, mem3_httpd, handle_shards_req},
+        {<<"_shards">>, chttpd_httpd_handlers, not_supported},
         {<<"_index">>, mango_httpd, handle_req},
         {<<"_explain">>, mango_httpd, handle_req},
         {<<"_find">>, mango_httpd, handle_req}
@@ -87,11 +87,11 @@ db_handlers() ->
 design_handlers() ->
     Handlers = [
         {<<"_view">>, chttpd_view, handle_view_req},
-        {<<"_show">>, chttpd_show, handle_doc_show_req},
-        {<<"_list">>, chttpd_show, handle_view_list_req},
+        {<<"_show">>, chttpd_httpd_handlers, not_supported},
+        {<<"_list">>, chttpd_httpd_handlers, not_supported},
         {<<"_update">>, chttpd_show, handle_doc_update_req},
         {<<"_info">>, chttpd_db, handle_design_info_req},
-        {<<"_rewrite">>, chttpd_rewrite, handle_rewrite_req}
+        {<<"_rewrite">>, chttpd_httpd_handlers, not_supported}
     ],
 
     lists:foreach(fun({Path, Mod, Fun}) ->
diff --git a/src/couch_replicator/src/couch_replicator_ids.erl b/src/couch_replicator/src/couch_replicator_ids.erl
index d1cbe57..44b9e47 100644
--- a/src/couch_replicator/src/couch_replicator_ids.erl
+++ b/src/couch_replicator/src/couch_replicator_ids.erl
@@ -58,7 +58,7 @@ base_id(#{?SOURCE := Src0, ?TARGET := Tgt0} = Rep, 3) ->
 
 base_id(#{?SOURCE := Src0, ?TARGET := Tgt0} = Rep, 2) ->
     {ok, HostName} = inet:gethostname(),
-    Port = case (catch mochiweb_socket_server:get(couch_httpd, port)) of
+    Port = case (catch mochiweb_socket_server:get(chttpd, port)) of
     P when is_number(P) ->
         P;
     _ ->