=head1 NAME Squatting::Cookbook - Web Development Techniques for Squatting =head1 INTRODUCTION Squatting exists because I fell in love with Camping's API, and I couldn't bear the thought of building another site using some other API. When I decided that the next site I wanted to build would be implemented in Perl, I had no choice but to port Camping from Ruby to Perl, and that's how Squatting was born. My hope is that other Perl programmers will be able to appreciate how concise this API is, and I hope they'll see just how far a little bit of code can go. =head2 The Anatomy of a Squatting Application =head3 The Main Module should be a subclass of Squatting package App; use strict; use warnings; use base 'Squatting'; # Be sure to pull in the other modules of your app. use App::Controllers; use App::Views; # OPTIONAL: App configuration our %CONFIG = ( root => 'www' ); # OPTIONAL: App initialization sub init { my ($class) = @_; # If there is any initialization that you want to do # when the app starts up, the init method is a good # place to do it. $class->next::method; } # OPTIONAL: Response wrapper sub service { my ($class, $c, @args) = @_; # Like Camping, the service method can be overridden # to let your code do things before and after a request. # The parameters to this method are: # # $c - the Squatting::Controller object # @args - regex captures created by matching $c->urls # against the request path # - before a request my $body = $class->next::method(@args); # - after a request # and be sure to return the response body (if any) $body; } 1; =head3 The Controllers Module Should Contain a List of Controller Objects package App::Controllers; use strict; use warnings; use Squatting ':controllers'; # - @C contains Squatting::Controller objects our @C = ( # Typically, you'll start with a controller for the app's home page. C( # Name => [ @list_of_url_patterns ] Home => [ '/' ], # method => handler get => sub { my ($self) = @_; $self->v->{message} = 'Hello, world.'; $self->render('home'); } ), # Here's an example of a controller that handles both # GET and POST requests. C( # Name => [ @list_of_url_patterns ] Contact => [ '/contact' ], # method => handler get => sub { my ($self) = @_; $self->render('contact'); }, # method => handler post => sub { my ($self) = @_; $self->redirect(R('Contact')); }, ), # Here's an example of a controller for handling static requests. C( # Name => [ @list_of_url_patterns ] Static => [ '/(css|js|images/.*)' ], # It's OK to put extra data in the controller. mime => { css => 'text/css', js => 'text/javascript', jpg => 'image/jpeg', gif => 'image/gif', png => 'image/png', }, # method => handler get => sub { my ($self, $path) = @_; if ($path =~ qr{\.\.}) { $self->status = 403; return; } my ($type) = ($path =~ /\.(\w+)$/); $self->headers->{'Content-Type'} = $self->{mime}->{$type} || 'text/plain'; my $file = "$App::CONFIG{root}/$path"; if (-e $file) { return scalar read_file($file); } else { $self->status = 404; return; } } ), ); 1; =head3 The Views Module Should Contain a List of View Objects package App::Views; use strict; use warnings; use Squatting ':views'; # - @V contains Squatting::View objects our @V = ( V( 'default', layout => sub { my ($self, $v, $content) = @_; }, home => sub { my ($self, $v) = @_; }, contact => sub { my ($self, $v) = @_; }, ) ); 1; =head1 PROGRAMMING TECHNIQUES =head2 COMET =head3 Event Architecture TODO - explain, possibly using IRC as a metaphor Events (and my current preference for ambient event generation) Channels Publishers Subscribers =head3 RESTless Controllers The following is the C controller from the Bavl project. It is included here to give you something to ponder while I think about how to explain this better. (I'm figuring this out as I go along.) C( Event => [ '/@event' ], get => sub { warn "coro [$Coro::current]"; my ($self) = shift; my $input = $self->input; my $cr = $self->cr; my @ch = channels($input->{channels}); my $last = time; while (1) { # Output warn "top of loop"; my @events = grep { defined } map { my $ch = $bavl->channels->{$_}; $ch->read } @ch; my $x = async { warn "printing..."; $cr->print(encode_json(\@events)); }; $x->join; # Hold for a brief moment until the next long poll request comes in. warn "waiting for next request"; $cr->next; $last = time; my $channels = [ $cr->param('channels') ]; @ch = channels($channels); # Try starting up 1 coroutine per channel. # Each coroutine will have the same Coro::Signal object => $activity. my $activity = Coro::Signal->new; my @coros = map { my $ch = $bavl->channels->{$_}; async { $ch->signal->wait; $activity->broadcast }; } @ch; # The first one who sends a signal to $activity wins. warn "waiting for activity on any of (@ch)"; $activity->timed_wait(20); # Cancel the remaining coros. for (@coros) { $_->cancel } } }, # The current POST action exists for debugging purposes, only. # In practice, channel updates will happen ambiently # when model data changes. # Hooks will be put into place to facilitate this. # # In the future, the POST action may be used as a notification # to the server side that $.ev.stop() happened # on the client side. post => sub { my ($self) = shift; my $input = $self->input; my $ch = $bavl->channels->{ $input->{channels} }; if ($ch) { $ch->write({ type => 'time', value => scalar(localtime) }); } 1; }, queue => { get => 'event' }, ), This might look scary, but if we're lucky, we'll be able to turn this into a reusable component. =head3 Long Polling with jQuery on the Client Side TODO jquery.ev.js $.ev.loop('/@event') $.ev.stop(); =head2 How to Set Up Sessions =head3 Continuity and Process Memory Pure Continuity apps typically don't use persistent session storage, because they can use lexically scoped variables instead. However, Squatting apps are RESTful and stateless by default, so you can't count on the lexical scope of a controller to stick around between requests. Luckily, package variables *will* stick around, so that's what we'll use to implement persistent sessions. package App; our %state; sub service { my ($app, $c, @args) = @_; my $cr = $c->cr; my $sid = $cr->{session_id}; if (defined $sid) { $c->state = $state{$sid} ||= {}; } $app->next::method($c, @args); } Here, we override service() in the main module of our app so that $c->state will provide a hashref whose values will persist between requests. Note that instead of writing C<$app-ESUPER::service>, we have to write C<$app-Enext::method>, because Squatting is a sublcass of L. =head3 When Squatting::On::Catalyst When squatting on top of Catalyst, the Catalyst session becomes C<$self-Estate> in Squatting. The session storage code in Catalyst is very mature, so it is highly recommended that all the session setup be done on the Catalyst side. =head3 Sessions From Scratch The challenge is to find a way to assign unique session ids to each visitor and use that session id as a key into a persistent store. TMTOWTDI =head2 How to Use Various Templating Systems With Squatting =head3 HTML::AsSubs I like L for the following reasons: =over 4 =item * It works as advertised. =item * The implementation is really small. =item * It seems to be widely deployed (even though no one uses it). =item * And generating HTML with code eliminates the need to install template files. =back The documentation is up-front about some of the module's shortcomings which I appreciate. However, the docs go a bit too far and recommend that this module not even be used! It says that there are "cleaner" alternatives, but when I looked at them, I came straight back to HTML::AsSubs. I think the module works just fine, and I'd like to show you how I use it. =head4 Addressing HTML::AsSubs Shortcomings (Alleged and Otherwise) =over 4 =item The exported link() function overrides the builtin link() function. Noted. You shouldn't be calling the builtin C in view code anyway, so it's not a big deal. =item The exported tr() function must be called using &tr(...) syntax. This is because it clashes with the builtin tr/../../ operator. I can live with this. =item Problem: exports so damned much. (from the code comments) The funny thing is, it's actually not exporting enough. It's missing subs for the C, C, and C tags. sub span { HTML::AsSubs::_elem('span', @_) } sub thead { HTML::AsSubs::_elem('thead', @_) } sub tbody { HTML::AsSubs::_elem('tbody', @_) } If there are any other missing tags, you know what to do. =back There's one more pseudo-tag that I like to add for practical reasons. sub x { map { HTML::Element->new('~literal', text => $_) } @_ } Normally, HTML::AsSubs will entity escape all the text that you give it. However, there are many times when you legitimately don't want text to be entity escaped, so that's what C is for. =head4 An Example View That Uses HTML::AsSubs package App::Views; use strict; use warnings; use Squatting ':views'; use HTML::AsSubs; sub span { HTML::AsSubs::_elem('span', @_) } sub thead { HTML::AsSubs::_elem('thead', @_) } sub tbody { HTML::AsSubs::_elem('tbody', @_) } sub x { map { HTML::Element->new('~literal', text => $_) } @_ } our @V = ( V( 'html', layout => sub { my ($self, $v, $content) = @_; html( head( title( $v->{title} ), style(x( $self->_css )), ), body( x( $content ) ) )->as_HTML; }, _css => sub {qq| body { background : #000; color : #f5deb3; } |}, home => sub { my ($self, $v) = @_; h1( $v->{message} )->as_HTML; }, ), ); 1; Again, the nicest part about generating HTML from code is that you don't have to worry about installing template files. The templates are in memory as perl expressions. When building web apps that are designed to be embedded, this is a really nice feature to have as it makes deployment that much easier. If HTML::AsSubs is a bit too low tech for you, there are more modern expressions of the code-to-html idea on CPAN. For example, L and L may be worth looking into. I'm happy with L, though. =head3 Tenjin Tenjin is the fastest templating system that no one outside of Japan seems to know about. It's really unfortunate that this module isn't on CPAN, but hopefully this will be rectified in the near future. Until then, you can download it from L. =head4 An Example View That Uses Tenjin First, make sure your template_path is configurable for deployment purposes. package App; our %CONFIG = ( template_path => './www' ); And here is the actual view: package App::Views; use strict; use warnings; no warnings 'once'; use Squatting ':views'; use Tenjin; # make functions defined in this package available to templates use base 'Tenjin::Context'; eval $Tenjin::Context::defun; $Tenjin::CONTEXT_CLASS = 'App::Views'; our @V = ( V( 'tenjin', tenjin => Tenjin::Engine->new({ path => [ $App::CONFIG{template_path} ], postfix => '.html' }), layout => sub { my ($self, $v, $content) = @_; my $tenjin = $self->{tenjin}; $v->{content} = $content; $tenjin->render(":layout", $v); }, _ => sub { my ($self, $v) = @_; my $tenjin = $self->{tenjin}; $v->{self} = $self; $tenjin->render(":$self->{template}", $v); } ), ); 1; That's all there is too it. Views for other file-based templating systems will follow a similar pattern where the special C<_> template is used to map method names to filenames. =head3 Template Toolkit L