diff options
Diffstat (limited to 'src/chttpd/src/chttpd_db.erl')
-rw-r--r-- | src/chttpd/src/chttpd_db.erl | 193 |
1 files changed, 39 insertions, 154 deletions
diff --git a/src/chttpd/src/chttpd_db.erl b/src/chttpd/src/chttpd_db.erl index bfd8f9fc2..4a7b631f9 100644 --- a/src/chttpd/src/chttpd_db.erl +++ b/src/chttpd/src/chttpd_db.erl @@ -15,16 +15,14 @@ -compile(tuple_calls). -include_lib("couch/include/couch_db.hrl"). --include_lib("couch_mrview/include/couch_mrview.hrl"). --include_lib("fabric/include/fabric.hrl"). --include_lib("mem3/include/mem3.hrl"). +-include_lib("couch_views/include/couch_views.hrl"). +-include_lib("kernel/include/logger.hrl"). -export([handle_request/1, handle_compact_req/2, handle_design_req/2, 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, @@ -222,9 +220,13 @@ changes_callback(waiting_for_updates, Acc) -> mochi = Resp1, chunks_sent = ChunksSent + 1 }}; -changes_callback({timeout, _ResponseType}, Acc) -> +changes_callback({timeout, ResponseType}, Acc) -> #cacc{mochi = Resp, chunks_sent = ChunksSent} = Acc, - {ok, Resp1} = chttpd:send_delayed_chunk(Resp, "\n"), + Chunk = case ResponseType of + "eventsource" -> "event: heartbeat\ndata: \n\n"; + _ -> "\n" + end, + {ok, Resp1} = chttpd:send_delayed_chunk(Resp, Chunk), {ok, Acc#cacc{mochi = Resp1, chunks_sent = ChunksSent + 1}}; changes_callback({error, Reason}, #cacc{mochi = #httpd{}} = Acc) -> #cacc{mochi = Req} = Acc, @@ -271,80 +273,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) -> @@ -412,6 +340,7 @@ db_req(#httpd{method='POST', path_parts=[DbName]}=Req, Db) -> Doc0 = chttpd:json_body(Req), Doc1 = couch_doc:from_json_obj_validate(Doc0, fabric2_db:name(Db)), + validate_attachment_names(Doc1), Doc2 = case Doc1#doc.id of <<"">> -> Doc1#doc{id=couch_uuids:new(), revs={0, []}}; @@ -432,6 +361,12 @@ db_req(#httpd{method='POST', path_parts=[DbName]}=Req, Db) -> chttpd_stats:incr_writes(), ok; Error -> + ?LOG_DEBUG(#{ + what => async_update_error, + db => DbName, + docid => DocId, + details => Error + }), couch_log:debug("Batch doc error (~s): ~p",[DocId, Error]) end end), @@ -630,41 +565,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) -> @@ -678,7 +578,7 @@ db_req(#httpd{method='GET',path_parts=[_,OP]}=Req, Db) when ?IS_ALL_DOCS(OP) -> db_req(#httpd{method='POST', path_parts=[_, OP, <<"queries">>]}=Req, Db) when ?IS_ALL_DOCS(OP) -> Props = chttpd:json_body_obj(Req), - case couch_mrview_util:get_view_queries(Props) of + case couch_views_util:get_view_queries(Props) of undefined -> throw({bad_request, <<"POST body must include `queries` parameter.">>}); @@ -773,22 +673,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) -> @@ -1011,7 +895,7 @@ send_all_docs_keys(Db, #mrargs{} = Args, VAcc0) -> doc = DocValue } end, - Row1 = fabric_view:transform_row(Row0), + Row1 = couch_views_http:transform_row(Row0), view_cb(Row1, Acc) end, {ok, VAcc2} = fabric2_db:fold_docs(Db, Keys, CB, VAcc1, OpenOpts), @@ -1231,6 +1115,12 @@ db_doc_req(#httpd{method='PUT'}=Req, Db, DocId) -> chttpd_stats:incr_writes(), ok; Error -> + ?LOG_NOTICE(#{ + what => async_update_error, + db => DbName, + docid => DocId, + details => Error + }), couch_log:notice("Batch doc error (~s): ~p",[DocId, Error]) end end), @@ -1252,7 +1142,7 @@ db_doc_req(#httpd{method='COPY'}=Req, Db, SourceDocId) -> missing_rev -> nil; Rev -> Rev end, - {TargetDocId0, TargetRevs} = couch_httpd_db:parse_copy_destination_header(Req), + {TargetDocId0, TargetRevs} = chttpd_util:parse_copy_destination_header(Req), TargetDocId = list_to_binary(mochiweb_util:unquote(TargetDocId0)), % open old doc Doc = couch_doc_open(Db, SourceDocId, SourceRev, []), @@ -1406,6 +1296,15 @@ bulk_get_multipart_boundary() -> receive_request_data(Req) -> receive_request_data(Req, chttpd:body_length(Req)). +receive_request_data(Req, Len) when Len == chunked -> + Ref = make_ref(), + ChunkFun = fun({_Length, Binary}, _State) -> + self() ! {chunk, Ref, Binary} + end, + couch_httpd:recv_chunked(Req, 4096, ChunkFun, ok), + GetChunk = fun GC() -> receive {chunk, Ref, Binary} -> {Binary, GC} end end, + {receive {chunk, Ref, Binary} -> Binary end, GetChunk}; + receive_request_data(Req, LenLeft) when LenLeft > 0 -> Len = erlang:min(4096, LenLeft), Data = chttpd:recv(Req, Len), @@ -1430,24 +1329,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, []). @@ -2076,6 +1957,10 @@ monitor_attachments(Atts) when is_list(Atts) -> stub -> Monitors; Else -> + ?LOG_ERROR(#{ + what => malformed_attachment_data, + attachment => Att + }), couch_log:error("~p from couch_att:fetch(data, ~p)", [Else, Att]), Monitors end @@ -2092,7 +1977,7 @@ set_namespace(<<"_local_docs">>, Args) -> set_namespace(<<"_design_docs">>, Args) -> set_namespace(<<"_design">>, Args); set_namespace(NS, #mrargs{} = Args) -> - couch_mrview_util:set_extra(Args, namespace, NS). + couch_views_util:set_extra(Args, namespace, NS). %% /db/_bulk_get stuff |