diff options
| -rw-r--r-- | CHANGES | 7 | ||||
| -rw-r--r-- | etc/couchdb/default.ini.tpl.in | 9 | ||||
| -rw-r--r-- | share/www/script/couch.js | 9 | ||||
| -rw-r--r-- | share/www/script/jquery.couch.js | 7 | ||||
| -rw-r--r-- | share/www/script/test/etags_views.js | 8 | ||||
| -rw-r--r-- | share/www/script/test/list_views.js | 67 | ||||
| -rw-r--r-- | share/www/script/test/show_documents.js | 115 | ||||
| -rw-r--r-- | src/couchdb/couch_db.hrl | 3 | ||||
| -rw-r--r-- | src/couchdb/couch_httpd.erl | 16 | ||||
| -rw-r--r-- | src/couchdb/couch_httpd_db.erl | 12 | ||||
| -rw-r--r-- | src/couchdb/couch_httpd_show.erl | 10 | ||||
| -rw-r--r-- | src/couchdb/couch_httpd_view.erl | 10 | 
12 files changed, 155 insertions, 118 deletions
@@ -17,6 +17,13 @@ Futon Utility Client:   * Added pagination to the database listing page.   * Implemented attachment uploading from the document page. +Design Document Resource Paths: + + * Added httpd_design_handlers config section. + * Moved _view to httpd_design_handlers. + * Added ability to render documents as non-JSON content-types with _show and  +   _list functions, which are also httpd_design_handlers. +  Version 0.8.1-incubating  ------------------------ diff --git a/etc/couchdb/default.ini.tpl.in b/etc/couchdb/default.ini.tpl.in index 8deca3cf..dd63db0b 100644 --- a/etc/couchdb/default.ini.tpl.in +++ b/etc/couchdb/default.ini.tpl.in @@ -52,13 +52,16 @@ _restart = {couch_httpd_misc_handlers, handle_restart_req}  _stats = {couch_httpd_stats_handlers, handle_stats_req}  [httpd_db_handlers] -_view = {couch_httpd_view, handle_view_req} +_design = {couch_httpd_db, handle_design_req}  _temp_view = {couch_httpd_view, handle_temp_view_req} -_show = {couch_httpd_show, handle_doc_show_req} -_list = {couch_httpd_show, handle_view_list_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   ; after _external's own path.  ; _mypath = {couch_httpd_external, handle_external_req, <<"mykey">>}  ; _external = {couch_httpd_external, handle_external_req} + +[httpd_design_handlers] +_view = {couch_httpd_view, handle_view_req} +_show = {couch_httpd_show, handle_doc_show_req} +_list = {couch_httpd_show, handle_view_list_req} diff --git a/share/www/script/couch.js b/share/www/script/couch.js index 8a0d3d23..821fb694 100644 --- a/share/www/script/couch.js +++ b/share/www/script/couch.js @@ -142,12 +142,13 @@ function CouchDB(name, httpHeaders) {    }    this.view = function(viewname, options, keys) { +    var viewParts = viewname.split('/'); +    var viewPath = this.uri + "_design/" + viewParts[0] + "/_view/"  +        + viewParts[1] + encodeOptions(options);      if(!keys) { -      this.last_req = this.request("GET", this.uri + "_view/" + -          viewname + encodeOptions(options));       +      this.last_req = this.request("GET", viewPath);            } else { -      this.last_req = this.request("POST", this.uri + "_view/" +  -        viewname + encodeOptions(options), { +      this.last_req = this.request("POST", viewPath, {          headers: {"Content-Type": "application/json"},          body: JSON.stringify({keys:keys})        });       diff --git a/share/www/script/jquery.couch.js b/share/www/script/jquery.couch.js index 0c55aa49..ace9a8a6 100644 --- a/share/www/script/jquery.couch.js +++ b/share/www/script/jquery.couch.js @@ -191,9 +191,9 @@                        appName = appName.join('/');                        index = ddoc.couchapp && ddoc.couchapp.index;                        if (index) { -                        appPath = ['', name, index[0], appName, index[1]].join('/'); +                        appPath = ['', name, ddoc._id, index].join('/');                        } else if (ddoc._attachments && ddoc._attachments["index.html"]) { -                        appPath = ['', name, '_design', appName, "index.html"].join('/'); +                        appPath = ['', name, ddoc._id, "index.html"].join('/');                        }                        if (appPath) options.eachApp(appName, appPath, ddoc);                      } @@ -298,8 +298,9 @@          },          view: function(name, options) {            options = options || {}; +          name = name.split('/');            $.ajax({ -            type: "GET", url: this.uri + "_view/" + name + encodeOptions(options), +            type: "GET", url: this.uri + "_design/" + name[0] + "/_view/" + name[1] + encodeOptions(options),              dataType: "json",              complete: function(req) {                var resp = $.httpData(req, "json"); diff --git a/share/www/script/test/etags_views.js b/share/www/script/test/etags_views.js index 808a6829..cb38ccf6 100644 --- a/share/www/script/test/etags_views.js +++ b/share/www/script/test/etags_views.js @@ -46,20 +46,20 @@ couchTests.etags_views = function(debug) {    T(saveResult.ok);    // verify get w/Etag on map view -  xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/basicView"); +  xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView");    T(xhr.status == 200);    var etag = xhr.getResponseHeader("etag"); -  xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/basicView", { +  xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/basicView", {      headers: {"if-none-match": etag}    });    T(xhr.status == 304);    // TODO GET with keys (when that is available)    // reduce view -  xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/withReduce"); +  xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce");    T(xhr.status == 200);    var etag = xhr.getResponseHeader("etag"); -  xhr = CouchDB.request("GET", "/test_suite_db/_view/etags/withReduce", { +  xhr = CouchDB.request("GET", "/test_suite_db/_design/etags/_view/withReduce", {      headers: {"if-none-match": etag}    });    T(xhr.status == 304); diff --git a/share/www/script/test/list_views.js b/share/www/script/test/list_views.js index 3e5e8db4..055ef51c 100644 --- a/share/www/script/test/list_views.js +++ b/share/www/script/test/list_views.js @@ -144,8 +144,8 @@ couchTests.list_views = function(debug) {    T(view.total_rows == 10);    // standard get -  var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView"); -  T(xhr.status == 200); +  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView"); +  T(xhr.status == 200, "standard get should be 200");    T(/Total Rows/.test(xhr.responseText));    T(/Key: 1/.test(xhr.responseText));    T(/LineNo: 0/.test(xhr.responseText)); @@ -159,14 +159,14 @@ couchTests.list_views = function(debug) {    // test that etags are available    var etag = xhr.getResponseHeader("etag"); -  xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView", { +  xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView", {      headers: {"if-none-match": etag}    });    T(xhr.status == 304);    // get with query params -  var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView?startkey=3"); -  T(xhr.status == 200); +  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView?startkey=3"); +  T(xhr.status == 200, "with query params");    T(/Total Rows/.test(xhr.responseText));    T(!(/Key: 1/.test(xhr.responseText)));    T(/FirstKey: 3/.test(xhr.responseText)); @@ -174,33 +174,33 @@ couchTests.list_views = function(debug) {    // with 0 rows -  var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/basicView?startkey=30"); -  T(xhr.status == 200); +  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/basicView?startkey=30"); +  T(xhr.status == 200, "0 rows");    T(/Total Rows/.test(xhr.responseText));    T(/Offset: null/.test(xhr.responseText));    // reduce with 0 rows -  var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/withReduce?startkey=30"); -  T(xhr.status == 200); +  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?startkey=30"); +  T(xhr.status == 200, "reduce 0 rows");    T(/Total Rows/.test(xhr.responseText));    T(/Offset: undefined/.test(xhr.responseText));    // when there is a reduce present, but not used -  var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/withReduce?reduce=false"); -  T(xhr.status == 200); +  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?reduce=false"); +  T(xhr.status == 200, "reduce false");    T(/Total Rows/.test(xhr.responseText));    T(/Key: 1/.test(xhr.responseText));    // when there is a reduce present, and used -  xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/withReduce?group=true"); -  T(xhr.status == 200); +  xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?group=true"); +  T(xhr.status == 200, "group reduce");    T(/Key: 1/.test(xhr.responseText));    // there should be etags on reduce as well    var etag = xhr.getResponseHeader("etag");    T(etag, "Etags should be served with reduce lists"); -  xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/withReduce?group=true", { +  xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?group=true", {      headers: {"if-none-match": etag}    });    T(xhr.status == 304); @@ -210,13 +210,13 @@ couchTests.list_views = function(debug) {    var saveResult = db.bulkSave(docs);    T(saveResult.ok); -  xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/simpleForm/withReduce?group=true", { +  xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?group=true", {      headers: {"if-none-match": etag}    }); -  T(xhr.status == 200); +  T(xhr.status == 200, "reduce etag");    // with accept headers for HTML -  xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/acceptSwitch/basicView", { +  xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/acceptSwitch/basicView", {      headers: {        "Accept": 'text/html'      } @@ -226,7 +226,7 @@ couchTests.list_views = function(debug) {    T(xhr.responseText.match(/Value/));    // now with xml -  xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/acceptSwitch/basicView", { +  xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/acceptSwitch/basicView", {      headers: {        "Accept": 'application/xml'      } @@ -236,49 +236,40 @@ couchTests.list_views = function(debug) {    T(xhr.responseText.match(/entry/));    // now with extra qs params -  var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/qsParams/basicView?foo=blam"); +  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/qsParams/basicView?foo=blam");    T(xhr.responseText.match(/blam/));    // aborting iteration -  var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/stopIter/basicView"); +  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter/basicView");    T(xhr.responseText.match(/^head 0 1 2 tail$/)); -  xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/stopIter2/basicView"); +  xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/basicView");    T(xhr.responseText.match(/^head 0 1 2 tail$/));    // aborting iteration with reduce -  var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/stopIter/withReduce?group=true"); +  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter/withReduce?group=true");    T(xhr.responseText.match(/^head 0 1 2 tail$/)); -  xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/stopIter2/withReduce?group=true"); +  xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/stopIter2/withReduce?group=true");    T(xhr.responseText.match(/^head 0 1 2 tail$/));    // empty list -  var xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/emptyList/basicView"); +  var xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/emptyList/basicView");    T(xhr.responseText.match(/^$/)); -  xhr = CouchDB.request("GET", "/test_suite_db/_list/lists/emptyList/withReduce?group=true"); +  xhr = CouchDB.request("GET", "/test_suite_db/_design/lists/_list/emptyList/withReduce?group=true");    T(xhr.responseText.match(/^$/));    // multi-key fetch -  var xhr = CouchDB.request("POST", "/test_suite_db/_list/lists/simpleForm/basicView", { +  var xhr = CouchDB.request("POST", "/test_suite_db/_design/lists/_list/simpleForm/basicView", {      body: '{"keys":[2,4,5,7]}'    }); -  T(xhr.status == 200); +  T(xhr.status == 200, "multi key");    T(/Total Rows/.test(xhr.responseText));    T(!(/Key: 1/.test(xhr.responseText)));    T(/Key: 2/.test(xhr.responseText));    T(/FirstKey: 2/.test(xhr.responseText));    T(/LastKey: 7/.test(xhr.responseText)); -  xhr = CouchDB.request("POST", "/test_suite_db/_list/lists/simpleForm/withReduce?group=true", { -    body: '{"keys":[2,4,5,7]}' -  }); -  T(xhr.status == 200); -  T(/Total Rows/.test(xhr.responseText)); -  T(!(/Key: 1/.test(xhr.responseText))); -  T(/Key: 2/.test(xhr.responseText)); -  T(/FirstKey: 2/.test(xhr.responseText)); -  T(/LastKey: 7/.test(xhr.responseText)); -   +    // no multi-key fetch allowed when group=false -  xhr = CouchDB.request("POST", "/test_suite_db/_list/lists/simpleForm/withReduce?group=false", { +  xhr = CouchDB.request("POST", "/test_suite_db/_design/lists/_list/simpleForm/withReduce?group=false", {      body: '{"keys":[2,4,5,7]}'    });    T(xhr.status == 400); diff --git a/share/www/script/test/show_documents.js b/share/www/script/test/show_documents.js index b9a66a1d..ed1ab3b3 100644 --- a/share/www/script/test/show_documents.js +++ b/share/www/script/test/show_documents.js @@ -33,9 +33,16 @@ couchTests.show_documents = function(debug) {          }        }),        "just-name" : stringFun(function(doc, req) { -        return { -          body : "Just " + doc.name -        }; +        if (doc) { +          return { +            body : "Just " + doc.name +          }; +        } else { +          return { +            body : "No such doc", +            code : 404 +          }; +        }        }),        "req-info" : stringFun(function(doc, req) {          return { @@ -123,68 +130,72 @@ couchTests.show_documents = function(debug) {    var docid = resp.id;    // show error -  var xhr = CouchDB.request("GET", "/test_suite_db/_show/"); -  T(xhr.status == 404); +  var xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/"); +  T(xhr.status == 404, 'Should be missing');    T(JSON.parse(xhr.responseText).reason == "Invalid path.");    // hello template world -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/hello/"+docid); +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/hello/"+docid);    T(xhr.responseText == "Hello World");    // hello template world (no docid) -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/hello/"); +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/hello");    T(xhr.responseText == "Empty World");    // // hello template world (non-existing docid) -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/hello/nonExistingDoc"); +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/hello/nonExistingDoc");    T(xhr.responseText == "New World");    // show with doc -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid); +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/"+docid);    T(xhr.responseText == "Just Rusty"); +  // show with missing doc +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/missingdoc"); +  console.log(xhr) +  T(xhr.status == 404, 'Doc should be missing'); +  T(xhr.responseText == "No such doc");    // show with missing func -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/missing/"+docid); -  T(xhr.status == 404); - +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/missing/"+docid); +  T(xhr.status == 404, "function is missing"); +      // missing design doc -  xhr = CouchDB.request("GET", "/test_suite_db/_show/missingdoc/just-name/"+docid); +  xhr = CouchDB.request("GET", "/test_suite_db/_design/missingddoc/_show/just-name/"+docid);    T(xhr.status == 404);    var resp = JSON.parse(xhr.responseText);    T(resp.error == "not_found");    // query parameters -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/req-info/"+docid+"?foo=bar", { -   headers: { -     "Accept": "text/html;text/plain;*/*", -     "X-Foo" : "bar" -   } +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/req-info/"+docid+"?foo=bar", { +    headers: { +      "Accept": "text/html;text/plain;*/*", +      "X-Foo" : "bar" +    }    });    var resp = JSON.parse(xhr.responseText);    T(equals(resp.headers["X-Foo"], "bar"));    T(equals(resp.query, {foo:"bar"}));    T(equals(resp.verb, "GET")); -  T(equals(resp.path[4], docid)); +  T(equals(resp.path[5], docid));    T(equals(resp.info.db_name, "test_suite_db"));    // returning a content-type -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/xml-type/"+docid); +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/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/_show/template/accept-switch/"+docid, { -   headers: {"Accept": "text/html;text/plain;*/*"} +  // different mime has different etag   +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/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/_show/template/accept-switch/"+docid, { -   headers: {"Accept": "image/png;*/*"} +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/accept-switch/"+docid, { +    headers: {"Accept": "image/png;*/*"}    });    T(xhr.responseText.match(/PNG/))    T("image/png" == xhr.getResponseHeader("Content-Type")); @@ -193,12 +204,12 @@ couchTests.show_documents = function(debug) {    // proper etags    // show with doc -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid); +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/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/_show/template/just-name/"+docid, { -   headers: {"if-none-match": etag} +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/"+docid, { +    headers: {"if-none-match": etag}    });    // should be 304    T(xhr.status == 304);     @@ -208,16 +219,16 @@ couchTests.show_documents = function(debug) {    resp = db.save(doc);    T(resp.ok);    // req with same etag -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { -   headers: {"if-none-match": etag} +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/"+docid, { +    headers: {"if-none-match": etag}    });    // status is 200        T(xhr.status == 200);    // get new etag and request again    etag = xhr.getResponseHeader("etag"); -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { -   headers: {"if-none-match": etag} +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/"+docid, { +    headers: {"if-none-match": etag}    });    // should be 304    T(xhr.status == 304); @@ -225,9 +236,9 @@ couchTests.show_documents = function(debug) {    // update design doc (but not function)    designDoc.isChanged = true;    T(db.save(designDoc).ok); - -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { -   headers: {"if-none-match": etag} +   +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/"+docid, { +    headers: {"if-none-match": etag}    });    // should be 304    T(xhr.status == 304); @@ -240,49 +251,49 @@ couchTests.show_documents = function(debug) {    }).toString();    T(db.save(designDoc).ok); -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/just-name/"+docid, { -   headers: {"if-none-match": etag} +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/just-name/"+docid, { +    headers: {"if-none-match": etag}    });    // status is 200        T(xhr.status == 200);    // JS can't set etag -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/no-set-etag/"+docid); +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/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/_show/template/respondWith/"+docid, { -   headers: { -     "Accept": 'text/html,application/atom+xml; q=0.9' -   } +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/respondWith/"+docid, { +    headers: { +      "Accept": 'text/html,application/atom+xml; q=0.9' +    }    });    T(xhr.getResponseHeader("Content-Type") == "text/html");    T(xhr.responseText == "Ha ha, you said \"plankton\".");    // now with xml -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, { -   headers: { -     "Accept": 'application/xml' -   } +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/respondWith/"+docid, { +    headers: { +      "Accept": 'application/xml' +    }    });    T(xhr.getResponseHeader("Content-Type") == "application/xml");    T(xhr.responseText.match(/node/));    T(xhr.responseText.match(/plankton/));    // registering types works -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, { -   headers: { -     "Accept": "application/x-foo" -   } +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/respondWith/"+docid, { +    headers: { +      "Accept": "application/x-foo" +    }    });    T(xhr.getResponseHeader("Content-Type") == "application/x-foo");    T(xhr.responseText.match(/foofoo/));    // test the respondWith mime matcher without -  xhr = CouchDB.request("GET", "/test_suite_db/_show/template/respondWith/"+docid, { +  xhr = CouchDB.request("GET", "/test_suite_db/_design/template/_show/respondWith/"+docid, {     headers: {       "Accept": 'text/html,application/atom+xml; q=0.9'     } diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl index aa97a19c..026afe14 100644 --- a/src/couchdb/couch_db.hrl +++ b/src/couchdb/couch_db.hrl @@ -61,7 +61,8 @@      path_parts,      db_url_handlers,      user_ctx, -    req_body = undefined +    req_body = undefined, +    design_url_handlers      }). diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl index 44ae0bb1..48ff403b 100644 --- a/src/couchdb/couch_httpd.erl +++ b/src/couchdb/couch_httpd.erl @@ -13,7 +13,7 @@  -module(couch_httpd).  -include("couch_db.hrl"). --export([start_link/0, stop/0, handle_request/3]). +-export([start_link/0, stop/0, handle_request/4]).  -export([header_value/2,header_value/3,qs_value/2,qs_value/3,qs/1,path/1,absolute_uri/2]).  -export([verify_is_server_admin/1,unquote/1,quote/1,recv/2,recv_chunked/4]). @@ -44,11 +44,18 @@ start_link() ->          fun({UrlKey, SpecStr}) ->              {?l2b(UrlKey), make_arity_2_fun(SpecStr)}          end, couch_config:get("httpd_db_handlers")), + +    DesignUrlHandlersList = lists:map( +        fun({UrlKey, SpecStr}) -> +            {?l2b(UrlKey), make_arity_2_fun(SpecStr)} +        end, couch_config:get("httpd_design_handlers")), +              UrlHandlers = dict:from_list(UrlHandlersList),      DbUrlHandlers = dict:from_list(DbUrlHandlersList), +    DesignUrlHandlers = dict:from_list(DesignUrlHandlersList),      Loop = fun(Req)->              apply(?MODULE, handle_request, -                    [Req, UrlHandlers, DbUrlHandlers]) +                    [Req, UrlHandlers, DbUrlHandlers, DesignUrlHandlers])          end,      % and off we go @@ -101,7 +108,7 @@ stop() ->      mochiweb_http:stop(?MODULE). -handle_request(MochiReq, UrlHandlers, DbUrlHandlers) -> +handle_request(MochiReq, UrlHandlers, DbUrlHandlers, DesignUrlHandlers) ->      statistics(runtime), % prepare request_time counter, see end of function      AuthenticationFun = make_arity_1_fun(              couch_config:get("httpd", "authentication_handler")), @@ -147,7 +154,8 @@ handle_request(MochiReq, UrlHandlers, DbUrlHandlers) ->          method = Method,          path_parts = [list_to_binary(couch_httpd:unquote(Part))                  || Part <- string:tokens(Path, "/")], -        db_url_handlers = DbUrlHandlers +        db_url_handlers = DbUrlHandlers, +        design_url_handlers = DesignUrlHandlers          },      DefaultFun = fun couch_httpd_db:handle_request/1,      HandlerFun = couch_util:dict_find(HandlerKey, UrlHandlers, DefaultFun), diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 005e32d1..75022cd3 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -13,7 +13,7 @@  -module(couch_httpd_db).  -include("couch_db.hrl"). --export([handle_request/1, db_req/2, couch_doc_open/4]). +-export([handle_request/1, handle_design_req/2, db_req/2, couch_doc_open/4]).  -import(couch_httpd,      [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2, @@ -41,6 +41,16 @@ handle_request(#httpd{path_parts=[DbName|RestParts],method=Method,          do_db_req(Req, Handler)      end. +handle_design_req(#httpd{ +        path_parts=[_DbName,_Design,_DesName, <<"_",_/binary>> = Action | _Rest], +        design_url_handlers = DesignUrlHandlers +    }=Req, Db) -> +    Handler = couch_util:dict_find(Action, DesignUrlHandlers, fun db_req/2), +    Handler(Req, Db); +     +handle_design_req(Req, Db) -> +    db_req(Req, Db). +  create_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) ->      ok = couch_httpd:verify_is_server_admin(Req),      case couch_server:create(DbName, [{user_ctx, UserCtx}]) of diff --git a/src/couchdb/couch_httpd_show.erl b/src/couchdb/couch_httpd_show.erl index 5a03d9de..7b6f2832 100644 --- a/src/couchdb/couch_httpd_show.erl +++ b/src/couchdb/couch_httpd_show.erl @@ -24,7 +24,7 @@  handle_doc_show_req(#httpd{          method='GET', -        path_parts=[_, _, DesignName, ShowName, DocId] +        path_parts=[_DbName, _Design, DesignName, _Show, ShowName, DocId]      }=Req, Db) ->      DesignId = <<"_design/", DesignName/binary>>,      #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, [], []), @@ -39,7 +39,7 @@ handle_doc_show_req(#httpd{  handle_doc_show_req(#httpd{          method='GET', -        path_parts=[_, _, DesignName, ShowName] +        path_parts=[_DbName, _Design, DesignName, _Show, ShowName]      }=Req, Db) ->      DesignId = <<"_design/", DesignName/binary>>,      #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, [], []), @@ -53,7 +53,8 @@ handle_doc_show_req(#httpd{method='GET'}=Req, _Db) ->  handle_doc_show_req(Req, _Db) ->      send_method_not_allowed(Req, "GET,HEAD"). -handle_view_list_req(#httpd{method='GET',path_parts=[_, _, DesignName, ListName, ViewName]}=Req, Db) -> +handle_view_list_req(#httpd{method='GET', +        path_parts=[_DbName, _Design, DesignName, _List, ListName, ViewName]}=Req, Db) ->      DesignId = <<"_design/", DesignName/binary>>,      #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, [], []),      Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>), @@ -63,7 +64,8 @@ handle_view_list_req(#httpd{method='GET',path_parts=[_, _, DesignName, ListName,  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=[_, _, DesignName, ListName, ViewName]}=Req, Db) -> +handle_view_list_req(#httpd{method='POST', +        path_parts=[_DbName, _Design, DesignName, _List, ListName, ViewName]}=Req, Db) ->      DesignId = <<"_design/", DesignName/binary>>,      #doc{body={Props}} = couch_httpd_db:couch_doc_open(Db, DesignId, [], []),      Lang = proplists:get_value(<<"language">>, Props, <<"javascript">>), diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl index 6b9befe1..322cf945 100644 --- a/src/couchdb/couch_httpd_view.erl +++ b/src/couchdb/couch_httpd_view.erl @@ -49,13 +49,15 @@ design_doc_view(Req, Db, Id, ViewName, Keys) ->      couch_stats_collector:increment({httpd, view_reads}),      Result. -handle_view_req(#httpd{method='GET',path_parts=[_,_, Id, ViewName]}=Req, Db) -> -    design_doc_view(Req, Db, Id, ViewName, nil); +handle_view_req(#httpd{method='GET', +        path_parts=[_Db, _Design, DName, _View, ViewName]}=Req, Db) -> +    design_doc_view(Req, Db, DName, ViewName, nil); -handle_view_req(#httpd{method='POST',path_parts=[_,_, Id, ViewName]}=Req, Db) -> +handle_view_req(#httpd{method='POST', +        path_parts=[_Db, _Design, DName, _View, ViewName]}=Req, Db) ->      {Props} = couch_httpd:json_body(Req),      Keys = proplists:get_value(<<"keys">>, Props, nil), -    design_doc_view(Req, Db, Id, ViewName, Keys); +    design_doc_view(Req, Db, DName, ViewName, Keys);  handle_view_req(Req, _Db) ->      send_method_not_allowed(Req, "GET,POST,HEAD").  | 
