From 4a4c4edd323db00d360db3355c5876b4f14a69a4 Mon Sep 17 00:00:00 2001 From: Adam Kocoloski Date: Tue, 22 Jun 2010 09:56:54 -0400 Subject: switch to 0.11-style (3-arity) design handlers, fix _show _list and _update still need to be fixed, and _view reopens the ddoc --- src/chttpd.erl | 11 ++--- src/chttpd_db.erl | 23 ++++++---- src/chttpd_show.erl | 129 ++++++++++++++++++++++++++++++++++------------------ src/chttpd_view.erl | 51 ++------------------- 4 files changed, 109 insertions(+), 105 deletions(-) diff --git a/src/chttpd.erl b/src/chttpd.erl index d178604d..a4bb1293 100644 --- a/src/chttpd.erl +++ b/src/chttpd.erl @@ -202,18 +202,17 @@ db_url_handlers() -> {<<"_view_cleanup">>, fun chttpd_view:handle_view_cleanup_req/2}, {<<"_compact">>, fun chttpd_db:handle_compact_req/2}, {<<"_design">>, fun chttpd_db:handle_design_req/2}, - {<<"_view">>, fun chttpd_db:handle_db_view_req/2}, {<<"_temp_view">>, fun chttpd_db:handle_temp_view_req/2}, {<<"_changes">>, fun chttpd_db:handle_changes_req/2} ]. design_url_handlers() -> [ - {<<"_view">>, fun chttpd_view:handle_view_req/2}, - {<<"_show">>, fun chttpd_show:handle_doc_show_req/2}, - {<<"_list">>, fun chttpd_show:handle_view_list_req/2}, - {<<"_update">>, fun chttpd_show:handle_doc_update_req/2}, - {<<"_info">>, fun chttpd_db:handle_design_info_req/2} + {<<"_view">>, fun chttpd_view:handle_view_req/3}, + {<<"_show">>, fun chttpd_show:handle_doc_show_req/3}, + {<<"_list">>, fun chttpd_show:handle_view_list_req/3}, + {<<"_update">>, fun chttpd_show:handle_doc_update_req/3}, + {<<"_info">>, fun chttpd_db:handle_design_info_req/3} ]. % Utilities diff --git a/src/chttpd_db.erl b/src/chttpd_db.erl index 2d2fadc2..0317aed4 100644 --- a/src/chttpd_db.erl +++ b/src/chttpd_db.erl @@ -16,7 +16,7 @@ -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/2, handle_view_cleanup_req/2]). + handle_design_info_req/3, handle_view_cleanup_req/2]). -import(chttpd, [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2, @@ -123,23 +123,30 @@ handle_view_cleanup_req(Req, _) -> chttpd:send_error(Req, 403, Msg). handle_design_req(#httpd{ - path_parts=[_DbName,_Design,_DesName, <<"_",_/binary>> = Action | _Rest], + path_parts=[_DbName, _Design, Name, <<"_",_/binary>> = Action | _Rest], design_url_handlers = DesignUrlHandlers }=Req, Db) -> - Handler = couch_util:get_value(Action, DesignUrlHandlers, fun db_req/2), - Handler(Req, Db); + case fabric:open_doc(Db, <<"_design/", Name/binary>>, []) of + {ok, DDoc} -> + % TODO we'll trigger a badarity here if ddoc attachment starts with "_", + % or if user tries an unknown Action + Handler = couch_util:get_value(Action, DesignUrlHandlers, fun db_req/2), + Handler(Req, Db, DDoc); + Error -> + throw(Error) + end; handle_design_req(Req, Db) -> db_req(Req, Db). -handle_design_info_req(#httpd{method='GET', path_parts=[_,_,Name,_]}=Req, Db) -> - {ok, GroupInfoList} = fabric:get_view_group_info(Db, Name), +handle_design_info_req(#httpd{method='GET'}=Req, Db, #doc{id=Id} = DDoc) -> + {ok, GroupInfoList} = fabric:get_view_group_info(Db, DDoc), send_json(Req, 200, {[ - {name, <<"_design/", Name/binary>>}, + {name, Id}, {view_index, {GroupInfoList}} ]}); -handle_design_info_req(Req, _Db) -> +handle_design_info_req(Req, _Db, _DDoc) -> send_method_not_allowed(Req, "GET"). create_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) -> diff --git a/src/chttpd_show.erl b/src/chttpd_show.erl index 9f4e0d02..f74b9dd6 100644 --- a/src/chttpd_show.erl +++ b/src/chttpd_show.erl @@ -12,38 +12,98 @@ -module(chttpd_show). --export([handle_doc_show_req/2, handle_doc_update_req/2, handle_view_list_req/2, - handle_doc_show/5, handle_view_list/7, start_list_resp/5, - send_list_row/6]). +-export([handle_doc_show_req/3, handle_doc_update_req/3, handle_view_list_req/3, + handle_view_list/7, get_fun_key/3]). -include("chttpd.hrl"). -import(chttpd, [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2, - start_json_response/2,send_chunk/2,send_chunked_error/2, + start_json_response/2,send_chunk/2,last_chunk/1,send_chunked_error/2, start_chunked_response/3, send_error/4]). +% /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) -> + case fabric:open_doc(Db, DocId, [conflicts]) of + {ok, Doc} -> + Doc; + {not_found, _} -> + nil + end. + handle_doc_show_req(#httpd{ - method='GET', - path_parts=[_DbName, _Design, DesignName, _Show, ShowName, DocId] - }=Req, Db) -> - handle_doc_show(Req, DesignName, ShowName, DocId, Db); + path_parts=[_, _, _, _, ShowName, DocId] + }=Req, Db, DDoc) -> + + % open the doc + Doc = maybe_open_doc(Db, DocId), + + % 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=[_DbName, _Design, DesignName, _Show, ShowName] - }=Req, Db) -> - handle_doc_show(Req, DesignName, ShowName, nil, Db); + path_parts=[_, _, _, _, ShowName, DocId|Rest] + }=Req, Db, DDoc) -> + + DocParts = [DocId|Rest], + DocId1 = ?l2b(string:join([?b2l(P)|| P <- DocParts], "/")), -handle_doc_show_req(#httpd{method='GET'}=Req, _Db) -> - send_error(Req, 404, <<"show_error">>, <<"Invalid path.">>); + % open the doc + Doc = maybe_open_doc(Db, DocId1), -handle_doc_show_req(Req, _Db) -> - send_method_not_allowed(Req, "GET,POST,HEAD"). + % 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) -> + 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) -> + % 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}). + +get_fun_key(#doc{body={Props}}, Type, Name) -> + Lang = couch_util:get_value(<<"language">>, Props, <<"javascript">>), + Src = couch_util:get_nested_json_value({Props}, [Type, Name]), + {Lang, Src}. handle_doc_update_req(#httpd{ method = 'PUT', path_parts=[_DbName, _Design, DesignName, _Update, UpdateName, DocId] - }=Req, Db) -> + }=Req, Db, _) -> DesignId = <<"_design/", DesignName/binary>>, #doc{body={Props}} = chttpd_db:couch_doc_open(Db, DesignId, nil, []), Lang = couch_util:get_value(<<"language">>, Props, <<"javascript">>), @@ -58,7 +118,7 @@ handle_doc_update_req(#httpd{ handle_doc_update_req(#httpd{ method = 'POST', path_parts=[_DbName, _Design, DesignName, _Update, UpdateName] - }=Req, Db) -> + }=Req, Db, _) -> DesignId = <<"_design/", DesignName/binary>>, #doc{body={Props}} = chttpd_db:couch_doc_open(Db, DesignId, nil, []), Lang = couch_util:get_value(<<"language">>, Props, <<"javascript">>), @@ -67,57 +127,38 @@ handle_doc_update_req(#httpd{ handle_doc_update_req(#httpd{ path_parts=[_DbName, _Design, _DesignName, _Update, _UpdateName, _DocId] - }=Req, _Db) -> + }=Req, _Db, _) -> send_method_not_allowed(Req, "PUT"); handle_doc_update_req(#httpd{ path_parts=[_DbName, _Design, _DesignName, _Update, _UpdateName] - }=Req, _Db) -> + }=Req, _Db, _) -> send_method_not_allowed(Req, "POST"); -handle_doc_update_req(Req, _Db) -> +handle_doc_update_req(Req, _Db, _) -> send_error(Req, 404, <<"update_error">>, <<"Invalid path.">>). - - - -handle_doc_show(Req, DesignName, ShowName, DocId, Db) -> - DesignId = <<"_design/", DesignName/binary>>, - #doc{body={Props}} = chttpd_db:couch_doc_open(Db, DesignId, nil, []), - Lang = couch_util:get_value(<<"language">>, Props, <<"javascript">>), - ShowSrc = couch_util:get_nested_json_value({Props}, [<<"shows">>, ShowName]), - Doc = case DocId of - nil -> nil; - _ -> - try chttpd_db:couch_doc_open(Db, DocId, nil, [conflicts]) of - FoundDoc -> FoundDoc - catch - _ -> nil - end - end, - send_doc_show_response(Lang, ShowSrc, DocId, Doc, Req, Db). - % view-list request with view and list from same design doc. handle_view_list_req(#httpd{method='GET', - path_parts=[_DbName, _Design, DesignName, _List, ListName, ViewName]}=Req, Db) -> + path_parts=[_DbName, _Design, DesignName, _List, ListName, ViewName]}=Req, Db, _) -> handle_view_list(Req, DesignName, ListName, DesignName, ViewName, Db, nil); % view-list request with view and list from different design docs. handle_view_list_req(#httpd{method='GET', - path_parts=[_DbName, _Design, DesignName, _List, ListName, ViewDesignName, ViewName]}=Req, Db) -> + path_parts=[_DbName, _Design, DesignName, _List, ListName, ViewDesignName, ViewName]}=Req, Db, _) -> handle_view_list(Req, DesignName, ListName, ViewDesignName, ViewName, Db, nil); -handle_view_list_req(#httpd{method='GET'}=Req, _Db) -> +handle_view_list_req(#httpd{method='GET'}=Req, _Db, _) -> send_error(Req, 404, <<"list_error">>, <<"Invalid path.">>); handle_view_list_req(#httpd{method='POST', - path_parts=[_DbName, _Design, DesignName, _List, ListName, ViewName]}=Req, Db) -> + path_parts=[_DbName, _Design, DesignName, _List, ListName, ViewName]}=Req, Db, _) -> ReqBody = chttpd:body(Req), {Props2} = ?JSON_DECODE(ReqBody), Keys = couch_util:get_value(<<"keys">>, Props2, nil), handle_view_list(Req#httpd{req_body=ReqBody}, DesignName, ListName, DesignName, ViewName, Db, Keys); -handle_view_list_req(Req, _Db) -> +handle_view_list_req(Req, _Db, _) -> send_method_not_allowed(Req, "GET,POST,HEAD"). handle_view_list(Req, ListDesignName, ListName, ViewDesignName, ViewName, Db, Keys) -> diff --git a/src/chttpd_view.erl b/src/chttpd_view.erl index 6984f3e6..6d93a5b9 100644 --- a/src/chttpd_view.erl +++ b/src/chttpd_view.erl @@ -13,7 +13,7 @@ -module(chttpd_view). -include("chttpd.hrl"). --export([handle_view_req/2,handle_temp_view_req/2,handle_db_view_req/2]). +-export([handle_view_req/3,handle_temp_view_req/2]). -export([get_stale_type/1, get_reduce_type/1, parse_view_params/3]). -export([make_view_fold_fun/6, finish_view_fold/3, view_row_obj/3]). @@ -82,11 +82,11 @@ extract_view_type(ViewName, [View|Rest], IsReduce) -> end. handle_view_req(#httpd{method='GET', - path_parts=[_Db, _Design, DName, _View, ViewName]}=Req, Db) -> + path_parts=[_Db, _Design, DName, _View, ViewName]}=Req, Db, DDoc) -> design_doc_view(Req, Db, DName, ViewName, nil); handle_view_req(#httpd{method='POST', - path_parts=[_Db, _Design, DName, _View, ViewName]}=Req, Db) -> + path_parts=[_Db, _Design, DName, _View, ViewName]}=Req, Db, DDoc) -> {Fields} = chttpd:json_body_obj(Req), case couch_util:get_value(<<"keys">>, Fields) of Keys when is_list(Keys) -> @@ -95,50 +95,7 @@ handle_view_req(#httpd{method='POST', throw({bad_request, "`keys` body member must be an array."}) end; -handle_view_req(Req, _Db) -> - send_method_not_allowed(Req, "GET,POST,HEAD"). - -handle_db_view_req(#httpd{method='GET', - path_parts=[_Db, _View, DName, ViewName]}=Req, Db) -> - QueryArgs = chttpd_view:parse_view_params(Req, nil, nil), - #view_query_args{ - list = ListName - } = QueryArgs, - ?LOG_DEBUG("ici ~p", [ListName]), - case ListName of - nil -> chttpd_view:design_doc_view(Req, Db, DName, ViewName, nil); - _ -> - chttpd_show:handle_view_list(Req, DName, ListName, DName, ViewName, Db, nil) - end; - -handle_db_view_req(#httpd{method='POST', - path_parts=[_Db, _View, DName, ViewName]}=Req, Db) -> - QueryArgs = chttpd_view:parse_view_params(Req, nil, nil), - #view_query_args{ - list = ListName - } = QueryArgs, - case ListName of - nil -> - {Fields} = chttpd:json_body_obj(Req), - case couch_util:get_value(<<"keys">>, Fields, nil) of - nil -> - Fmt = "POST to view ~p/~p in database ~p with no keys member.", - ?LOG_DEBUG(Fmt, [DName, ViewName, Db]), - chttpd_view:design_doc_view(Req, Db, DName, ViewName, nil); - Keys when is_list(Keys) -> - chttpd_view:design_doc_view(Req, Db, DName, ViewName, Keys); - _ -> - throw({bad_request, "`keys` member must be a array."}) - end; - _ -> - ReqBody = chttpd:body(Req), - {Props2} = ?JSON_DECODE(ReqBody), - Keys = couch_util:get_value(<<"keys">>, Props2, nil), - chttpd_show:handle_view_list(Req#httpd{req_body=ReqBody}, - DName, ListName, DName, ViewName, Db, Keys) - end; - -handle_db_view_req(Req, _Db) -> +handle_view_req(Req, _Db, _DDoc) -> send_method_not_allowed(Req, "GET,POST,HEAD"). handle_temp_view_req(#httpd{method='POST'}=Req, Db) -> -- cgit v1.2.3