The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Squatting::Cookbook - Web Development Techniques for Squatting

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.

The Anatomy of a Squatting Application

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;

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;

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;

PROGRAMMING TECHNIQUES

COMET

Event Architecture

TODO - explain, possibly using IRC as a metaphor

Events (and my current preference for ambient event generation)

Channels

Publishers

Subscribers

RESTless Controllers

The following is the Event 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.

Long Polling with jQuery on the Client Side

TODO

jquery.ev.js

  $.ev.loop('/@event')
  $.ev.stop();

How to Set Up Sessions

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 $app->SUPER::service, we have to write $app->next::method, because Squatting is a sublcass of Class::C3::Componentised.

When Squatting::On::Catalyst

When squatting on top of Catalyst, the Catalyst session becomes $self->state 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.

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

How to Use Various Templating Systems With Squatting

HTML::AsSubs

I like HTML::AsSubs for the following reasons:

  • It works as advertised.

  • The implementation is really small.

  • It seems to be widely deployed (even though no one uses it).

  • And generating HTML with code eliminates the need to install template files.

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.

Addressing HTML::AsSubs Shortcomings (Alleged and Otherwise)

The exported link() function overrides the builtin link() function.

Noted. You shouldn't be calling the builtin link() in view code anyway, so it's not a big deal.

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.

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 span, thead, and tbody 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.

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 x() is for.

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, Template::Declare and HTML::Tiny may be worth looking into. I'm happy with HTML::AsSubs, though.

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 http://www.kuwata-lab.com/tenjin/.

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 _ template is used to map method names to filenames.

Template Toolkit

Template Toolkit is probably the most popular templating system in use by the Perl community as of this writing. This is one way you could implement a view for it:

  package App::Views;
  use strict;
  use warnings;
  use Squatting ':views';
  use Template;

  our @V = (
    V(
      'html',
      tt => Template->new($App::CONFIG{tt_config}),

      layout => sub {
        my ($self, $v, $body) = @_;
        my $tt = $self->{tt};
        $v->{body} = $body;
        my $output;
        $tt->process('layout' . $App::CONFIG{tt_postfix}, $v, \$output);
        return $output;
      },

      _ => sub {
        my ($self, $v) = @_;
        my $tt = $self->{tt};
        $v->{R} = \&R;
        my $output;
        $tt->process($self->{template} . $App::CONFIG{tt_postfix}, $v, \$output);
        return $output;
      },
    ),
  );
  1;

Credit for this example goes to draegtun. http://draegtun.wordpress.com/2008/10/21/using-template-toolkit-with-squatting/

HTML::Mason

TODO

HTML::Template

HTML::Template is one of the strictest templating systems around. There is very little processing you can do from within the templates, so you're really forced to do all your data manipulation BEFORE the templating system sees it. Some people like this hard separation, and if you're one of them, here is how you'd make use of HTML::Template from within Squatting.

  package App::Views;
  use strict;
  use warnings;
  use Squatting ':views';
  use HTML::Template::Pro;

  our @V = (
    V(
      'html',
      layout => sub {
        my ($self, $v, $content) = @_;
        my $root = $App::CONFIG{root};
        my $t = HTML::Template::Pro->new(filename => "$root/layout.html");
        $v->{content} = $content;
        $t->param(%$v);
        $t->output;
      },
      _ => sub {
        my ($self, $v) = @_;
        my $root = $App::CONFIG{root};
        my $template = $self->{template};
        my $t = HTML::Template::Pro->new(filename => "$root/$template.html");
        $t->param(%$v);
        $t->output;
      },
    )
  );
  1;

XML::Atom

TODO - This is not a templating system, but it's useful to know how to generate well-formed Atom feeds, so I'm going to include it in this section as well.

Views are not just for HTML....

How to Take Advantage of Having Multiple Views

In the documentation for the Squatting module, it said that multiple views per app were supported, and that it was "kinda like Catalyst (but not quite)". Catalyst also supports multiple views per app, so there are certain techniques that both frameworks can implement.

Kinda Like Catalyst -- Views as Data Formats

But Not Quite -- Multiple Views of the Same Format == Themes

How to Internationalize and Localize Squatting Apps

The longer you wait to internationalize a web application, the harder the task becomes due to the ever increasing number of strings being used. Thus, if you have any ambition of catering to an international audience, it would be wise to internationalize your application right from the beginning when the task is at its easiest.

Using Subdomains to Determine Language Preference

First, we need a high-level strategy for determining what language to present to the user. Wikipedia's approach of using 2-letter language codes in their subdomains is my favorite way of doing this. (For example, the English version of Wikipedia is at http://en.wikipedia.org/ and the Korean version of Wikipedia is at http://ko.wikipedia.org/.)

I like this approach for a number of reasons.

Visitors have control over what language to use.
The URLs look nice while remaining easy to manage.
It's search engine friendly.

To make our Squatting apps aware of what subdomain was requested, the service() method can be overridden as follows:

  package App;
  use strict;
  use warnings;
  use base 'Squatting';

  use App::L10N;
  use I18N::LangTags::List;

  sub translation_function {
    my ($c) = @_;
    my @h   = split(/\./ => $c->env->{HTTP_HOST});
    my $lang_tag = I18N::LangTags::List::name($h[0]) || 'en';
    my $l10n     = App::L10N->get_handle($lang_tag);
    sub { $l10n->maketext(@_) if @_ };
  }

  sub service {
    my ($app, $c, @args) = @_;
    $c->v->{tr} = translation_function($c);
    $app->next::method($c, @args);
  }

The important code is in translation_function($c).

Creating Localization Classes with Locale::Maketext

Handling UTF-8 Input Correctly

How to be an OpenID Consumer

TODO - go into much more detail and clean up the code.

helper function for making a Net::OpenID::Consumer object

  sub csr {
    my ($self) = @_;
    return Net::OpenID::Consumer->new(
      ua    => LWPx::ParanoidAgent->new,
      cache => Cache::File->new(cache_root => '/tmp/openid-consumer-cache'),
      args  => $self->input,
      consumer_secret => '...',
      required_root   => 'http://work:4234/'
    );
  }

Login controller; form is provided somewhere else; POST is the entry point; GET is where the sequence finishes.

  C(
    Login => [ '/login' ],
    get => sub {
      my ($self) = @_;
      my $csr = csr($self);
      $self->headers->{'Content-Type'} = 'text/plain';
      if (my $setup_url = $csr->user_setup_url) {
        # redirect/link/popup user to $setup_url
        $self->redirect($setup_url);
        return;
      } elsif ($csr->user_cancel) {
        # restore web app state to prior to check_url
        return "user_cancel";
      } elsif (my $vident = $csr->verified_identity) {
         my $verified_url = $vident->url;
         return "verified_url $verified_url !";
      } else {
         return "Error validating identity: " . $csr->err;
      }
    },
    post => sub {
      my ($self) = @_;
      my $input = $self->input;
      my $csr = csr($self);
      my $claimed_identity = $csr->claimed_identity($input->{openid});
      my $check_url = $claimed_identity->check_url(
        return_to  => "http://work:4234/login",
        trust_root => "http://work:4234/",
      );
      $self->redirect($check_url);
    },
  ),

How to be an OpenID Provider

How to Compose Multiple Squatting Apps Into One App

*BEFORE* you init the app, you can mount other apps like this:

  App->mount('OtherApp0' => '/prefix0');
  App->mount('OtherApp1' => '/prefix1');
  App->mount('OtherApp2' => '/prefix2');

Once you're done mounting, run init:

  App->init();

Finally, if you need to relocate, do that after you finish mounting. Remember: the order is always mount, init, and relocate. Also, remember that an app can only be mounted once, and relocate should only be called once.

How to Embed a Squatting App Into Other Frameworks

In order to embed a Squatting app into an app written in another framework, we need to be able to do the following things.

get incoming CGI parameters
get incoming HTTP request headers
get incoming HTTP method
set outgoing HTTP status
set outgoing HTTP response headers
set outgoing content

If we can do all these things, Squatting can make itself at home. Here are some concrete examples to get you started.

Catalyst

To embed a Squatting app into a Catalyst app, you can add code like this to your Root controller.

  use App 'On::Catalyst';
  App->init;
  App->relocate('/somewhere');
  sub somewhere : Local { App->catalyze($_[1]) }

If you want the Squatting app to be completely in charge, you don't even have to relocate() -- just redefine the default() method like this:

  use App 'On::Catalyst';
  App->init;
  sub default : Private { App->catalyze($_[1]) }

HTTP::Engine

Running an app on top of HTTP::Engine is accomplished by using the Squatting::On::HTTP::Engine module like this:

  use App 'On::HTTP::Engine';
  App->init;
  App->http_engine(
    interface => 'ServerSimple',
    args      => {
      host => 'localhost',
      port => 2222,
    }
  )->run;

Squatting::On::HTTP::Engine supports many other interfaces such as FCGI and ModPerl (for Apache 2.2 only), so please consult the docs for this module if this method of deployment interests you.

Mojo

TODO

Jifty

TODO

Raw mod_perl1

TODO

Raw mod_perl2

TODO

HTML::Mason

TODO: Implement a dhandler that embeds a Squatting app

CGI

To run a Squatting app in a CGI environment, a script like the following has to be written.

  use App 'On::CGI';
  my $q = CGI->new;
  App->init;
  App->relocate('/cgi-bin/app.cgi');
  App->cgi($q);

The key to doing this right is to relocate the app correctly. The path that you relocate to should be the same as the REQUEST_PATH for the script. For example, if the URL you use to get to the script is http://localhost/cgi-bin/app.cgi, then you should relocate to /cgi-bin/app.cgi.

How to Replace a Squatting App's Layout

Now that you've embedded or composed some Squatting apps together, the next thing you'll want to do is make the whole system of sites look consistent. To do this, you'll usually get the App's first view object and replace its layout method with your own.

  my $view = $App::Views::V[0];
  $view->{layout} = sub {
    my ($self, $v, $content) = @_;
    #
    # Make the layout look however you want
    # using any templating system you want
    # ( or none at all ),
    # and return a string that wraps $content
    #
  };

DEPLOYMENT TECHNIQUES

Let Squatting+Continuity Own Port 80

This is the simplest thing you could possibly do, but it's also somewhat limiting.

Reverse Proxying to Squatting+Continuity w/ Perlbal

TODO

Reverse Proxying to Squatting+Continuity w/ nginx

TODO

Piggy-Backing on Top of Other Frameworks

If you've embedded a Squatting app into another application, the rules and conventions governing the other application's framework take precedence. Follow their deployment guidelines, and you should be fine.

SCALING TECHNIQUES

This section is for those who wish to scale Squatting apps that are using a Continuity foundation. If any part of your site is RESTless and stateful, and you've suddenly got a lot of traffic to your site, this section is for you.

Session Affinity with Multiple Instances

TODO

Linux and OpenSSI

TODO

DragonFlyBSD Single Image Cluster

This is currently science fiction.