http://www.perlmonks.org?node_id=648593

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

    The main thing that leaps out at me is the name: in my humble opinion the concept is not so extraordinary that it merits its own top-level namespace. CGI::REST would be more reasonable.

    Looking more closely, the $1, $2, $3, ... construct is pretty fugly. I would recommend you investigate what @- and @+ do, in order to build something that works to any arbitrary depth.

    The dispatcher package is pretty minimalist: this forces a lot of make-work code into the renderer packages. I think it is important to add more infrastructure in order to make the renderers as simple as possible to write. You need to refactor agressively and pull as much repetitive code out of the renderers as possible.

    The module would be entering a fiercely competitive mindshare space. There's a lot of modules out there that already do this sort of thing. You'll need to have a few very good real-life ready-to-run applications that people can tinker with and see what happens.

    It would also be a good thing to have a look at what's out there already and discuss how your module is similar to others, and how it differs.

    In any event, feel free to publish it on CPAN.

    • another intruder with the mooring in the heart of the Perl

      Thanks for the idea of looking at @+ and friend; $1 - $9 always bugged me too (though after having spent a little time hacking Forth, I flinch a little at any function that takes more than three parameters :-) , so the usefullness of actually having those extra parameters is lessened somewhat for me).

      I agree on factoring out the common, rendering code. I've done that with my own projects, and put it into a utility REST::Dispatchable (not Renderer; I just came up with that name last night) class; I didn't include it here because I thought it might detract from the main subject.

Re: RFC: REST.pm
by Anonymous Monk on Nov 02, 2007 at 13:53 UTC
    Why don't you do a bunch of us a favor and define what "REST" means to you and in this context? There are lots of expansions to "REST" and some of the ones I've found don't seem to apply and some appear like they might apply and then there are the ones that I didn't find. So which one are you using above? Or did you define it somewhere and I just missed it?

      No, I didn't define what I meant with "REST" elsewhere. I really had only two goals when starting this:

      1. Make "pretty" url's
      2. Have separate handlers for the HTTP verbs or methods

      The fact that I'm having trouble accomplishing #1 (i.e., unable to hack mod_rewrite sufficiently that index.pl is hidden, or even that it depends on mod_rewrite) is frustrating but minor. From the reading I've done on REST, handling the HTTP verbs differently is pretty important, and I think this approach does it pretty well.

      On the value of "pretty" url's, I've recently been thinking that having a url-mapping for functionality within a web application is a useful tool to organize the application.

      The biggest drawback I see with my implementation is the extra code you'd have to write (as opposed to "configure" in the url-mapping) to differentiate content-types, e.g.:

      # somewhere in a Renderer subclass... sub POST { my $self = shift; my $filename = $self->cgi->param('file'); my $type = $self->cgi->uploadInfo($filename)->{'Content-Type'}; if ( $type eq 'text/html' ) { # do something with the HTML } elsif ( $type eq 'image/gif' ) { # do something with the GIF } # ... }

        It is nice that you've done reading on REST. Perhaps you could enlighten us with some links to some of your favorite such readings... because perhaps one of the things you read would happen to define what "REST" is for the REST of us.

        Even just expanding the acronym "REST" (as you are using it) would be a small help. :)

        "The fact that I'm having trouble accomplishing #1 (i.e., unable to hack mod_rewrite sufficiently that index.pl is hidden, or even that it depends on mod_rewrite)"

        httpd.conf:
        ScriptAliasMatch /rest\b /path/to/index.pl
        http://somewhere.com/rest/candidate/4/vote/yes

        or:

        ScriptAliasMatch /candidate\b /path/to/index.pl
        http://somewhere.com/candidate/4/vote/yes