We start the class by declaring the package and using the
strict pragma:
package Apache::BrowseSee;
use strict;
Next, we import common constants (e.g., OK,
NOT_FOUND, etc.), load the
File::Spec::Functions and
File::Basename modules, and import a few
path-manipulation functions that we are going to use:
use Apache::Constants qw(:common);
use File::Spec::Functions qw(catdir canonpath curdir updir);
use File::Basename 'dirname';
Now let's look at the functions. We start with the
simple constructor:
sub new { bless { }, shift;}
The real entry point, the handler, is prototyped as
($$). The handler starts by instantiating its
object, if it hasn't already been done, and storing
the $r object, so we don't need
to pass it to the functions as an argument:
sub handler ($$) {
my($self, $r) = @_;
$self = $self->new unless ref $self;
$self->{r} = $r;
Next we retrieve the path_info element of the
request record:
$self->{dir} = $r->path_info || '/';
For example, if the request was /browse/foo/bar,
where /browse is the location of the handler,
the path_info element will be
/foo/bar. The default value /
is used when the path is not specified.
Then we reset the entries for dirs and
files:
$self->{dirs} = { };
$self->{files} = { };
This is needed because it's possible that the
$self object is created outside the handler (e.g.,
in the startup file) and may persist between requests.
eval { $self->fetch( ) };
return NOT_FOUND if $@;
If the fetch( ) method dies, the error message is
assigned to $@ and we return
NOT_FOUND. You may choose to approach it
differently and return an error message explaining what has happened.
You may also want to log the event before returning:
warn($@), return NOT_FOUND if $@;
Normally this shouldn't happen, unless a user messes
with the arguments (something you should always be on the lookout
for, because they will do it).
The response is generated by three functions. The head(
) method is a very simple one—it sends the HTTP
header text/html and prints an HTML preamble using
the current directory name as a title:
sub fetch {
my $self = shift;
my $doc_root = Apache->document_root;
my $base_dir = canonpath( catdir($doc_root, $self->{dir}));
my $base_entry = $self->{dir} eq '/' ? '' : $self->{dir};
my $dh = Apache::gensym( );
opendir $dh, $base_dir or die "Cannot open $base_dir: $!";
for (readdir $dh) {
next if $_ eq curdir( ); # usually '.'
my $full_dir = catdir $base_dir, $_;
my $entry = "$base_entry/$_";
if (-d $full_dir) {
if ($_ eq updir( )) { # '..'
$entry = dirname $self->{dir};
next if catdir($base_dir, $entry) eq $doc_root;
}
$self->{dirs}{$_} = $entry;
}
else {
$self->{files}{$_} = $entry;
}
}
closedir $dh;
}
While looping through the directory entries, we skip over the current
directory entry using the curdir( ) function
imported from File::Spec::Functions (which is
equivalent to . on Unix) and handle the parent directory entry
specially by matching the updir( ) function (which
is equivalent to .. on Unix). The function dirname(
) gives us the parent directory, and afterward we check
that this directory is different from the document root. If
it's the same, we skip this entry.
Figure 6-2. The files can be browsed but not viewed
Now let's say that as soon as we get the module up
and running, the client comes back and tells us he would like us to
implement a very similar application, except that files should now be
viewable (clickable). This is because later he wants to allow only
authorized users to read the files while letting everybody see what
he has to offer.
We knew that was coming, remember? Since we are lazy and
it's not exciting to write the same code again and
again, we will do the minimum amount of work while still keeping the
client happy. This time we are going to implement the
Apache::BrowseRead module:
package Apache::BrowseRead;
use strict;
use base qw(Apache::BrowseSee);
We place the new module into
Apache/BrowseRead.pm, declare a new package, and
tell Perl that this package inherits from
Apache::BrowseSee using the
base pragma. The last line is roughly equivalent
to:
BEGIN {
require Apache::BrowseSee;
@Apache::BrowseRead::ISA = qw(Apache::BrowseSee);
}
Since this class is going to do the same job as
Apache::BrowseSee, apart from rendering the file
listings differently, all we have to do is override the
render( ) method:
sub render {
my $self = shift;
print "<p>Current Directory: <i>$self->{dir}</i><br>";
my $location = $self->{r}->location;
print qq{<a href="$location$self->{dirs}{$_}">$_</a><br>}
for sort keys %{ $self->{dirs} || { } };
print qq{<a href="$self->{files}{$_}">$_</a><br>}
for sort keys %{ $self->{files} || { } };
}
As you can see, the only difference here is that we link to the real
files now.
We complete the package as usual with 1; and
_ _END_ _:
1;
_ _END_ _