summaryrefslogtreecommitdiff
path: root/deps/chttpd/src/chttpd_misc.erl
diff options
context:
space:
mode:
Diffstat (limited to 'deps/chttpd/src/chttpd_misc.erl')
-rw-r--r--deps/chttpd/src/chttpd_misc.erl283
1 files changed, 283 insertions, 0 deletions
diff --git a/deps/chttpd/src/chttpd_misc.erl b/deps/chttpd/src/chttpd_misc.erl
new file mode 100644
index 00000000..1d0556d5
--- /dev/null
+++ b/deps/chttpd/src/chttpd_misc.erl
@@ -0,0 +1,283 @@
+% 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_misc).
+
+-export([handle_welcome_req/2,handle_favicon_req/2,handle_utils_dir_req/2,
+ handle_all_dbs_req/1,handle_replicate_req/1,handle_restart_req/1,
+ handle_uuids_req/1,handle_config_req/1,handle_log_req/1,
+ handle_task_status_req/1,handle_sleep_req/1,handle_welcome_req/1,
+ handle_utils_dir_req/1, handle_favicon_req/1, handle_system_req/1]).
+
+
+-include_lib("couch/include/couch_db.hrl").
+
+-import(chttpd,
+ [send_json/2,send_json/3,send_method_not_allowed/2,
+ send_chunk/2,start_chunked_response/3]).
+
+% httpd global handlers
+
+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:version())},
+ {bigcouch, get_version()}
+ ]});
+handle_welcome_req(Req, _) ->
+ send_method_not_allowed(Req, "GET,HEAD").
+
+get_version() ->
+ Releases = release_handler:which_releases(),
+ Version = case [V || {"bigcouch", V, _, current} <- Releases] of
+ [] ->
+ case [V || {"bigcouch", V, _, permanent} <- Releases] of
+ [] ->
+ "dev";
+ [Permanent] ->
+ Permanent
+ end;
+ [Current] ->
+ Current
+ end,
+ list_to_binary(Version).
+
+handle_favicon_req(Req) ->
+ handle_favicon_req(Req, couch_config:get("chttpd", "docroot")).
+
+handle_favicon_req(#httpd{method='GET'}=Req, DocumentRoot) ->
+ chttpd:serve_file(Req, "favicon.ico", DocumentRoot);
+handle_favicon_req(Req, _) ->
+ send_method_not_allowed(Req, "GET,HEAD").
+
+handle_utils_dir_req(Req) ->
+ handle_utils_dir_req(Req, couch_config:get("chttpd", "docroot")).
+
+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/
+ chttpd:serve_file(Req, RelativePath, DocumentRoot);
+ {_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_sleep_req(#httpd{method='GET'}=Req) ->
+ Time = list_to_integer(chttpd:qs_value(Req, "time")),
+ receive snicklefart -> ok after Time -> ok end,
+ send_json(Req, {[{ok, true}]});
+handle_sleep_req(Req) ->
+ send_method_not_allowed(Req, "GET,HEAD").
+
+handle_all_dbs_req(#httpd{method='GET'}=Req) ->
+ ShardDbName = couch_config:get("mem3", "shard_db", "dbs"),
+ %% shard_db is not sharded but mem3:shards treats it as an edge case
+ %% so it can be pushed thru fabric
+ {ok, Info} = fabric:get_db_info(ShardDbName),
+ Etag = couch_httpd:make_etag({Info}),
+ chttpd:etag_respond(Req, Etag, fun() ->
+ {ok, Resp} = chttpd:start_delayed_json_response(Req, 200, [{"Etag",Etag}]),
+ fabric:all_docs(ShardDbName, fun all_dbs_callback/2,
+ {nil, Resp}, #view_query_args{})
+ end);
+handle_all_dbs_req(Req) ->
+ send_method_not_allowed(Req, "GET,HEAD").
+
+all_dbs_callback({total_and_offset, _Total, _Offset}, {_, Resp}) ->
+ {ok, Resp1} = chttpd:send_delayed_chunk(Resp, "["),
+ {ok, {"", Resp1}};
+all_dbs_callback({row, {Row}}, {Prepend, Resp}) ->
+ case couch_util:get_value(id, Row) of <<"_design", _/binary>> ->
+ {ok, {Prepend, Resp}};
+ DbName ->
+ {ok, Resp1} = chttpd:send_delayed_chunk(Resp, [Prepend, ?JSON_ENCODE(DbName)]),
+ {ok, {",", Resp1}}
+ end;
+all_dbs_callback(complete, {_, Resp}) ->
+ {ok, Resp1} = chttpd:send_delayed_chunk(Resp, "]"),
+ chttpd:end_delayed_json_response(Resp1);
+all_dbs_callback({error, Reason}, {_, Resp}) ->
+ chttpd:send_delayed_error(Resp, Reason).
+
+handle_task_status_req(#httpd{method='GET'}=Req) ->
+ {Replies, _BadNodes} = gen_server:multi_call(couch_task_status, all),
+ 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) ->
+ couch_httpd:validate_ctype(Req, "application/json"),
+ %% see HACK in chttpd.erl about replication
+ PostBody = get(post_body),
+ try replicate(PostBody, Ctx, mem3_rep_manager) of
+ {ok, {continuous, RepId}} ->
+ send_json(Req, 202, {[{ok, true}, {<<"_local_id">>, RepId}]});
+ {ok, {cancelled, RepId}} ->
+ send_json(Req, 200, {[{ok, true}, {<<"_local_id">>, RepId}]});
+ {ok, {JsonResults}} ->
+ send_json(Req, {[{ok, true} | JsonResults]});
+ {error, {Type, Details}} ->
+ send_json(Req, 500, {[{error, Type}, {reason, Details}]});
+ {error, not_found} ->
+ send_json(Req, 404, {[{error, not_found}]});
+ {error, Reason} ->
+ try
+ send_json(Req, 500, {[{error, Reason}]})
+ catch
+ exit:{json_encode, _} ->
+ send_json(Req, 500, {[{error, couch_util:to_binary(Reason)}]})
+ end
+ catch
+ throw:{db_not_found, Msg} ->
+ send_json(Req, 404, {[{error, db_not_found}, {reason, Msg}]});
+ throw:{unauthorized, Msg} ->
+ send_json(Req, 404, {[{error, unauthorized}, {reason, Msg}]})
+ end;
+handle_replicate_req(Req) ->
+ send_method_not_allowed(Req, "POST").
+
+replicate({Props} = PostBody, Ctx, Module) ->
+ Node = choose_node([
+ couch_util:get_value(<<"source">>, Props),
+ couch_util:get_value(<<"target">>, Props)
+ ]),
+ case rpc:call(Node, couch_rep, replicate, [PostBody, Ctx, Module]) of
+ {badrpc, Reason} ->
+ erlang:error(Reason);
+ Res ->
+ Res
+ end.
+
+choose_node(Key) when is_binary(Key) ->
+ Checksum = erlang:crc32(Key),
+ Nodes = lists:sort([node()|erlang:nodes()]),
+ lists:nth(1 + Checksum rem length(Nodes), Nodes);
+choose_node(Key) ->
+ choose_node(term_to_binary(Key)).
+
+handle_restart_req(#httpd{method='POST'}=Req) ->
+ couch_server_sup:restart_core_server(),
+ send_json(Req, 200, {[{ok, true}]});
+handle_restart_req(Req) ->
+ send_method_not_allowed(Req, "POST").
+
+
+handle_uuids_req(Req) ->
+ couch_httpd_misc_handlers:handle_uuids_req(Req).
+
+
+% Config request handler
+
+
+% GET /_config/
+% GET /_config
+handle_config_req(#httpd{method='GET', path_parts=[_]}=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(), couch_config:all()),
+ KVs = dict:fold(fun(Section, Values, Acc) ->
+ [{list_to_binary(Section), {Values}} | Acc]
+ end, [], Grouped),
+ send_json(Req, 200, {KVs});
+% GET /_config/Section
+handle_config_req(#httpd{method='GET', path_parts=[_,Section]}=Req) ->
+ KVs = [{list_to_binary(Key), list_to_binary(Value)}
+ || {Key, Value} <- couch_config:get(Section)],
+ send_json(Req, 200, {KVs});
+% PUT /_config/Section/Key
+% "value"
+handle_config_req(#httpd{method='PUT', path_parts=[_, Section, Key]}=Req) ->
+ Value = chttpd:json_body(Req),
+ Persist = chttpd:header_value(Req, "X-Couch-Persist") /= "false",
+ OldValue = couch_config:get(Section, Key, ""),
+ ok = couch_config:set(Section, Key, ?b2l(Value), Persist),
+ send_json(Req, 200, list_to_binary(OldValue));
+% GET /_config/Section/Key
+handle_config_req(#httpd{method='GET', path_parts=[_, Section, Key]}=Req) ->
+ case couch_config:get(Section, Key, null) of
+ null ->
+ throw({not_found, unknown_config_value});
+ Value ->
+ send_json(Req, 200, list_to_binary(Value))
+ end;
+% DELETE /_config/Section/Key
+handle_config_req(#httpd{method='DELETE',path_parts=[_,Section,Key]}=Req) ->
+ Persist = chttpd:header_value(Req, "X-Couch-Persist") /= "false",
+ case couch_config:get(Section, Key, null) of
+ null ->
+ throw({not_found, unknown_config_value});
+ OldValue ->
+ couch_config:delete(Section, Key, Persist),
+ send_json(Req, 200, list_to_binary(OldValue))
+ end;
+handle_config_req(Req) ->
+ send_method_not_allowed(Req, "GET,PUT,DELETE").
+
+% httpd log handlers
+
+handle_log_req(#httpd{method='GET'}=Req) ->
+ Bytes = list_to_integer(chttpd:qs_value(Req, "bytes", "1000")),
+ Offset = list_to_integer(chttpd:qs_value(Req, "offset", "0")),
+ Chunk = couch_log:read(Bytes, Offset),
+ {ok, Resp} = start_chunked_response(Req, 200, [
+ % send a plaintext response
+ {"Content-Type", "text/plain; charset=utf-8"},
+ {"Content-Length", integer_to_list(length(Chunk))}
+ ]),
+ send_chunk(Resp, Chunk),
+ send_chunk(Resp, "");
+handle_log_req(Req) ->
+ send_method_not_allowed(Req, "GET").
+
+% Note: this resource is exposed on the backdoor interface, but it's in chttpd
+% because it's not couch trunk
+handle_system_req(Req) ->
+ 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),
+ {message_queue_len, MessageQueueLen} = process_info(whereis(couch_server),
+ message_queue_len),
+ send_json(Req, {[
+ {uptime, element(1,statistics(wall_clock)) 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()},
+ {process_count, erlang:system_info(process_count)},
+ {process_limit, erlang:system_info(process_limit)},
+ {message_queue_len, MessageQueueLen}
+ ]}).