diff options
Diffstat (limited to 'src/chttpd/src/chttpd_misc.erl')
-rw-r--r-- | src/chttpd/src/chttpd_misc.erl | 239 |
1 files changed, 129 insertions, 110 deletions
diff --git a/src/chttpd/src/chttpd_misc.erl b/src/chttpd/src/chttpd_misc.erl index 25a0fa77f..6d119572d 100644 --- a/src/chttpd/src/chttpd_misc.erl +++ b/src/chttpd/src/chttpd_misc.erl @@ -31,9 +31,15 @@ -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_chunk/2,start_chunked_response/3]). +-import( + chttpd, + [ + send_json/2, send_json/3, + send_method_not_allowed/2, + send_chunk/2, + start_chunked_response/3 + ] +). -define(MAX_DB_NUM_FOR_DBS_INFO, 100). @@ -42,19 +48,21 @@ handle_welcome_req(Req) -> handle_welcome_req(Req, <<"Welcome">>). -handle_welcome_req(#httpd{method='GET'}=Req, WelcomeMessage) -> - send_json(Req, {[ - {couchdb, WelcomeMessage}, - {version, list_to_binary(couch_server:get_version())}, - {git_sha, list_to_binary(couch_server:get_git_sha())}, - {uuid, couch_server:get_uuid()}, - {features, get_features()} - ] ++ case config:get("vendor") of - [] -> - []; - Properties -> - [{vendor, {[{?l2b(K), ?l2b(V)} || {K, V} <- Properties]}}] - end +handle_welcome_req(#httpd{method = 'GET'} = Req, WelcomeMessage) -> + send_json(Req, { + [ + {couchdb, WelcomeMessage}, + {version, list_to_binary(couch_server:get_version())}, + {git_sha, list_to_binary(couch_server:get_git_sha())}, + {uuid, couch_server:get_uuid()}, + {features, get_features()} + ] ++ + case config:get("vendor") of + [] -> + []; + Properties -> + [{vendor, {[{?l2b(K), ?l2b(V)} || {K, V} <- Properties]}}] + end }); handle_welcome_req(Req, _) -> send_method_not_allowed(Req, "GET,HEAD"). @@ -70,7 +78,7 @@ get_features() -> handle_favicon_req(Req) -> handle_favicon_req(Req, get_docroot()). -handle_favicon_req(#httpd{method='GET'}=Req, DocumentRoot) -> +handle_favicon_req(#httpd{method = 'GET'} = Req, DocumentRoot) -> {DateNow, TimeNow} = calendar:universal_time(), DaysNow = calendar:date_to_gregorian_days(DateNow), DaysWhenExpires = DaysNow + 365, @@ -87,25 +95,26 @@ handle_favicon_req(Req, _) -> handle_utils_dir_req(Req) -> handle_utils_dir_req(Req, get_docroot()). -handle_utils_dir_req(#httpd{method='GET'}=Req, DocumentRoot) -> +handle_utils_dir_req(#httpd{method = 'GET'} = Req, DocumentRoot) -> "/" ++ UrlPath = chttpd:path(Req), case chttpd:partition(UrlPath) of - {_ActionKey, "/", RelativePath} -> - % GET /_utils/path or GET /_utils/ - CachingHeaders = [{"Cache-Control", "private, must-revalidate"}], - DefaultValues = "child-src 'self' data: blob:; default-src 'self'; img-src 'self' data:; font-src 'self'; " - "script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline';", - Headers = chttpd_util:maybe_add_csp_header("utils", CachingHeaders, DefaultValues), - chttpd:serve_file(Req, RelativePath, DocumentRoot, Headers); - {_ActionKey, "", _RelativePath} -> - % GET /_utils - RedirectPath = chttpd:path(Req) ++ "/", - chttpd:send_redirect(Req, RedirectPath) + {_ActionKey, "/", RelativePath} -> + % GET /_utils/path or GET /_utils/ + CachingHeaders = [{"Cache-Control", "private, must-revalidate"}], + DefaultValues = + "child-src 'self' data: blob:; default-src 'self'; img-src 'self' data:; font-src 'self'; " + "script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline';", + Headers = chttpd_util:maybe_add_csp_header("utils", CachingHeaders, DefaultValues), + chttpd:serve_file(Req, RelativePath, DocumentRoot, Headers); + {_ActionKey, "", _RelativePath} -> + % GET /_utils + RedirectPath = chttpd:path(Req) ++ "/", + chttpd:send_redirect(Req, RedirectPath) end; handle_utils_dir_req(Req, _) -> send_method_not_allowed(Req, "GET,HEAD"). -handle_all_dbs_req(#httpd{method='GET'}=Req) -> +handle_all_dbs_req(#httpd{method = 'GET'} = Req) -> Args = couch_mrview_http:parse_params(Req, undefined), ShardDbName = config:get("mem3", "shards_db", "_dbs"), %% shard_db is not sharded but mem3:shards treats it as an edge case @@ -114,8 +123,8 @@ handle_all_dbs_req(#httpd{method='GET'}=Req) -> Etag = couch_httpd:make_etag({Info}), Options = [{user_ctx, Req#httpd.user_ctx}], {ok, Resp} = chttpd:etag_respond(Req, Etag, fun() -> - {ok, Resp} = chttpd:start_delayed_json_response(Req, 200, [{"ETag",Etag}]), - VAcc = #vacc{req=Req,resp=Resp}, + {ok, Resp} = chttpd:start_delayed_json_response(Req, 200, [{"ETag", Etag}]), + VAcc = #vacc{req = Req, resp = Resp}, fabric:all_docs(ShardDbName, Options, fun all_dbs_callback/2, VAcc, Args) end), case is_record(Resp, vacc) of @@ -125,26 +134,27 @@ handle_all_dbs_req(#httpd{method='GET'}=Req) -> handle_all_dbs_req(Req) -> send_method_not_allowed(Req, "GET,HEAD"). -all_dbs_callback({meta, _Meta}, #vacc{resp=Resp0}=Acc) -> +all_dbs_callback({meta, _Meta}, #vacc{resp = Resp0} = Acc) -> {ok, Resp1} = chttpd:send_delayed_chunk(Resp0, "["), - {ok, Acc#vacc{resp=Resp1}}; -all_dbs_callback({row, Row}, #vacc{resp=Resp0}=Acc) -> + {ok, Acc#vacc{resp = Resp1}}; +all_dbs_callback({row, Row}, #vacc{resp = Resp0} = Acc) -> Prepend = couch_mrview_http:prepend_val(Acc), - case couch_util:get_value(id, Row) of <<"_design", _/binary>> -> - {ok, Acc}; - DbName -> - {ok, Resp1} = chttpd:send_delayed_chunk(Resp0, [Prepend, ?JSON_ENCODE(DbName)]), - {ok, Acc#vacc{prepend=",", resp=Resp1}} + case couch_util:get_value(id, Row) of + <<"_design", _/binary>> -> + {ok, Acc}; + DbName -> + {ok, Resp1} = chttpd:send_delayed_chunk(Resp0, [Prepend, ?JSON_ENCODE(DbName)]), + {ok, Acc#vacc{prepend = ",", resp = Resp1}} end; -all_dbs_callback(complete, #vacc{resp=Resp0}=Acc) -> +all_dbs_callback(complete, #vacc{resp = Resp0} = Acc) -> {ok, Resp1} = chttpd:send_delayed_chunk(Resp0, "]"), {ok, Resp2} = chttpd:end_delayed_json_response(Resp1), - {ok, Acc#vacc{resp=Resp2}}; -all_dbs_callback({error, Reason}, #vacc{resp=Resp0}=Acc) -> + {ok, Acc#vacc{resp = Resp2}}; +all_dbs_callback({error, Reason}, #vacc{resp = Resp0} = Acc) -> {ok, Resp1} = chttpd:send_delayed_error(Resp0, Reason), - {ok, Acc#vacc{resp=Resp1}}. + {ok, Acc#vacc{resp = Resp1}}. -handle_dbs_info_req(#httpd{method='POST'}=Req) -> +handle_dbs_info_req(#httpd{method = 'POST'} = Req) -> chttpd:validate_ctype(Req, "application/json"), Props = chttpd:json_body_obj(Req), Keys = couch_mrview_util:get_view_keys(Props), @@ -152,41 +162,52 @@ handle_dbs_info_req(#httpd{method='POST'}=Req) -> undefined -> throw({bad_request, "`keys` member must exist."}); _ -> ok end, - MaxNumber = config:get_integer("chttpd", - "max_db_number_for_dbs_info_req", ?MAX_DB_NUM_FOR_DBS_INFO), + MaxNumber = config:get_integer( + "chttpd", + "max_db_number_for_dbs_info_req", + ?MAX_DB_NUM_FOR_DBS_INFO + ), case length(Keys) =< MaxNumber of true -> ok; false -> throw({bad_request, too_many_keys}) end, {ok, Resp} = chttpd:start_json_response(Req, 200), send_chunk(Resp, "["), - lists:foldl(fun(DbName, AccSeparator) -> - case catch fabric:get_db_info(DbName) of - {ok, Result} -> - Json = ?JSON_ENCODE({[{key, DbName}, {info, {Result}}]}), - send_chunk(Resp, AccSeparator ++ Json); - _ -> - Json = ?JSON_ENCODE({[{key, DbName}, {error, not_found}]}), - send_chunk(Resp, AccSeparator ++ Json) + lists:foldl( + fun(DbName, AccSeparator) -> + case catch fabric:get_db_info(DbName) of + {ok, Result} -> + Json = ?JSON_ENCODE({[{key, DbName}, {info, {Result}}]}), + send_chunk(Resp, AccSeparator ++ Json); + _ -> + Json = ?JSON_ENCODE({[{key, DbName}, {error, not_found}]}), + send_chunk(Resp, AccSeparator ++ Json) + end, + % AccSeparator now has a comma + "," end, - "," % AccSeparator now has a comma - end, "", Keys), + "", + Keys + ), send_chunk(Resp, "]"), chttpd:end_json_response(Resp); handle_dbs_info_req(Req) -> send_method_not_allowed(Req, "POST"). -handle_task_status_req(#httpd{method='GET'}=Req) -> +handle_task_status_req(#httpd{method = 'GET'} = Req) -> ok = chttpd:verify_is_server_admin(Req), {Replies, _BadNodes} = gen_server:multi_call(couch_task_status, all), - Response = lists:flatmap(fun({Node, Tasks}) -> - [{[{node,Node} | Task]} || Task <- Tasks] - end, Replies), + Response = lists:flatmap( + fun({Node, Tasks}) -> + [{[{node, Node} | Task]} || Task <- Tasks] + end, + Replies + ), send_json(Req, lists:sort(Response)); handle_task_status_req(Req) -> send_method_not_allowed(Req, "GET,HEAD"). -handle_replicate_req(#httpd{method='POST', user_ctx=Ctx, req_body=PostBody} = Req) -> +handle_replicate_req(#httpd{method = 'POST', user_ctx = Ctx, req_body = PostBody} = Req) -> chttpd:validate_ctype(Req, "application/json"), %% see HACK in chttpd.erl about replication case replicate(PostBody, Ctx) of @@ -198,11 +219,11 @@ handle_replicate_req(#httpd{method='POST', user_ctx=Ctx, req_body=PostBody} = Re send_json(Req, {[{ok, true} | JsonResults]}); {ok, stopped} -> send_json(Req, 200, {[{ok, stopped}]}); - {error, not_found=Error} -> + {error, not_found = Error} -> chttpd:send_error(Req, Error); - {error, {_, _}=Error} -> + {error, {_, _} = Error} -> chttpd:send_error(Req, Error); - {_, _}=Error -> + {_, _} = Error -> chttpd:send_error(Req, Error) end; handle_replicate_req(Req) -> @@ -210,50 +231,50 @@ handle_replicate_req(Req) -> replicate({Props} = PostBody, Ctx) -> case couch_util:get_value(<<"cancel">>, Props) of - true -> - cancel_replication(PostBody, Ctx); - _ -> - Node = choose_node([ - couch_util:get_value(<<"source">>, Props), - couch_util:get_value(<<"target">>, Props) - ]), - case rpc:call(Node, couch_replicator, replicate, [PostBody, Ctx]) of - {badrpc, Reason} -> - erlang:error(Reason); - Res -> - Res - end + true -> + cancel_replication(PostBody, Ctx); + _ -> + Node = choose_node([ + couch_util:get_value(<<"source">>, Props), + couch_util:get_value(<<"target">>, Props) + ]), + case rpc:call(Node, couch_replicator, replicate, [PostBody, Ctx]) of + {badrpc, Reason} -> + erlang:error(Reason); + Res -> + Res + end end. cancel_replication(PostBody, Ctx) -> {Res, _Bad} = rpc:multicall(couch_replicator, replicate, [PostBody, Ctx]), case [X || {ok, {cancelled, _}} = X <- Res] of - [Success|_] -> - % Report success if at least one node canceled the replication - Success; - [] -> - case lists:usort(Res) of - [UniqueReply] -> - % Report a universally agreed-upon reply - UniqueReply; + [Success | _] -> + % Report success if at least one node canceled the replication + Success; [] -> - {error, badrpc}; - Else -> - % Unclear what to do here -- pick the first error? - % Except try ignoring any {error, not_found} responses - % because we'll always get two of those - hd(Else -- [{error, not_found}]) - end + case lists:usort(Res) of + [UniqueReply] -> + % Report a universally agreed-upon reply + UniqueReply; + [] -> + {error, badrpc}; + Else -> + % Unclear what to do here -- pick the first error? + % Except try ignoring any {error, not_found} responses + % because we'll always get two of those + hd(Else -- [{error, not_found}]) + end end. choose_node(Key) when is_binary(Key) -> Checksum = erlang:crc32(Key), - Nodes = lists:sort([node()|erlang:nodes()]), + Nodes = lists:sort([node() | erlang:nodes()]), lists:nth(1 + Checksum rem length(Nodes), Nodes); choose_node(Key) -> choose_node(term_to_binary(Key)). -handle_reload_query_servers_req(#httpd{method='POST'}=Req) -> +handle_reload_query_servers_req(#httpd{method = 'POST'} = Req) -> chttpd:validate_ctype(Req, "application/json"), ok = couch_proc_manager:reload(), send_json(Req, 200, {[{ok, true}]}); @@ -263,23 +284,21 @@ handle_reload_query_servers_req(Req) -> handle_uuids_req(Req) -> couch_httpd_misc_handlers:handle_uuids_req(Req). - -handle_up_req(#httpd{method='GET'} = Req) -> +handle_up_req(#httpd{method = 'GET'} = Req) -> case config:get("couchdb", "maintenance_mode") of - "true" -> - send_json(Req, 404, {[{status, maintenance_mode}]}); - "nolb" -> - send_json(Req, 404, {[{status, nolb}]}); - _ -> - {ok, {Status}} = mem3_seeds:get_status(), - case couch_util:get_value(status, Status) of - ok -> - send_json(Req, 200, {Status}); - seeding -> - send_json(Req, 404, {Status}) - end + "true" -> + send_json(Req, 404, {[{status, maintenance_mode}]}); + "nolb" -> + send_json(Req, 404, {[{status, nolb}]}); + _ -> + {ok, {Status}} = mem3_seeds:get_status(), + case couch_util:get_value(status, Status) of + ok -> + send_json(Req, 200, {Status}); + seeding -> + send_json(Req, 404, {Status}) + end end; - handle_up_req(Req) -> send_method_not_allowed(Req, "GET,HEAD"). |