A while ago I mentioned a voting website I created and a home-grown REST-ful library I created that I use in the site's code. I'm thinking that the library might be useful to others, but I'd like some feedback, both on it's probable utility and on the code in the library itself.
(Update: REST = Representational State Transfer; a good place to start reading up on this, as always, is wikipedia.)
So, here goes! First, the library - REST.pm
package REST; use strict; use CGI; our $DEBUG = 0; sub new { return bless {CGI => CGI->new}, shift }; sub cgi { shift()->{CGI} } sub debug { my ($self, $urls, @problems) = @_; my $cgi = $self->cgi; print $cgi->header, $cgi->start_html, "REST::debug executing: @problems<hr>", "<dl>", (map { "<dt><tt>$_</tt></dt><dd>$urls->{$_}</dd>" } keys % +$urls), "</dl>", "Path info: <tt>", $cgi->path_info, "</tt><br>", "Request method: <tt>", $cgi->request_method, "</tt><br>", $cgi->end_html; } sub run { my ($self, %urls) = @_; my $pathInfo = $self->cgi->path_info; my $method = $self->cgi->request_method; my ($package, @pathParams); foreach my $path ( keys %urls ) { $package = $urls{$path}; if ( $pathInfo =~ $path ) { @pathParams = ($1, $2, $3, $4, $5, $6, $7, $8, $9); last; } undef $package; } if ( $package ) { my $dispatcher = $package->new($self->cgi); if ( $dispatcher->can($method) ) { $dispatcher->$method(@pathParams); return; } } if ( $DEBUG ) { $self->debug(\%urls, $@); } else { print $self->cgi->header, $self->cgi->start_html, $self->cgi->end_ +html; } } 1;
As far as how you'd use REST, the overall idea is to set up a hash of "paths" to class/package names, create a REST instance, and then invoke run on that instance. These paths are actually regular expressions, allowing you to pass paramters as part of the path. E.g., the URL http://somewhere.com/index.pl/candidate/4/vote/yes would be mapped via a path like q{/candidate/(\d+)/vote/yes}. The regular expression would match 4 and then pass that value along as a parameter to the handling function.
#!/usr/bin/perl -w # (index.pl) use strict; use REST; use HTML::Template; my %urls = ( qr{^/?$} => 'Welcome', qr{^/hello$} => 'NiceToMeetYou', qr{^/bye/(\w+)$} => 'Goodbye', ); REST->new->run(%urls);
Each class would need to implement GET or POST, or whatever other verbs as appropriate for your HTTP clients. In essence, REST.pm is a dispatcher. Your "application code" would be in classes like the following (which could either be in index.pl or use'd as appropriate):
package Renderer; # a class w/common functionality in all "application" classes sub new { my ($class, $cgi) = @_; return bless { CGI => $cgi }, $class; } sub cgi { return shift->{CGI} } sub render { my ($self, $content) = @_; my $templateText = q{ <html> <head><title>Welcome</title></head> <body><TMPL_VAR NAME='CONTENT'></body> </html> }; my $template = HTML::Template->new_scalar_ref(\$templateText); $template->param(CONTENT => $content); print $self->cgi->header(-type => 'text/html'), $template->output; } package Welcome; use base 'Renderer'; sub GET { my $self = shift; $self->render(q{ <p>Welcome. My name is Perl. What's your name?</p> <form method="POST" action="index.pl/hello"> <input type="text" name="name"> <input type="submit"> </form> }); } package NiceToMeetYou; use base 'Renderer'; sub POST { my $self = shift; my $name = $self->cgi->param('name'); # yes, it would be scrubbed in + production code $self->render(qq{ <p>Nice to meet you $name. <a href="../index.pl/bye/$name">Leaving already?</a></p> }); } package Goodbye; use base 'Renderer'; sub GET { my ($self, $name) = @_; $self->render("Goodbye, $name. It was nice visiting."); }
There are a couple ideas and possible improvemetns I have in mind. One thing I don't like is that even though this "simulates" REST-fulness, it doesn't really dispatch differently based on different MIME-types. And I don't like how index.pl (or whatever your filename is) appears in the URL in the browser; I've tried playing with mod_rewrite, but have never been able to get it just right.
So, any suggestions? Comments? If this turns out to be useful for others, I'd consider putting it on CPAN. Let me know what you all think.
|
---|
Replies are listed 'Best First'. | |
---|---|
Re: RFC: REST.pm
by grinder (Bishop) on Nov 02, 2007 at 09:58 UTC | |
by t'mo (Pilgrim) on Nov 03, 2007 at 03:23 UTC | |
Re: RFC: REST.pm
by Anonymous Monk on Nov 02, 2007 at 13:53 UTC | |
by t'mo (Pilgrim) on Nov 03, 2007 at 03:15 UTC | |
by Anonymous Monk on Nov 03, 2007 at 04:08 UTC | |
by t'mo (Pilgrim) on Nov 03, 2007 at 23:13 UTC | |
by Anonymous Monk on Nov 16, 2007 at 17:47 UTC |