From 7cb449346ebe033073fb70f774f11d4fe1e8b991 Mon Sep 17 00:00:00 2001 From: Robin Berjon Date: Thu, 25 Oct 2012 09:27:55 +0200 Subject: [PATCH 1/9] rewrite as a function --- share/server/loop.js | 1 + share/server/render.js | 22 +++++++++++++ src/couchdb/couch_httpd_rewrite.erl | 50 +++++++++++++++++++++++++++-- src/couchdb/couch_query_servers.erl | 10 ++++++ 4 files changed, 81 insertions(+), 2 deletions(-) diff --git a/share/server/loop.js b/share/server/loop.js index fcd016f1764..7d27f6107e4 100644 --- a/share/server/loop.js +++ b/share/server/loop.js @@ -60,6 +60,7 @@ var DDoc = (function() { "lists" : Render.list, "shows" : Render.show, "filters" : Filter.filter, + "rewrites" : Render.rewrite, "views" : Filter.filter_view, "updates" : Render.update, "validate_doc_update" : Validate.validate diff --git a/share/server/render.js b/share/server/render.js index f19e80983f3..773437cc7a3 100644 --- a/share/server/render.js +++ b/share/server/render.js @@ -276,6 +276,25 @@ var Render = (function() { } }; + function runRewrite(fun, ddoc, args) { + try { + var result = fun.apply(ddoc, args); + if (!result) { + throw(["error", "rewrite_error", "rewrite function could not produce mapping"]); + } + if (typeof result === "string") { + result = { path: result }; + } + if (typeof result !== "object") { + throw(["error", "rewrite_error", "incomprehensible response from rewrite function"]); + } + if (!result.method) result.method = args[0].method; + respond(["rew", doc, result]); + } catch(e) { + renderError(e, fun.toSource()); + } + }; + function resetList() { gotRow = false; lastRow = false; @@ -335,6 +354,9 @@ var Render = (function() { }, list : function(fun, ddoc, args) { runList(fun, ddoc, args); + }, + rewrite : function(fun, ddoc, args) { + runRewrite(fun, ddoc, args); } }; })(); diff --git a/src/couchdb/couch_httpd_rewrite.erl b/src/couchdb/couch_httpd_rewrite.erl index 756cdefb2ae..3e56bea0a0d 100644 --- a/src/couchdb/couch_httpd_rewrite.erl +++ b/src/couchdb/couch_httpd_rewrite.erl @@ -138,8 +138,54 @@ handle_rewrite_req(#httpd{ couch_httpd:send_error(Req, 404, <<"rewrite_error">>, <<"Invalid path.">>); Bin when is_binary(Bin) -> - couch_httpd:send_error(Req, 400, <<"rewrite_error">>, - <<"Rewrite rules are a String. They must be a JSON Array.">>); + case couch_query_servers:rewrite(Req, _Db, DDoc) of + undefined -> + couch_httpd:send_error(Req, 404, <<"rewrite_error">>, + <<"Invalid path.">>); + Rewrite -> + % first figure out the method (extract or default) + RewMethod = case couch_util:get_value(<<"method">>, Rewrite) of + undefined -> + couch_util:to_binary(Method); + M -> M + end, + % then get the path (blow up if absent) + RewPath = case couch_util:get_value(<<"path">>, Rewrite) of + undefined -> + couch_httpd:send_error(Req, 400, <<"rewrite_error">>, + <<"Rewrite result must specify a path.">>); + P -> binary_to_list(P) + end, + % then fire off the mochi request + % most of this code is duplicated -- test with it, but try to refactor later + RewPath1 = case mochiweb_util:safe_relative_path(RewPath) of + undefined -> + ?b2l(Prefix) ++ "/" ++ RewPath; + P1 -> + ?b2l(Prefix) ++ "/" ++ P1 + end, + RewPath2 = ?b2l(iolist_to_binary(normalize_path(RewPath1))), + Headers = mochiweb_headers:default("x-couchdb-requested-path", + MochiReq:get(raw_path), + MochiReq:get(headers)), + ?LOG_DEBUG("rewrite to ~p ~n", [RewPath2]), + MochiReq1 = mochiweb_request:new(MochiReq:get(socket), + RewMethod, + RewPath2, + MochiReq:get(version), + Headers), + MochiReq1:cleanup(), + #httpd{ + db_url_handlers = DbUrlHandlers, + design_url_handlers = DesignUrlHandlers, + default_fun = DefaultFun, + url_handlers = UrlHandlers, + user_ctx = UserCtx + } = Req, + erlang:put(pre_rewrite_user_ctx, UserCtx), + couch_httpd:handle_request_int(MochiReq1, DefaultFun, + UrlHandlers, DbUrlHandlers, DesignUrlHandlers) + end; Rules -> % create dispatch list from rules DispatchList = [make_rule(Rule) || {Rule} <- Rules], diff --git a/src/couchdb/couch_query_servers.erl b/src/couchdb/couch_query_servers.erl index e29f23b9276..ecef3870941 100644 --- a/src/couchdb/couch_query_servers.erl +++ b/src/couchdb/couch_query_servers.erl @@ -20,6 +20,7 @@ -export([reduce/3, rereduce/3,validate_doc_update/5]). -export([filter_docs/5]). -export([filter_view/3]). +-export([rewrite/3]). -export([with_ddoc_proc/2, proc_prompt/2, ddoc_prompt/3, ddoc_proc_prompt/3, json_doc/1]). @@ -239,6 +240,15 @@ validate_doc_update(DDoc, EditDoc, DiskDoc, Ctx, SecObj) -> throw({unauthorized, Message}) end. +rewrite(Req, Db, DDoc) -> + JsonReq = couch_httpd_external:json_req_obj(Req, Db), + case ddoc_prompt(DDoc, [<<"rewrites">>], [JsonReq]) of + [<<"error">>] -> + undefined; + [<<"rew">>, Rewrite] -> + Rewrite + end. + json_doc(nil) -> null; json_doc(Doc) -> couch_doc:to_json_obj(Doc, [revs]). From e4c60abca42e606553096c7f6c3f5bf0311ed2b6 Mon Sep 17 00:00:00 2001 From: Robin Berjon Date: Thu, 25 Oct 2012 15:32:34 +0200 Subject: [PATCH 2/9] now that strings are acceptable, strings that aren't functions ought to return 500 --- share/www/script/test/rewrite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/www/script/test/rewrite.js b/share/www/script/test/rewrite.js index ed7d26cb059..793713d80c3 100644 --- a/share/www/script/test/rewrite.js +++ b/share/www/script/test/rewrite.js @@ -435,7 +435,7 @@ couchTests.rewrite = function(debug) { } db.save(ddoc); var res = CouchDB.request("GET", "/"+dbName+"/_design/invalid/_rewrite/foo"); - TEquals(400, res.status, "should return 400"); + TEquals(500, res.status, "should return 500"); var ddoc_requested_path = { _id: "_design/requested_path", From 8135966bf31ffa5741a23549f0e204ee7beb1da4 Mon Sep 17 00:00:00 2001 From: Robin Berjon Date: Thu, 25 Oct 2012 16:27:47 +0200 Subject: [PATCH 3/9] function based rewrite that works at least for simple cases --- share/server/render.js | 2 +- share/www/script/test/rewrite.js | 34 +++++++++++++++++++++++++++++ src/couchdb/couch_httpd_rewrite.erl | 15 +++++++------ 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/share/server/render.js b/share/server/render.js index 773437cc7a3..f5a8811e82a 100644 --- a/share/server/render.js +++ b/share/server/render.js @@ -289,7 +289,7 @@ var Render = (function() { throw(["error", "rewrite_error", "incomprehensible response from rewrite function"]); } if (!result.method) result.method = args[0].method; - respond(["rew", doc, result]); + respond(["rew", result]); } catch(e) { renderError(e, fun.toSource()); } diff --git a/share/www/script/test/rewrite.js b/share/www/script/test/rewrite.js index 793713d80c3..4ca3b90cc74 100644 --- a/share/www/script/test/rewrite.js +++ b/share/www/script/test/rewrite.js @@ -427,6 +427,40 @@ couchTests.rewrite = function(debug) { }); }); + // test function rewrites + run_on_modified_server( + [{section: "httpd", + key: "authentication_handlers", + value: "{couch_httpd_auth, special_test_authentication_handler}"}, + {section:"httpd", + key: "WWW-Authenticate", + value: "X-Couch-Test-Auth"}], + + function(){ + var func_rewrite = function (req) { + // console.log(req); + return "foo.txt"; + }; + var ddoc_func = { + _id: "_design/funcrew", + rewrites: stringFun(func_rewrite), + _attachments:{ + "foo.txt": { + content_type:"text/plain", + data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + } + }, + }; + T(db.save(ddoc_func).ok); + + // very basic rewrite + req = CouchDB.request("GET", "/" + dbName + "/_design/funcrew/_rewrite/foo"); + console.log(req.responseText); + T(req.responseText == "This is a base64 encoded text"); + T(req.getResponseHeader("Content-Type") == "text/plain"); + } + ); + // test invalid rewrites // string var ddoc = { diff --git a/src/couchdb/couch_httpd_rewrite.erl b/src/couchdb/couch_httpd_rewrite.erl index 3e56bea0a0d..a37a46d5535 100644 --- a/src/couchdb/couch_httpd_rewrite.erl +++ b/src/couchdb/couch_httpd_rewrite.erl @@ -138,17 +138,18 @@ handle_rewrite_req(#httpd{ couch_httpd:send_error(Req, 404, <<"rewrite_error">>, <<"Invalid path.">>); Bin when is_binary(Bin) -> + %% XXX TODO + % - this still needs to be refactored, too much code is duplicated with next + % - needs more tests + % - needs some docs + % - the rewrite function gets the request — maybe it should get the post-_rewrite path? case couch_query_servers:rewrite(Req, _Db, DDoc) of undefined -> couch_httpd:send_error(Req, 404, <<"rewrite_error">>, <<"Invalid path.">>); - Rewrite -> + {Rewrite} -> % first figure out the method (extract or default) - RewMethod = case couch_util:get_value(<<"method">>, Rewrite) of - undefined -> - couch_util:to_binary(Method); - M -> M - end, + RewMethod = couch_util:get_value(<<"method">>, Rewrite, couch_util:to_binary(Method)), % then get the path (blow up if absent) RewPath = case couch_util:get_value(<<"path">>, Rewrite) of undefined -> @@ -168,7 +169,7 @@ handle_rewrite_req(#httpd{ Headers = mochiweb_headers:default("x-couchdb-requested-path", MochiReq:get(raw_path), MochiReq:get(headers)), - ?LOG_DEBUG("rewrite to ~p ~n", [RewPath2]), + ?LOG_DEBUG("rewrite to ~p with method ~p ~n", [RewPath2, RewMethod]), MochiReq1 = mochiweb_request:new(MochiReq:get(socket), RewMethod, RewPath2, From 5322893f43b126522bd1ebf4f0bb490c0fab59bd Mon Sep 17 00:00:00 2001 From: Robin Berjon Date: Thu, 25 Oct 2012 16:50:38 +0200 Subject: [PATCH 4/9] refactor rewrite processing; minor fix in test --- share/www/script/test/rewrite.js | 1 - src/couchdb/couch_httpd_rewrite.erl | 107 +++++++++------------------- 2 files changed, 33 insertions(+), 75 deletions(-) diff --git a/share/www/script/test/rewrite.js b/share/www/script/test/rewrite.js index 4ca3b90cc74..f8c297924c6 100644 --- a/share/www/script/test/rewrite.js +++ b/share/www/script/test/rewrite.js @@ -455,7 +455,6 @@ couchTests.rewrite = function(debug) { // very basic rewrite req = CouchDB.request("GET", "/" + dbName + "/_design/funcrew/_rewrite/foo"); - console.log(req.responseText); T(req.responseText == "This is a base64 encoded text"); T(req.getResponseHeader("Content-Type") == "text/plain"); } diff --git a/src/couchdb/couch_httpd_rewrite.erl b/src/couchdb/couch_httpd_rewrite.erl index a37a46d5535..61928356da8 100644 --- a/src/couchdb/couch_httpd_rewrite.erl +++ b/src/couchdb/couch_httpd_rewrite.erl @@ -139,7 +139,6 @@ handle_rewrite_req(#httpd{ <<"Invalid path.">>); Bin when is_binary(Bin) -> %% XXX TODO - % - this still needs to be refactored, too much code is duplicated with next % - needs more tests % - needs some docs % - the rewrite function gets the request — maybe it should get the post-_rewrite path? @@ -157,35 +156,7 @@ handle_rewrite_req(#httpd{ <<"Rewrite result must specify a path.">>); P -> binary_to_list(P) end, - % then fire off the mochi request - % most of this code is duplicated -- test with it, but try to refactor later - RewPath1 = case mochiweb_util:safe_relative_path(RewPath) of - undefined -> - ?b2l(Prefix) ++ "/" ++ RewPath; - P1 -> - ?b2l(Prefix) ++ "/" ++ P1 - end, - RewPath2 = ?b2l(iolist_to_binary(normalize_path(RewPath1))), - Headers = mochiweb_headers:default("x-couchdb-requested-path", - MochiReq:get(raw_path), - MochiReq:get(headers)), - ?LOG_DEBUG("rewrite to ~p with method ~p ~n", [RewPath2, RewMethod]), - MochiReq1 = mochiweb_request:new(MochiReq:get(socket), - RewMethod, - RewPath2, - MochiReq:get(version), - Headers), - MochiReq1:cleanup(), - #httpd{ - db_url_handlers = DbUrlHandlers, - design_url_handlers = DesignUrlHandlers, - default_fun = DefaultFun, - url_handlers = UrlHandlers, - user_ctx = UserCtx - } = Req, - erlang:put(pre_rewrite_user_ctx, UserCtx), - couch_httpd:handle_request_int(MochiReq1, DefaultFun, - UrlHandlers, DbUrlHandlers, DesignUrlHandlers) + perform_rewrite(Req, MochiReq, Prefix, RewPath, RewMethod) end; Rules -> % create dispatch list from rules @@ -203,57 +174,45 @@ handle_rewrite_req(#httpd{ % build new path, reencode query args, eventually convert % them to json Bindings1 = maybe_encode_bindings(Bindings), - Path = binary_to_list( + binary_to_list( iolist_to_binary([ string:join(Parts, [?SEPARATOR]), [["?", mochiweb_util:urlencode(Bindings1)] || Bindings1 =/= [] ] - ])), - - % if path is relative detect it and rewrite path - case mochiweb_util:safe_relative_path(Path) of - undefined -> - ?b2l(Prefix) ++ "/" ++ Path; - P1 -> - ?b2l(Prefix) ++ "/" ++ P1 - end - + ])) end, - - % normalize final path (fix levels "." and "..") - RawPath1 = ?b2l(iolist_to_binary(normalize_path(RawPath))), - - % In order to do OAuth correctly, we have to save the - % requested path. We use default so chained rewriting - % wont replace the original header. - Headers = mochiweb_headers:default("x-couchdb-requested-path", - MochiReq:get(raw_path), - MochiReq:get(headers)), - - ?LOG_DEBUG("rewrite to ~p ~n", [RawPath1]), - - % build a new mochiweb request - MochiReq1 = mochiweb_request:new(MochiReq:get(socket), - MochiReq:get(method), - RawPath1, - MochiReq:get(version), - Headers), - - % cleanup, It force mochiweb to reparse raw uri. - MochiReq1:cleanup(), - - #httpd{ - db_url_handlers = DbUrlHandlers, - design_url_handlers = DesignUrlHandlers, - default_fun = DefaultFun, - url_handlers = UrlHandlers, - user_ctx = UserCtx - } = Req, - erlang:put(pre_rewrite_user_ctx, UserCtx), - couch_httpd:handle_request_int(MochiReq1, DefaultFun, - UrlHandlers, DbUrlHandlers, DesignUrlHandlers) + perform_rewrite(Req, MochiReq, Prefix, RawPath, Method1) end. +perform_rewrite(Req, MochiReq, Prefix, RewPath, RewMethod) -> + RewPath1 = case mochiweb_util:safe_relative_path(RewPath) of + undefined -> + ?b2l(Prefix) ++ "/" ++ RewPath; + P1 -> + ?b2l(Prefix) ++ "/" ++ P1 + end, + RewPath2 = ?b2l(iolist_to_binary(normalize_path(RewPath1))), + Headers = mochiweb_headers:default("x-couchdb-requested-path", + MochiReq:get(raw_path), + MochiReq:get(headers)), + ?LOG_DEBUG("rewrite to ~p with method ~p ~n", [RewPath2, RewMethod]), + MochiReq1 = mochiweb_request:new(MochiReq:get(socket), + RewMethod, + RewPath2, + MochiReq:get(version), + Headers), + MochiReq1:cleanup(), + #httpd{ + db_url_handlers = DbUrlHandlers, + design_url_handlers = DesignUrlHandlers, + default_fun = DefaultFun, + url_handlers = UrlHandlers, + user_ctx = UserCtx + } = Req, + erlang:put(pre_rewrite_user_ctx, UserCtx), + couch_httpd:handle_request_int(MochiReq1, DefaultFun, + UrlHandlers, DbUrlHandlers, DesignUrlHandlers). + quote_plus({bind, X}) -> mochiweb_util:quote_plus(X); quote_plus(X) -> From 43e98e6e79e8b659270df4ec8872f09e5dc04a78 Mon Sep 17 00:00:00 2001 From: Robin Berjon Date: Thu, 25 Oct 2012 17:06:51 +0200 Subject: [PATCH 5/9] pass the path after _rewrite to the rewrite function, it makes things simpler --- share/server/render.js | 7 +++++-- share/www/script/test/rewrite.js | 8 +++++--- src/couchdb/couch_httpd_rewrite.erl | 1 - 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/share/server/render.js b/share/server/render.js index f5a8811e82a..e38f51ed35e 100644 --- a/share/server/render.js +++ b/share/server/render.js @@ -278,7 +278,10 @@ var Render = (function() { function runRewrite(fun, ddoc, args) { try { - var result = fun.apply(ddoc, args); + log(JSON.stringify(args, null, 4)); + var req = args[0]; + var reqPath = req.path.slice(req.path.indexOf("_rewrite") + 1).join("/"); + var result = fun.apply(ddoc, [req, reqPath]); if (!result) { throw(["error", "rewrite_error", "rewrite function could not produce mapping"]); } @@ -288,7 +291,7 @@ var Render = (function() { if (typeof result !== "object") { throw(["error", "rewrite_error", "incomprehensible response from rewrite function"]); } - if (!result.method) result.method = args[0].method; + if (!result.method) result.method = req.method; respond(["rew", result]); } catch(e) { renderError(e, fun.toSource()); diff --git a/share/www/script/test/rewrite.js b/share/www/script/test/rewrite.js index f8c297924c6..c608088d486 100644 --- a/share/www/script/test/rewrite.js +++ b/share/www/script/test/rewrite.js @@ -437,9 +437,11 @@ couchTests.rewrite = function(debug) { value: "X-Couch-Test-Auth"}], function(){ - var func_rewrite = function (req) { - // console.log(req); - return "foo.txt"; + var func_rewrite = function (req, path) { + if (path === "foo") { + return "foo.txt"; + } + return false; }; var ddoc_func = { _id: "_design/funcrew", diff --git a/src/couchdb/couch_httpd_rewrite.erl b/src/couchdb/couch_httpd_rewrite.erl index 61928356da8..7f6698eac3e 100644 --- a/src/couchdb/couch_httpd_rewrite.erl +++ b/src/couchdb/couch_httpd_rewrite.erl @@ -141,7 +141,6 @@ handle_rewrite_req(#httpd{ %% XXX TODO % - needs more tests % - needs some docs - % - the rewrite function gets the request — maybe it should get the post-_rewrite path? case couch_query_servers:rewrite(Req, _Db, DDoc) of undefined -> couch_httpd:send_error(Req, 404, <<"rewrite_error">>, From 44e3b633a6a88f3bbd76272bab915071a95e3cde Mon Sep 17 00:00:00 2001 From: Robin Berjon Date: Thu, 25 Oct 2012 17:10:00 +0200 Subject: [PATCH 6/9] test that not matching returns 500 --- share/server/render.js | 1 + share/www/script/test/rewrite.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/share/server/render.js b/share/server/render.js index e38f51ed35e..0062f5a7eb2 100644 --- a/share/server/render.js +++ b/share/server/render.js @@ -281,6 +281,7 @@ var Render = (function() { log(JSON.stringify(args, null, 4)); var req = args[0]; var reqPath = req.path.slice(req.path.indexOf("_rewrite") + 1).join("/"); + log("Path is " + reqPath); var result = fun.apply(ddoc, [req, reqPath]); if (!result) { throw(["error", "rewrite_error", "rewrite function could not produce mapping"]); diff --git a/share/www/script/test/rewrite.js b/share/www/script/test/rewrite.js index c608088d486..cbf0aa63340 100644 --- a/share/www/script/test/rewrite.js +++ b/share/www/script/test/rewrite.js @@ -459,6 +459,10 @@ couchTests.rewrite = function(debug) { req = CouchDB.request("GET", "/" + dbName + "/_design/funcrew/_rewrite/foo"); T(req.responseText == "This is a base64 encoded text"); T(req.getResponseHeader("Content-Type") == "text/plain"); + + // rewrite that doesn't exist + req = CouchDB.request("GET", "/" + dbName + "/_design/funcrew/_rewrite/does/not/exist"); + TEquals(500, req.status, "should return 500"); } ); From f3f6c05a1084d4a019e82777696d2f80202b6c0a Mon Sep 17 00:00:00 2001 From: Robin Berjon Date: Thu, 25 Oct 2012 17:45:50 +0200 Subject: [PATCH 7/9] more tests to check that it actually works --- share/server/render.js | 4 ++-- share/www/script/test/rewrite.js | 41 ++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/share/server/render.js b/share/server/render.js index 0062f5a7eb2..ac75fd30504 100644 --- a/share/server/render.js +++ b/share/server/render.js @@ -278,10 +278,10 @@ var Render = (function() { function runRewrite(fun, ddoc, args) { try { - log(JSON.stringify(args, null, 4)); + // log(JSON.stringify(args, null, 4)); var req = args[0]; var reqPath = req.path.slice(req.path.indexOf("_rewrite") + 1).join("/"); - log("Path is " + reqPath); + // log("Path is " + reqPath); var result = fun.apply(ddoc, [req, reqPath]); if (!result) { throw(["error", "rewrite_error", "rewrite function could not produce mapping"]); diff --git a/share/www/script/test/rewrite.js b/share/www/script/test/rewrite.js index cbf0aa63340..f250cc14247 100644 --- a/share/www/script/test/rewrite.js +++ b/share/www/script/test/rewrite.js @@ -438,8 +438,19 @@ couchTests.rewrite = function(debug) { function(){ var func_rewrite = function (req, path) { - if (path === "foo") { - return "foo.txt"; + if (path === "foo") return "foo.txt"; + if (/^simple\//.test(path)) { + return "_show/simple?value=" + path.replace(/\//g, "-"); + } + if (path === "echo-method") return "_show/methodic"; + if (/^change-method=/.test(path)) { + var parts = path.split("=") + , meth = parts[1] + ; + return { path: "_show/methodic", method: meth }; + } + if (path === "always-get") { + return { path: "_show/methodic", method: "GET" }; } return false; }; @@ -452,6 +463,14 @@ couchTests.rewrite = function(debug) { data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" } }, + shows: { + simple: stringFun(function (doc, req) { + return "Value=" + req.query["value"]; + }), + methodic: stringFun(function (doc, req) { + return "method=" + req.method; + }), + }, }; T(db.save(ddoc_func).ok); @@ -463,6 +482,24 @@ couchTests.rewrite = function(debug) { // rewrite that doesn't exist req = CouchDB.request("GET", "/" + dbName + "/_design/funcrew/_rewrite/does/not/exist"); TEquals(500, req.status, "should return 500"); + + // show with simple computation + req = CouchDB.request("GET", "/" + dbName + "/_design/funcrew/_rewrite/simple/with/value"); + T(req.responseText == "Value=simple-with-value"); + + var meths = "GET POST PUT DELETE".split(" "); + meths.forEach(function (m) { + req = CouchDB.request(m, "/" + dbName + "/_design/funcrew/_rewrite/echo-method"); + T(req.responseText == "method=" + m); + }); + meths.forEach(function (m) { + req = CouchDB.request("GET", "/" + dbName + "/_design/funcrew/_rewrite/change-method=" + m); + T(req.responseText == "method=" + m); + }); + meths.forEach(function (m) { + req = CouchDB.request(m, "/" + dbName + "/_design/funcrew/_rewrite/always-get"); + T(req.responseText == "method=GET"); + }); } ); From ba446cb71ed0debff42fbc94adbea5014e64d174 Mon Sep 17 00:00:00 2001 From: Robin Berjon Date: Thu, 25 Oct 2012 17:54:24 +0200 Subject: [PATCH 8/9] cleaning up comments --- src/couchdb/couch_httpd_rewrite.erl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/couchdb/couch_httpd_rewrite.erl b/src/couchdb/couch_httpd_rewrite.erl index 7f6698eac3e..76c0c3cc3c0 100644 --- a/src/couchdb/couch_httpd_rewrite.erl +++ b/src/couchdb/couch_httpd_rewrite.erl @@ -138,9 +138,6 @@ handle_rewrite_req(#httpd{ couch_httpd:send_error(Req, 404, <<"rewrite_error">>, <<"Invalid path.">>); Bin when is_binary(Bin) -> - %% XXX TODO - % - needs more tests - % - needs some docs case couch_query_servers:rewrite(Req, _Db, DDoc) of undefined -> couch_httpd:send_error(Req, 404, <<"rewrite_error">>, From efccc87c9797b8f520ccaaf88025fab1bbb6e508 Mon Sep 17 00:00:00 2001 From: Robin Berjon Date: Thu, 25 Oct 2012 18:00:35 +0200 Subject: [PATCH 9/9] docs --- src/couchdb/couch_httpd_rewrite.erl | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/couchdb/couch_httpd_rewrite.erl b/src/couchdb/couch_httpd_rewrite.erl index 76c0c3cc3c0..43f178b559a 100644 --- a/src/couchdb/couch_httpd_rewrite.erl +++ b/src/couchdb/couch_httpd_rewrite.erl @@ -106,6 +106,32 @@ %% {"from": "/a", /a?foo=b /some/b foo =:= b %% "to": "/some/:foo", %% }} +%% +%% Alternatively, the rewriting can be performed by a function. It is specified +%% as follows: +%% +%% { +%% .... +%% "rewrites": "function (req, path) { +%% // process the request, and return the rewrite +%% }" +%% } +%% +%% The function is called with the request object and a path string. The latter +%% contains whatever path is left after removing everything up to _rewrite/. +%% +%% Which rewrite takes place depends on what the function returns, which can be +%% one of three things: +%% +%% - false, or a falsy value: indicates that the rewrite could not be +%% performed. This translates to a 500 error. +%% - "a/path": causes the rewrite to that path to take place, with the +%% original method being kept. +%% - { path: "a/path", method: "FOO" }: rewrites to that path and changes +%% the method to the one provided. +%% +%% Rewrite functions are meant to be devoid of side-effects and one should +%% write under the assumption that they are being cached.