views:

497

answers:

2

I'm using Apache and Perl (modperl), with Handlers to handle requests. I'm new to this, and I'm not quite sure how to lay things out in a sensible way.

Right now I have the following:

package MyClass::Handler;

use warnings;
use strict;
# includes

our %action = ( 
   'a' => \&a,
   # And more
);

sub handler {
    my $a = shift;
    my $r = Apache2::Request->new($a);

    # Do things

    return Apache2::Const::OK();
}

Should I have a different file for each "space"? Using stackoverflow as a template, do I need a User.pm for all the User management? A Story.pm for stories?

+4  A: 

You might be interested in the excellent CGI::Application framework from CPAN. Despite its name, it works both under normal CGI and mod_perl. It's designed to make the task of setting up web-app dispatch tables very simple. Throw in CGI::Application::Dispatch and you get nice REST-like URLs.

friedo
Thanks, I'm trying to learn it a little bit. How would I layout the rest of the app?
Timmy
I generally try to think in terms of very specific "applications" each with a small number of "screens." So in your example, I'd typically write one module for the "user" application which would have screens (runmodes in CGI-app jargon) to edit their profile, change their password, etc.Generally I try to keep each module to five or fewer runmodes and around 300 lines of code.
friedo
Thanks! I'm actually doing things this way, but really not sure if this is the "good" way..
Timmy
+2  A: 

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.

DougWebb