The previous post wasn't built in a vacuum.
I was chasing down ideas for a purpose. I've been building JSON APIs for the lab for quite some time, each with as much knowledge about them as I had at that time, which means I have a number of bad APIs out there, and I'm in a position now where I'd like to unify them into either task-specific or access-level-specific groups.
That last piece of code was me finally able to take the lesson I learned from perlbrew about the symbol table being usable as a dispatch table. But it had a problem.
That problem was that, to use it, I would have to use or require whatever module I want to access into that one module. This is a problem because then I'd need a different API.pm for each version of the API I wanted to make. This is not good.
I remembered code that I used to use to download Dilbert comics. I subclassed HTML::Parser, adding functions that were specific to the task of grabbing images. Which is exactly what I do here.
I was chasing down ideas for a purpose. I've been building JSON APIs for the lab for quite some time, each with as much knowledge about them as I had at that time, which means I have a number of bad APIs out there, and I'm in a position now where I'd like to unify them into either task-specific or access-level-specific groups.
That last piece of code was me finally able to take the lesson I learned from perlbrew about the symbol table being usable as a dispatch table. But it had a problem.
That problem was that, to use it, I would have to use or require whatever module I want to access into that one module. This is a problem because then I'd need a different API.pm for each version of the API I wanted to make. This is not good.
I remembered code that I used to use to download Dilbert comics. I subclassed HTML::Parser, adding functions that were specific to the task of grabbing images. Which is exactly what I do here.
#!/usr/bin/env perl
# the application code
use feature qw{ say } ;
use strict ;
use warnings ;
use lib ;
my $api = API->new( @ARGV ) ;
$api->run() ;
package API ;
use base 'API_Base' ;
use lib ;
use API_PED ;
Above, I make a version of API that includes API_PED.
API_Base is conceptually very perlbrew-influenced, but I totally dropped the "I think you mean" because this is a JSON API, not a command-line program.
package API_Base ;
use feature qw{ say } ;
use strict ;
use warnings ;
use CGI ;
use Getopt::Long ;
use Data::Dumper ;
use JSON ;
# Yes, I still use CGI. I kick it old-school.
sub new {
my ( $class, @argv ) = @_ ;
my $self ;
my $cgi = CGI->new() ;
%{ $self->{param} } = map { $_ => $cgi->param($_) } $cgi->param() ;
( undef, @{ $self->{pathinfo} } ) = split m{/}, $cgi->path_info() ;
return bless $self, $class ;
}
sub run {
my ($self) = @_ ;
$self->run_command( $self->{pathinfo}, $self->{param} ) ;
}
sub run_command {
my ( $self, $pathinfo, $param ) = @_ ;
my $command = $pathinfo->[0] || 'test' ;
my $s = $self->can("api_$command") ;
unless ($s) {
$command =~ y/-/_/ ;
$s = $self->can("api_$command") ;
}
unless ($s) {
$self->fail( $pathinfo, $param ) ;
exit ;
}
unless ( 'CODE' eq ref $s ) { $self->fail( $pathinfo, $param ) }
$self->$s( $pathinfo, $param ) ;
}
sub fail {
my ( $self, $pathinfo, $param ) = @_ ;
say 'content-type: application/json' ;
say '' ;
say encode_json {
status => 'fail',
param => $param,
pathinfo => $pathinfo
} ;
print STDERR 'INAPPROPRIATE USAGE: '
. 'desired path = '
. ( join '/', '',@$pathinfo ) ;
}
sub api_test {
my ( $self, $pathinfo, $param ) = @_ ;
say 'content-type: application/json' ;
say '' ;
say encode_json { status => 1, param => $param, pathinfo => $pathinfo } ;
}
1 ;
We go into the symbol table twice. one to export api_PED, which would become api.cgi/PED, and one to make subroutines named ped_* into a dispatch table, allowing api.cgi/PED/test, api/PED/mail and api.cgi/PED/lookup.
package API_PED ;
use feature qw{ say } ;
use warnings ;
use Exporter qw{import} ;
use JSON ;
# PED is our campus LDAP server
use lib '/depot/gcore/apps/lib/' ;
use PED qw{ purdue_ldap_lookup } ;
our @EXPORT ;
for my $entry ( keys %API_PED:: ) {
next if $entry !~ /^api_/ ;
push @EXPORT, $entry ;
}
# The goal here is to do as much as we can without repetition
# we export api_PED so that API accept this as a command without
# having to write it into API.pm
# api_PED checks for any subroutine starting with 'ped_' and
# runs it
# so in essence, exporting a sub starting with api_ adds it to the
# API dispatch table, and writing a sub starting with ped_ adds it
# to this module's dispatch tableee
sub api_PED {
my ( $self, $pathinfo, $param ) = @_ ;
my %commands ;
shift @$pathinfo ;
foreach my $entry ( keys %API_PED:: ) {
next if $entry !~ /^ped_/ ;
$commands{$entry} = 1 ;
}
my $sub_name = shift @$pathinfo ;
my $command = 'ped_' . $sub_name ;
if ( $commands{$command} ) {
my $full = join '::', 'API_PED', $command ;
&{$full}( $pathinfo, $param ) ;
exit ;
}
else {
say 'content-type: application/json' ;
say '' ;
say encode_json { c => \%commands, p => $pathinfo, e => $command } ;
}
}
sub ped_test {
my ( $pathinfo, $param ) = @_ ;
say 'content-type: application/json' ;
say '' ;
say encode_json { result => 1 } ;
exit ;
}
sub ped_mail {
my ( $pathinfo, $param ) = @_ ;
my $name = $pathinfo->[0] ;
my $lookup = purdue_ldap_lookup($name) ;
say 'content-type: application/json' ;
say '' ;
say encode_json {
status => ( scalar keys %$lookup ? 1 : 0 ),
mail => $lookup->{mail},
} ;
exit ;
}
sub ped_lookup {
my ( $pathinfo, $param ) = @_ ;
my $name = $pathinfo->[0] ;
my $lookup = purdue_ldap_lookup($name) ;
say 'content-type: application/json' ;
say '' ;
say encode_json {
status => ( scalar keys %$lookup ? 1 : 0 ),
lookup => $lookup,
} ;
exit ;
}
1 ;
I'm not the happiest. Each sub handles encoding the output itself. I normally use JSON, but I could imagine exporting JSONP, XML, CSV or something else, and I could imagine passing back the data and an indication as to how it should be handled. I think I might have that somewhere, like I had the base code in a web comic reader from the late 1990s.
To sum up, I'm in the middle panel of this:
![]() |
| Three Panel Soul. I think the artist turned this into a t-shirt. |

No comments:
Post a Comment