For a recent project, I wrote a custom configuration handler which implemented a new ResourceURI config setting. This let me put configuration lines into httpd.conf like this:
ResourceURI SomeResource GET,POST,DELETE "^/...$"
The three arguments are my resource class name, the list of HTTP methods which the resource can respond to, and a regex that matches the URI(s) for the resource.
The custom configuration class replaces each of these lines with a block like this:
PerlModule Handler::{resource class}
PerlModule Resource::{resource class}
<Location ~ "{uri regex}">
Order allow,deny
Allow from all
<LimitExcept {allowed methods}>
Order deny,allow
Deny from all
</LimitExcept>
SetHandler modperl
PerlHandler Handler
PerlSetVar Resource {resource class}
</Location>
This takes care of loading my classes, lets Apache reject invalid methods, sets a flag to indicate which resource to route to, and passes all requests through my Handler::handler() function.
package Handler;
sub handler {
my $r = shift;
my $resource_class = 'Resource::' . $r->dir_config('Resource');
my $handler_class = 'Handler::' . $r->dir_config('Resource');
my $resource = $resource_class->new($r, $r->uri);
return Apache2::Const::HTTP_NOT_FOUND unless $resource;
my $method = $r->method();
return Apache2::Const::HTTP_NOT_IMPLEMENTED
unless $handler_class->can($method);
return $handler_class->$method($r, $resource);
}
Now you just need to implement Resource::* classes with your resource logic (including how to format representations), and Handler::* classes with methods named GET, HEAD, POST, etc and have them use $r and $resource to handle the requests.
For every new resource you need, you add one line of configuration, implement one Handler class (which I've found can often be an almost empty module inheriting from a generic base class), and implement one Resource class which contains the bulk of the code. I've also found that I often need to create one resource object in the context of handling a request for another resource; that's why my resource constructors take a separate argument for the uri instead of just getting the uri from $r. If I'm in Resource::Foo and I need a Resource::Bar object, I can say my $bar = Resource::Bar->new($r, '/bars/1234'); which creates the Bar object using the same URI that a client would use.