diff --git a/etc/couchdb/default.ini.tpl.in b/etc/couchdb/default.ini.tpl.in index 582017fd023..e3600a9a837 100644 --- a/etc/couchdb/default.ini.tpl.in +++ b/etc/couchdb/default.ini.tpl.in @@ -47,7 +47,7 @@ _restart = {couch_httpd_misc_handlers, handle_restart_req} [httpd_db_handlers] _view = {couch_httpd_view, handle_view_req} _slow_view = {couch_httpd_view, handle_slow_view_req} -_form = {couch_httpd_form, handle_form_req} +_show = {couch_httpd_show, handle_doc_show_req} ; The external module takes an optional argument allowing you to narrow it to a ; single script. Otherwise the script name is inferred from the first path section diff --git a/share/server/main.js b/share/server/main.js index 0302721e641..38ca326c8aa 100644 --- a/share/server/main.js +++ b/share/server/main.js @@ -335,7 +335,7 @@ while (cmd = eval(readline())) { print(toJSON(error)); } break; - case "form": + case "show_doc": var funSrc = cmd[1]; var doc = cmd[2]; var req = cmd[3]; @@ -346,7 +346,7 @@ while (cmd = eval(readline())) { } catch (error) { // Available error fields: // message, fileName, lineNumber, stack, name - log("form function raised error: "+error.toString()); + log("doc show function raised error: "+error.toString()); log("stacktrace: "+error.stack); try { print(toJSON(error)); diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js index 3263c532c36..81609f1937a 100644 --- a/share/www/script/couch_tests.js +++ b/share/www/script/couch_tests.js @@ -558,6 +558,11 @@ var tests = { for(var i=0; i') - } - }), - "no-set-etag" : stringFun(function(doc, req) { - return { - headers : { - "Etag" : "skipped" - }, - "body" : "something" - } - }), - "accept-switch" : stringFun(function(doc, req) { - if (req.headers["Accept"].match(/image/)) { + show: { + docs: { + "hello" : stringFun(function() { return { - // a 16x16 px version of the CouchDB logo - "base64" : ["iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAsV", - "BMVEUAAAD////////////////////////5ur3rEBn////////////////wDBL/", - "AADuBAe9EB3IEBz/7+//X1/qBQn2AgP/f3/ilpzsDxfpChDtDhXeCA76AQH/v7", - "/84eLyWV/uc3bJPEf/Dw/uw8bRWmP1h4zxSlD6YGHuQ0f6g4XyQkXvCA36MDH6", - "wMH/z8/yAwX64ODeh47BHiv/Ly/20dLQLTj98PDXWmP/Pz//39/wGyJ7Iy9JAA", - "AADHRSTlMAbw8vf08/bz+Pv19jK/W3AAAAg0lEQVR4Xp3LRQ4DQRBD0QqTm4Y5", - "zMxw/4OleiJlHeUtv2X6RbNO1Uqj9g0RMCuQO0vBIg4vMFeOpCWIWmDOw82fZx", - "vaND1c8OG4vrdOqD8YwgpDYDxRgkSm5rwu0nQVBJuMg++pLXZyr5jnc1BaH4GT", - "LvEliY253nA3pVhQqdPt0f/erJkMGMB8xucAAAAASUVORK5CYII="].join(''), - headers : { - "Content-Type" : "image/png", - "Vary" : "Accept" // we set this for proxy caches - } + body : "Hello World" }; - } else { + }), + "just-name" : stringFun(function(doc, req) { return { - "body" : "accepting text requests", - headers : { - "Content-Type" : "text/html", - "Vary" : "Accept" - } + body : "Just " + doc.name }; - } - }), - "respondWith" : stringFun(function(doc, req) { - registerType("foo", "application/foo","application/x-foo"); - return respondWith(req, { - html : function() { - return { - body:"Ha ha, you said \"" + doc.word + "\"." - }; - }, - xml : function() { - var xml = new XML(''); - // becase Safari can't stand to see that dastardly - // E4X outside of a string. - this.eval('xml.node.@foo = doc.word'); + }), + "req-info" : stringFun(function(doc, req) { + return { + json : req + } + }), + "xml-type" : stringFun(function(doc, req) { + return { + "headers" : { + "Content-Type" : "application/xml" + }, + "body" : new XML('') + } + }), + "no-set-etag" : stringFun(function(doc, req) { + return { + headers : { + "Etag" : "skipped" + }, + "body" : "something" + } + }), + "accept-switch" : stringFun(function(doc, req) { + if (req.headers["Accept"].match(/image/)) { return { - body: xml + // a 16x16 px version of the CouchDB logo + "base64" : +["iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAsV", +"BMVEUAAAD////////////////////////5ur3rEBn////////////////wDBL/", +"AADuBAe9EB3IEBz/7+//X1/qBQn2AgP/f3/ilpzsDxfpChDtDhXeCA76AQH/v7", +"/84eLyWV/uc3bJPEf/Dw/uw8bRWmP1h4zxSlD6YGHuQ0f6g4XyQkXvCA36MDH6", +"wMH/z8/yAwX64ODeh47BHiv/Ly/20dLQLTj98PDXWmP/Pz//39/wGyJ7Iy9JAA", +"AADHRSTlMAbw8vf08/bz+Pv19jK/W3AAAAg0lEQVR4Xp3LRQ4DQRBD0QqTm4Y5", +"zMxw/4OleiJlHeUtv2X6RbNO1Uqj9g0RMCuQO0vBIg4vMFeOpCWIWmDOw82fZx", +"vaND1c8OG4vrdOqD8YwgpDYDxRgkSm5rwu0nQVBJuMg++pLXZyr5jnc1BaH4GT", +"LvEliY253nA3pVhQqdPt0f/erJkMGMB8xucAAAAASUVORK5CYII="].join(''), + headers : { + "Content-Type" : "image/png", + "Vary" : "Accept" // we set this for proxy caches + } }; - }, - foo : function() { + } else { return { - body: "foofoo" + "body" : "accepting text requests", + headers : { + "Content-Type" : "text/html", + "Vary" : "Accept" + } }; - }, - fallback : "html" - }); - }) + } + }), + "respondWith" : stringFun(function(doc, req) { + registerType("foo", "application/foo","application/x-foo"); + return respondWith(req, { + html : function() { + return { + body:"Ha ha, you said \"" + doc.word + "\"." + }; + }, + xml : function() { + var xml = new XML(''); + // becase Safari can't stand to see that dastardly + // E4X outside of a string. + this.eval('xml.node.@foo = doc.word'); + return { + body: xml + }; + }, + foo : function() { + return { + body: "foofoo" + }; + }, + fallback : "html" + }); + }) + } } }; T(db.save(designDoc).ok); @@ -2289,35 +2297,35 @@ var tests = { T(resp.ok); var docid = resp.id; - // form error - var xhr = CouchDB.request("GET", "/test_suite_db/_form/"); + // show error + var xhr = CouchDB.request("GET", "/test_suite_db/_show/"); T(xhr.status == 404); T(JSON.parse(xhr.responseText).reason == "Invalid path."); // hello template world - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/hello/"+docid); + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/hello/"+docid); T(xhr.responseText == "Hello World"); - // form with doc - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/just-name/"+docid); + // show with doc + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid); T(xhr.responseText == "Just Rusty"); - // form with missing doc - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/just-name/missingdoc"); + // show with missing doc + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/missingdoc"); T(xhr.status == 404); var resp = JSON.parse(xhr.responseText); T(resp.error == "not_found"); T(resp.reason == "missing"); // missing design doc - xhr = CouchDB.request("GET", "/test_suite_db/_form/missingdoc/just-name/"+docid); + xhr = CouchDB.request("GET", "/test_suite_db/_show/missingdoc/just-name/"+docid); T(xhr.status == 404); var resp = JSON.parse(xhr.responseText); T(resp.error == "not_found"); T(resp.reason == "missing_design_doc"); // query parameters - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/req-info/"+docid+"?foo=bar", { + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/req-info/"+docid+"?foo=bar", { headers: { "Accept": "text/html;text/plain;*/*", "X-Foo" : "bar" @@ -2331,21 +2339,21 @@ var tests = { T(equals(resp.info.db_name, "test_suite_db")); // returning a content-type - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/xml-type/"+docid); + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/xml-type/"+docid); T("application/xml" == xhr.getResponseHeader("Content-Type")); T("Accept" == xhr.getResponseHeader("Vary")); // accept header switching // different mime has different etag - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/accept-switch/"+docid, { + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/accept-switch/"+docid, { headers: {"Accept": "text/html;text/plain;*/*"} }); T("text/html" == xhr.getResponseHeader("Content-Type")); T("Accept" == xhr.getResponseHeader("Vary")); var etag = xhr.getResponseHeader("etag"); - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/accept-switch/"+docid, { + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/accept-switch/"+docid, { headers: {"Accept": "image/png;*/*"} }); T(xhr.responseText.match(/PNG/)) @@ -2354,12 +2362,12 @@ var tests = { T(etag2 != etag); // proper etags - // form with doc - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/just-name/"+docid); + // show with doc + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid); // extract the ETag header values etag = xhr.getResponseHeader("etag"); // get again with etag in request - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/just-name/"+docid, { + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { headers: {"if-none-match": etag} }); // should be 304 @@ -2370,7 +2378,7 @@ var tests = { resp = db.save(doc); T(resp.ok); // req with same etag - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/just-name/"+docid, { + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { headers: {"if-none-match": etag} }); // status is 200 @@ -2378,7 +2386,7 @@ var tests = { // get new etag and request again etag = xhr.getResponseHeader("etag"); - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/just-name/"+docid, { + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { headers: {"if-none-match": etag} }); // should be 304 @@ -2388,21 +2396,21 @@ var tests = { designDoc.isChanged = true; T(db.save(designDoc).ok); - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/just-name/"+docid, { + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { headers: {"if-none-match": etag} }); // should be 304 T(xhr.status == 304); // update design doc function - designDoc.forms["just-name"] = (function(doc, req) { + designDoc.show.docs["just-name"] = (function(doc, req) { return { body : "Just old " + doc.name }; }).toString(); T(db.save(designDoc).ok); - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/just-name/"+docid, { + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { headers: {"if-none-match": etag} }); // status is 200 @@ -2410,13 +2418,13 @@ var tests = { // JS can't set etag - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/no-set-etag/"+docid); + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/no-set-etag/"+docid); // extract the ETag header values etag = xhr.getResponseHeader("etag"); T(etag != "skipped") // test the respondWith mime matcher - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/respondWith/"+docid, { + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, { headers: { "Accept": 'text/html,application/atom+xml; q=0.9' } @@ -2425,7 +2433,7 @@ var tests = { T(xhr.responseText == "Ha ha, you said \"plankton\"."); // now with xml - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/respondWith/"+docid, { + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, { headers: { "Accept": 'application/xml' } @@ -2435,7 +2443,7 @@ var tests = { T(xhr.responseText.match(/plankton/)); // registering types works - xhr = CouchDB.request("GET", "/test_suite_db/_form/template/respondWith/"+docid, { + xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, { headers: { "Accept": "application/x-foo" } diff --git a/src/couchdb/Makefile.am b/src/couchdb/Makefile.am index 7fb2ec68056..8e280c702e3 100644 --- a/src/couchdb/Makefile.am +++ b/src/couchdb/Makefile.am @@ -55,7 +55,7 @@ source_files = \ couch_httpd.erl \ couch_httpd_db.erl \ couch_httpd_external.erl \ - couch_httpd_form.erl \ + couch_httpd_show.erl \ couch_httpd_view.erl \ couch_httpd_misc_handlers.erl \ couch_key_tree.erl \ @@ -91,7 +91,7 @@ compiled_files = \ couch_httpd.beam \ couch_httpd_db.beam \ couch_httpd_external.beam \ - couch_httpd_form.beam \ + couch_httpd_show.beam \ couch_httpd_view.beam \ couch_httpd_misc_handlers.beam \ couch_key_tree.beam \ diff --git a/src/couchdb/couch_httpd_form.erl b/src/couchdb/couch_httpd_show.erl similarity index 72% rename from src/couchdb/couch_httpd_form.erl rename to src/couchdb/couch_httpd_show.erl index f4fa2c182de..d4b4997b6b6 100644 --- a/src/couchdb/couch_httpd_form.erl +++ b/src/couchdb/couch_httpd_show.erl @@ -10,9 +10,9 @@ % License for the specific language governing permissions and limitations under % the License. --module(couch_httpd_form). +-module(couch_httpd_show). --export([handle_form_req/2]). +-export([handle_doc_show_req/2]). -include("couch_db.hrl"). @@ -22,7 +22,7 @@ start_json_response/2,send_chunk/2,end_json_response/1, start_chunked_response/3, send_error/4]). -handle_form_req(#httpd{method='GET',path_parts=[_, _, DesignName, FormName, Docid]}=Req, Db) -> +handle_doc_show_req(#httpd{method='GET',path_parts=[_, _, DesignName, ShowName, Docid]}=Req, Db) -> DesignId = <<"_design/", DesignName/binary>>, % Anyway we can dry up this error handling? case (catch couch_httpd_db:couch_doc_open(Db, DesignId, [], [])) of @@ -33,40 +33,46 @@ handle_form_req(#httpd{method='GET',path_parts=[_, _, DesignName, FormName, Doci DesignDoc -> #doc{body={Props}} = DesignDoc, Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>), - case proplists:get_value(<<"forms">>, Props, nil) of - {Forms} -> - case proplists:get_value(FormName, Forms, nil) of + case proplists:get_value(<<"show">>, Props, nil) of + {DocAndViews} -> + case proplists:get_value(<<"docs">>, DocAndViews, nil) of nil -> - throw({not_found, missing_form}); - FormSrc -> - case (catch couch_httpd_db:couch_doc_open(Db, Docid, [], [])) of - {not_found, missing} -> - throw({not_found, missing}); - {not_found, deleted} -> - throw({not_found, deleted}); - Doc -> - % ok we have everythign we need. let's make it happen. - send_form_response(Lang, FormSrc, Doc, Req, Db) + throw({not_found, missing_show_docs}); + {DocShows} -> + case proplists:get_value(ShowName, DocShows, nil) of + nil -> + throw({not_found, missing_show_doc_function}); + ShowSrc -> + case (catch couch_httpd_db:couch_doc_open( + Db, Docid, [], [])) of + {not_found, missing} -> + throw({not_found, missing}); + {not_found, deleted} -> + throw({not_found, deleted}); + Doc -> + % ok we have everythign we need. let's make it happen. + send_doc_show_response(Lang, ShowSrc, Doc, Req, Db) + end end end; nil -> - throw({not_found, missing_form}) + throw({not_found, missing_show}) end end; -handle_form_req(#httpd{method='GET'}=Req, _Db) -> +handle_doc_show_req(#httpd{method='GET'}=Req, _Db) -> send_error(Req, 404, <<"form_error">>, <<"Invalid path.">>); -handle_form_req(Req, _Db) -> +handle_doc_show_req(Req, _Db) -> send_method_not_allowed(Req, "GET,HEAD"). -send_form_response(Lang, FormSrc, #doc{revs=[DocRev|_]}=Doc, #httpd{mochi_req=MReq}=Req, Db) -> +send_doc_show_response(Lang, ShowSrc, #doc{revs=[DocRev|_]}=Doc, #httpd{mochi_req=MReq}=Req, Db) -> % make a term with etag-effecting Req components, but not always changing ones. Headers = MReq:get(headers), Hlist = mochiweb_headers:to_list(Headers), Accept = proplists:get_value('Accept', Hlist), - <> = erlang:md5(term_to_binary({Lang, FormSrc, DocRev, Accept})), + <> = erlang:md5(term_to_binary({Lang, ShowSrc, DocRev, Accept})), CurrentEtag = list_to_binary("\"" ++ lists:flatten(io_lib:format("form_~.36B",[SigInt])) ++ "\""), EtagsToMatch = string:tokens( couch_httpd:header_value(Req, "If-None-Match", ""), ", "), @@ -77,7 +83,7 @@ send_form_response(Lang, FormSrc, #doc{revs=[DocRev|_]}=Doc, #httpd{mochi_req=MR couch_httpd:send_response(Req, 304, [{"Etag", CurrentEtag}], <<>>); false -> % Run the external form renderer. - {JsonResponse} = couch_query_servers:render_doc_form(Lang, FormSrc, Doc, Req, Db), + {JsonResponse} = couch_query_servers:render_doc_show(Lang, ShowSrc, Doc, Req, Db), % Here we embark on the delicate task of replacing or creating the % headers on the JsonResponse object. We need to control the Etag and % Vary headers. If the external function controls the Etag, we'd have to diff --git a/src/couchdb/couch_query_servers.erl b/src/couchdb/couch_query_servers.erl index c4ed5c8bf50..8d473381139 100644 --- a/src/couchdb/couch_query_servers.erl +++ b/src/couchdb/couch_query_servers.erl @@ -17,7 +17,7 @@ -export([init/1, terminate/2, handle_call/3, handle_cast/2, handle_info/2,code_change/3,stop/0]). -export([start_doc_map/2, map_docs/2, stop_doc_map/1]). --export([reduce/3, rereduce/3,validate_doc_update/5,render_doc_form/5]). +-export([reduce/3, rereduce/3,validate_doc_update/5,render_doc_show/5]). % -export([test/0]). -include("couch_db.hrl"). @@ -122,11 +122,12 @@ validate_doc_update(Lang, FunSrc, EditDoc, DiskDoc, Ctx) -> ok = ret_os_process(Lang, Pid) end. -render_doc_form(Lang, FormSrc, Doc, Req, Db) -> +render_doc_show(Lang, ShowSrc, Doc, Req, Db) -> Pid = get_os_process(Lang), JsonDoc = couch_doc:to_json_obj(Doc, [revs]), JsonReq = couch_httpd_external:json_req_obj(Req, Db), - try couch_os_process:prompt(Pid, [<<"form">>, FormSrc, JsonDoc, JsonReq]) of + try couch_os_process:prompt(Pid, + [<<"show_doc">>, ShowSrc, JsonDoc, JsonReq]) of FormResp -> FormResp after