##
sub Update
{
my $appeasy = shift; # your CGI::AppEasy object
....
return qq(update successful
) # or whatever
}
####
$appeasy->response->code( 404 ); # or whatever
####
'/help' => \$help_text,
####
'/' => \$default_page,
####
=pod
Version: 20100504.2
Brief example:
use CGI::AppEasy;
CGI::AppEasy->new( '/' => \&cmd, )->serve;
sub cmd
{
my( $easy ) = @_;
$easy->response->code( 404 );
"Command ".$easy->path_info." not found
"
}
=cut
package CGI::AppEasy;
use HTTP::Daemon;
use HTTP::Status;
use URI;
use URI::QueryParam;
use Data::Dumper;
use strict;
use warnings;
sub new
{
my( $pkg, %config ) = @_;
bless \%config, $pkg
}
# read-only access to these contained objects:
sub request { $_[0]{'request'} }
sub response { $_[0]{'response'} }
sub daemon { $_[0]{'daemon'} }
sub uri { $_[0]{'uri'} }
sub server_name { $_[0]{'server_name'} }
sub server_port { $_[0]{'server_port'} }
sub remote_port { $_[0]{'remote_port'} }
sub remote_host { $_[0]{'remote_host'} }
sub remote_addr { $_[0]{'remote_addr'} }
# delegation calls:
sub path_info { $_[0]->uri->path }
sub query { $_[0]->uri->query }
sub request_method { $_[0]->request->method }
sub protocol { $_[0]->request->protocol }
sub content_type { $_[0]->request->content_type } # via Header
sub content_length { $_[0]->request->content_length } # via Header
sub content_encoding { $_[0]->request->content_encoding } # via Header
sub content_language { $_[0]->request->content_language } # via Header
sub referer { $_[0]->request->referer } # via Header
sub user_agent { $_[0]->request->user_agent } # via Header
# note that http('host') returns the server hostname, not the requester's hostname
sub host { $_[0]->request->header('Host') }
sub Accept { $_[0]->request->header('Accept') }
sub accept_charset { $_[0]->request->header('Accept-Charset') }
sub accept_encoding { $_[0]->request->header('Accept-Encoding') }
sub accept_language { $_[0]->request->header('Accept-Language') }
sub keep_alive { $_[0]->request->header('Keep-Alive') }
sub return
{
my( $self, $content ) = @_;
$self->{content_set}=1;
$self->response->content($content);
$content
}
sub end
{
my $self = shift;
$self->{'serving'} = 0;
$self
}
sub serve
{
my $self = shift;
my %config = (
LocalPort => 8080,
Blocking => 1, # default from IO::Socket::INET
Timeout => 5,
%$self, @_ );
$self->{'daemon'} =
my $d = HTTP::Daemon->new(
LocalPort => $config{'LocalPort'}||8080,
) or die;
warn "contact ", $d->url, "\n";
$self->{'serving'} = 1;
my($c,$peer_addr);
while ( $self->{'serving'} and ($c,$peer_addr) = $d->accept and $c )
# if timeout, $c will be undef
# how to handle this case?
# more to the point - when does this actually happen?
# my rudimentary attempts to trigger it failed.
{
{
my($port,$iaddr) = sockaddr_in( $peer_addr );
$self->{'remote_port'} = $port;
$self->{'remote_host'} = gethostbyaddr($iaddr,AF_INET);
$self->{'remote_addr'} = inet_ntoa($iaddr);
}
while ( $self->{'serving'} and my $r = $c->get_request )
{
warn "\nRequest:\n".$r->as_string;
$self->{'request'} = $r;
$self->{'uri'} = URI->new( $r->url, $d->url );
( $self->{'server_name'}, $self->{'server_port'} ) = $r->header('Host') =~ /(.*):(.*)/;
my $params_hr = $self->uri->query_form_hash; # not currently used here
$self->{'response'} = HTTP::Response->new(
RC_OK,
undef,
[ # headers
'Content-type' => 'text/html',
],
);
# XXX currently, only GET method is supported
$r->method eq 'GET' or $c->send_error( RC_METHOD_NOT_ALLOWED ), next;
# if the path matches a defined cmd handler exactly, just go with that.
# otherwise, try to find a matching prefix among the defined handlers.
my $path = $self->uri->path;
my $cmd = $config{$path} ? $path : (sort { ($a =~ m,/,g) <=> ($b =~ m,/,g) or length($b) <=> length($a) } grep { $path =~ /^\Q$_\E\b/ } keys %config)[0];
defined $cmd and $cmd eq '/' && $cmd ne $path and undef $cmd; # but don't accept '/' as a matching prefix.
if ( $cmd )
{
if ( ref($config{$cmd}) eq 'CODE' )
{
$self->{'content_set'}=0;
my $ret = $config{$cmd}->( $self );
$self->return($ret) unless $self->{'content_set'};
# ignore returned value if the handler has called ->return()!
}
elsif ( ref($config{$cmd}) eq 'SCALAR' ) # text
{
$self->return( ${ $config{$cmd} } );
}
else
{
$self->response->code( RC_NOT_FOUND );
$self->return( <Error:
Command '$cmd' not recognized at this time!
EOF
}
}
else
{
$self->response->code( RC_NOT_FOUND );
$self->return( <Error:
Command path '$path' not recognized at this time!
EOF
}
$c->send_response( $self->response );
}
$c->close;
undef $c;
}
}
sub continue_link
{
my $context = shift;
my $text = shift;
my $cmd = '/'; @_ % 2 and $cmd = shift;
my %args = @_;
my $uri = new URI;
$uri->path( $cmd );
$uri->query_form( %args );
qq($text)
}
sub continue_formstart
{
my $context = shift;
my $cmd = '/'; @_ % 2 and $cmd = shift;
my %args = @_;
join '',
qq('
}
use Exporter;
our @ISA=qw(Exporter);
our @EXPORT_OK=qw( %LINK %FORMSTART %BUTTON );
{
package Tie::EmbedFunc;
sub TIEHASH { bless $_[1], $_[0] }
sub FETCH { join $", $_[0]->( split $; => $_[1] => -1 ); }
}
tie our %LINK, 'Tie::EmbedFunc', sub { __PACKAGE__->continue_link(@_) };
tie our %FORMSTART, 'Tie::EmbedFunc', sub { __PACKAGE__->continue_formstart(@_) };
tie our %BUTTON, 'Tie::EmbedFunc', sub { __PACKAGE__->continue_button(@_) };
=pod
if you say
use CGI::AppEasy qw( $BUTTON );
then you do this in your code:
$html = "... $BUTTON{'Recalc','/recalc',foo=>42} ...";
of course, you don't have to pollute your own namespace if you don't want to:
use CGI::AppEasy;
$html = "... $CGI::AppEasy::BUTTON{'Recalc','/recalc',foo=>42} ...";
You can even use your own variable if you wish:
use CGI::AppEasy;
our %Button; *Button = \%CGI::AppEasy::BUTTON;
$html = "... $Button{'Recalc','/recalc',foo=>42} ...";
=cut
__PACKAGE__