a Dance for the REST of us
I have widgets called "foo," and I am building web app that will also serve as a RESTful app for data access via the command line. All "foo" related routes are packaged together, and are loaded via `load_app "app::foo", prefix => "/foo"` so that `get '/:foo_id'` really become `http://server/foo/:foo_id`. My routes are 1. get '/' =========== Returns a welcome page via a full html request. A full html request is where a full web page is loaded, as opposed to an ajax request where only a part of the web page is refreshed. 2. get '/all' ============== Returns minimal info for all valid "foo" from the data store via a full html request. However, I would also like to return them as a json string via the command line. Now, when requested via the web browser, this method should really return only some minimal info for each "foo." Clicking on any specific "foo" should fire #3 below to get its details -- a typical drill-down application. However, when requested via the commandline, there should be an option to return *all* the details, with some checks, so that gigabytes of data are not returned. 3. get '/:foo_id' ================== Returns all the details for a specific "foo" as a json string via an ajax request. 4. get '/new' ============== Returns a page to begin constructing a new "foo" in the browser via a full html request. There is no equivalent command line method for this. 5. post '/save' ======================= Saves a new "foo" created in #4 or above, or via a command line method. In other words, a user should be able to supply in a json string via the command line everything that a user would create interactively in #4 above. 6. get '/delete/:foo_id' ========================= This method removed a specific foo. Now, two things I don't understand about this -- one, of course, in a RESTful app, this would be a DELETE method. This is a bit confusing to me -- is the HTTP get (I am using a lowercase 'get') not the same as a REST GET (using an uppercase get)? In other words, can I use a `get` to perform a `DELETE`? Two, how do I send authentication? I don't want Sukrieh deleting Sawyer's widget. In fact, this authentication theme runs through all the routes -- do I just pass a session token for authentication with every route? Finally, should #3 be the last route? Else, how do I differentiate between `get '/delete/5'` and `get '/5'` and `get '/all'`? Thanks in advance for a quick lesson, whosoever cares to give one. -- Puneet Kishor
I don't know much about REST, but the base should be that you have to map the thing you want to do upon HTTP methods, which have a well-defined semantics. In particular, if you want to... ... get some data, without having collateral effects on the data => use HTTP GET ... modify some existing data => use HTTP POST ... add some data => use HTTP PUT ... delete some data => use HTTP DELETE Dancer happens to support the four methods via the usual route definition approach, with lowercase functions for each HTTP METHOD, except that (HTTP) DELETE is called "del" (this is due to the fact that "delete" is a built-in keyword in Perl and it's better to leave it alone). So, if you want to delete something under the REST umbrella, you're supposed to use Dancer's "del" instead of "get": del '/:foo_id' => sub { ... } Regarding the ordering, there is an example in the "ACTION SKIPPING" section of Dancer::Introduction, even though the example is a bit wrong because it does not include the due "return" before pass (see documentation about "pass" for this). This is how it should be: get '/say/:word' => sub { return pass if (params->{word} =~ /^\d+$/); "I say a word: ".params->{word}; }; get '/say/:number' => sub { "I say a number: ".params->{number}; }; Cheers, Flavio. On Fri, Dec 24, 2010 at 12:42 AM, Puneet Kishor <punk.kish@gmail.com> wrote:
I have widgets called "foo," and I am building web app that will also serve as a RESTful app for data access via the command line. All "foo" related routes are packaged together, and are loaded via `load_app "app::foo", prefix => "/foo"`
so that `get '/:foo_id'` really become `http://server/foo/:foo_id`<http://server/foo/:foo_id> .
My routes are
1. get '/' =========== Returns a welcome page via a full html request. A full html request is where a full web page is loaded, as opposed to an ajax request where only a part of the web page is refreshed.
2. get '/all' ============== Returns minimal info for all valid "foo" from the data store via a full html request. However, I would also like to return them as a json string via the command line. Now, when requested via the web browser, this method should really return only some minimal info for each "foo." Clicking on any specific "foo" should fire #3 below to get its details -- a typical drill-down application. However, when requested via the commandline, there should be an option to return *all* the details, with some checks, so that gigabytes of data are not returned.
3. get '/:foo_id' ================== Returns all the details for a specific "foo" as a json string via an ajax request.
4. get '/new' ============== Returns a page to begin constructing a new "foo" in the browser via a full html request. There is no equivalent command line method for this.
5. post '/save' ======================= Saves a new "foo" created in #4 or above, or via a command line method. In other words, a user should be able to supply in a json string via the command line everything that a user would create interactively in #4 above.
6. get '/delete/:foo_id' ========================= This method removed a specific foo. Now, two things I don't understand about this -- one, of course, in a RESTful app, this would be a DELETE method. This is a bit confusing to me -- is the HTTP get (I am using a lowercase 'get') not the same as a REST GET (using an uppercase get)? In other words, can I use a `get` to perform a `DELETE`? Two, how do I send authentication? I don't want Sukrieh deleting Sawyer's widget.
In fact, this authentication theme runs through all the routes -- do I just pass a session token for authentication with every route?
Finally, should #3 be the last route? Else, how do I differentiate between `get '/delete/5'` and `get '/5'` and `get '/all'`?
Thanks in advance for a quick lesson, whosoever cares to give one.
-- Puneet Kishor _______________________________________________ Dancer-users mailing list Dancer-users@perldancer.org http://www.backup-manager.org/cgi-bin/listinfo/dancer-users
Very clear explanation, thanks. A couple of questions follow (see below)... Flavio Poletti wrote:
I don't know much about REST, but the base should be that you have to map the thing you want to do upon HTTP methods, which have a well-defined semantics. In particular, if you want to...
... get some data, without having collateral effects on the data => use HTTP GET ... modify some existing data => use HTTP POST ... add some data => use HTTP PUT ... delete some data => use HTTP DELETE
Dancer happens to support the four methods via the usual route definition approach, with lowercase functions for each HTTP METHOD, except that (HTTP) DELETE is called "del" (this is due to the fact that "delete" is a built-in keyword in Perl and it's better to leave it alone).
So, if you want to delete something under the REST umbrella, you're supposed to use Dancer's "del" instead of "get":
del '/:foo_id' => sub { ... }
Right, that is on the dancer side. What is it going to be on the browser side? In my Javascript, I have $.ajax({ url : "http://server/foo/" + foo_id, type : "GET", data : "", dataType: "json", error : function() { alert("Error loading html document"); }, success : function(data) { alert("Foo " + foo_id + " was successfully deleted"); } }); So, from the browser's perspective, I am still sending an http get request, correct? Or, should that "type:" above really be a "DELETE" instead of a "GET"? Of course, if that is the case, then, from the browser's perspective, there is no difference between a get request that deletes and a get request that really gets.
Regarding the ordering, there is an example in the "ACTION SKIPPING" section of Dancer::Introduction, even though the example is a bit wrong because it does not include the due "return" before pass (see documentation about "pass" for this). This is how it should be:
get'/say/:word' => sub { return pass if (params->{word} =~ /^\d+$/); "I say a word:".params->{word}; };
get'/say/:number' => sub { "I say a number:".params->{number}; };
This is a bit worrisome as I have to get my ordering really correct. I guess I could first have all the routes that are identified by specific keywords ("all", "new", "save", etc.), and then end with the route that is just an id. If I get the ordering wrong, I can end up with unexpected results. I have another question regarding putting only similar routes in a single package, but I will save that for another email.
Cheers,
Flavio.
On Fri, Dec 24, 2010 at 12:42 AM, Puneet Kishor <punk.kish@gmail.com <mailto:punk.kish@gmail.com>> wrote:
I have widgets called "foo," and I am building web app that will also serve as a RESTful app for data access via the command line. All "foo" related routes are packaged together, and are loaded via `load_app "app::foo", prefix => "/foo"`
so that `get '/:foo_id'` really become `http://server/foo/:foo_id` <http://server/foo/:foo_id>.
My routes are
1. get '/' =========== Returns a welcome page via a full html request. A full html request is where a full web page is loaded, as opposed to an ajax request where only a part of the web page is refreshed.
2. get '/all' ============== Returns minimal info for all valid "foo" from the data store via a full html request. However, I would also like to return them as a json string via the command line. Now, when requested via the web browser, this method should really return only some minimal info for each "foo." Clicking on any specific "foo" should fire #3 below to get its details -- a typical drill-down application. However, when requested via the commandline, there should be an option to return *all* the details, with some checks, so that gigabytes of data are not returned.
3. get '/:foo_id' ================== Returns all the details for a specific "foo" as a json string via an ajax request.
4. get '/new' ============== Returns a page to begin constructing a new "foo" in the browser via a full html request. There is no equivalent command line method for this.
5. post '/save' ======================= Saves a new "foo" created in #4 or above, or via a command line method. In other words, a user should be able to supply in a json string via the command line everything that a user would create interactively in #4 above.
6. get '/delete/:foo_id' ========================= This method removed a specific foo. Now, two things I don't understand about this -- one, of course, in a RESTful app, this would be a DELETE method. This is a bit confusing to me -- is the HTTP get (I am using a lowercase 'get') not the same as a REST GET (using an uppercase get)? In other words, can I use a `get` to perform a `DELETE`? Two, how do I send authentication? I don't want Sukrieh deleting Sawyer's widget.
In fact, this authentication theme runs through all the routes -- do I just pass a session token for authentication with every route?
Finally, should #3 be the last route? Else, how do I differentiate between `get '/delete/5'` and `get '/5'` and `get '/all'`?
Thanks in advance for a quick lesson, whosoever cares to give one.
-- Puneet Kishor _______________________________________________ Dancer-users mailing list Dancer-users@perldancer.org <mailto:Dancer-users@perldancer.org> http://www.backup-manager.org/cgi-bin/listinfo/dancer-users
-- Puneet Kishor http://punkish.org Carbon Model http://carbonmodel.org Charter Member, Open Source Geospatial Foundation http://www.osgeo.org Science Fellow http://creativecommons.org/about/people/fellows#puneetkishor Nelson Institute, UW-Madison http://www.nelson.wisc.edu --------------------------------------------------------------------------- Assertions are politics; backing up assertions with evidence is science ===========================================================================
On Fri, Dec 24, 2010 at 4:40 AM, Puneet Kishor <punk.kish@gmail.com> wrote:
Very clear explanation, thanks. A couple of questions follow (see below)...
Flavio Poletti wrote:
I don't know much about REST, but the base should be that you have to map the thing you want to do upon HTTP methods, which have a well-defined semantics. In particular, if you want to...
... get some data, without having collateral effects on the data => use HTTP GET ... modify some existing data => use HTTP POST ... add some data => use HTTP PUT ... delete some data => use HTTP DELETE
Dancer happens to support the four methods via the usual route definition approach, with lowercase functions for each HTTP METHOD, except that (HTTP) DELETE is called "del" (this is due to the fact that "delete" is a built-in keyword in Perl and it's better to leave it alone).
So, if you want to delete something under the REST umbrella, you're supposed to use Dancer's "del" instead of "get":
del '/:foo_id' => sub { ... }
Right, that is on the dancer side. What is it going to be on the browser side? In my Javascript, I have
$.ajax({ url : "http://server/foo/" + foo_id, type : "GET", data : "", dataType: "json", error : function() { alert("Error loading html document"); }, success : function(data) { alert("Foo " + foo_id + " was successfully deleted"); } });
So, from the browser's perspective, I am still sending an http get request, correct? Or, should that "type:" above really be a "DELETE" instead of a "GET"?
Of course, if that is the case, then, from the browser's perspective, there is no difference between a get request that deletes and a get request that really gets.
I think that the type should be DELETE for this to be REST-compliant. When you set DELETE in the Javascript, a HTTP DELETE request is sent to the server and Dancer is able to catch the route you set with "del". If you set the request type to GET, Dancer will try to look for the correct route handler in the group set with "get" and ignore what you set with "del".
Regarding the ordering, there is an example in the "ACTION SKIPPING"
section of Dancer::Introduction, even though the example is a bit wrong because it does not include the due "return" before pass (see documentation about "pass" for this). This is how it should be:
get'/say/:word' => sub { return pass if (params->{word} =~ /^\d+$/); "I say a word:".params->{word}; };
get'/say/:number' => sub { "I say a number:".params->{number}; };
This is a bit worrisome as I have to get my ordering really correct. I guess I could first have all the routes that are identified by specific keywords ("all", "new", "save", etc.), and then end with the route that is just an id.
If I get the ordering wrong, I can end up with unexpected results.
This surely can happen. Anyway the example above with "pass" is geared at avoiding what you fear, i.e. you are left the possibility to perform additional checks inside the route handler and pass the control to the next matching route if this check is not satisfactory. Another way could be using a regexp, which might be particularly helpful when you need to tell alpha-based words and numbers apart. The general strategy, anyway, should be having the most "particular" routes at the beginning, and the "widest" at the end.
I have another question regarding putting only similar routes in a single package, but I will save that for another email.
Cheers,
Flavio.
On Fri, Dec 24, 2010 at 12:42 AM, Puneet Kishor <punk.kish@gmail.com <mailto:punk.kish@gmail.com>> wrote:
I have widgets called "foo," and I am building web app that will also serve as a RESTful app for data access via the command line. All "foo" related routes are packaged together, and are loaded via `load_app "app::foo", prefix => "/foo"`
so that `get '/:foo_id'` really become `http://server/foo/:foo_id`<http://server/foo/:foo_id> <http://server/foo/:foo_id>.
My routes are
1. get '/' =========== Returns a welcome page via a full html request. A full html request is where a full web page is loaded, as opposed to an ajax request where only a part of the web page is refreshed.
2. get '/all' ============== Returns minimal info for all valid "foo" from the data store via a full html request. However, I would also like to return them as a json string via the command line. Now, when requested via the web browser, this method should really return only some minimal info for each "foo." Clicking on any specific "foo" should fire #3 below to get its details -- a typical drill-down application. However, when requested via the commandline, there should be an option to return *all* the details, with some checks, so that gigabytes of data are not returned.
3. get '/:foo_id' ================== Returns all the details for a specific "foo" as a json string via an ajax request.
4. get '/new' ============== Returns a page to begin constructing a new "foo" in the browser via a full html request. There is no equivalent command line method for this.
5. post '/save' ======================= Saves a new "foo" created in #4 or above, or via a command line method. In other words, a user should be able to supply in a json string via the command line everything that a user would create interactively in #4 above.
6. get '/delete/:foo_id' ========================= This method removed a specific foo. Now, two things I don't understand about this -- one, of course, in a RESTful app, this would be a DELETE method. This is a bit confusing to me -- is the HTTP get (I am using a lowercase 'get') not the same as a REST GET (using an uppercase get)? In other words, can I use a `get` to perform a `DELETE`? Two, how do I send authentication? I don't want Sukrieh deleting Sawyer's widget.
In fact, this authentication theme runs through all the routes -- do I just pass a session token for authentication with every route?
Finally, should #3 be the last route? Else, how do I differentiate between `get '/delete/5'` and `get '/5'` and `get '/all'`?
Thanks in advance for a quick lesson, whosoever cares to give one.
-- Puneet Kishor _______________________________________________ Dancer-users mailing list Dancer-users@perldancer.org <mailto:Dancer-users@perldancer.org>
-- Puneet Kishor http://punkish.org Carbon Model http://carbonmodel.org Charter Member, Open Source Geospatial Foundation http://www.osgeo.org Science Fellow http://creativecommons.org/about/people/fellows#puneetkishor Nelson Institute, UW-Madison http://www.nelson.wisc.edu --------------------------------------------------------------------------- Assertions are politics; backing up assertions with evidence is science ===========================================================================
participants (2)
-
Flavio Poletti -
Puneet Kishor