[cgiapp] CGI::Application status update from the maintainer

Mark Stosberg mark at summersault.com
Fri Sep 14 21:22:58 EDT 2012


>> I would generally plan to keep the core small, but would welcome more
>> full-featured stacks to be shipped that were based on it, as Titanium
>> did for CGI::Application.
>
> My motivation for this is based on our needs. We use CAP to write not
> just authenticated applications, but those with role based
> authorization.  I have had moderate success using Authen and Authz,
> but in order to do what was necessary to Authen before Authz or to
> properly scale out the RBAC restrictions, I had to ditch both and
> implement my own callbacks during the prerun stage.  I suppose both
> plugins got be over the learning curve, and after I was able to
> implement authentication and authorization without the plugins.
>
> The one trouble I had with Authen and Authz is that while Authz will
> call the Authen::username method if this plugin is being used, my
> experiences lead me to believe that Authz will get called no matter if
> there is an authenticated username or not - leading to having to
> handle the case of username == undef. In otherwords, failure to
> authenticate is not triggered before Authz is attempted (thus, this
> has to be handled outside of Authen by Authz)...does this make sense?
>
> I tried to order the plugins so that Authen failed before Authz was
> attempted, but I couldn't figure out how to do this cleanly. I feel
> like this is a serious problem.

My major project also uses Authentication and Authorization.  I use and
enjoy CGI::App's Authorization plugin, but found the Authentication
plugin too cumbersone vs rolling my own, so I took the ideas from it,
and implemented my own check at the prerun stage to see if the user is
authenticated. It looks something like this (pseudo-code):

Maybe the key part is that I configure the 'Authz' plugin, so that if
authorization fails, they are redirected to authenticate.

     $self->authz('shelter_state')->config(
          GET_USERNAME       =>  ...,
          FORBIDDEN_RUNMODE  => 'redirect_to_login',
          DRIVER =>  ...,
      );

My "redirect_to_login" run mode looks like this:

   sub redirect_to_login {
       my $self = shift;

       my $q = $self->query;

       my $return_url = $q->self_url;
       $return_url = $q->escape($return_url);

       my $url = $self->cfg('ROOT_URL').'/account/login';

       $self->redirect($url."?return_url=$return_url");
   }

When the login process is done, we redirect the user back to where they
came from, and Authorization can be attempted again.

The Authenication and Authorization plugins were originally written the
same author, Cees Hek. I would be suprised to find the two plugins can't
be made to play nicely together.

Did you try re-ordering your "use" lines of the plugins to see if that
addressed your problem? Both the plugins register a callback at the
'prerun' stage when their 'import' method is called during 'use'.
Reversing the order of the 'use' lines will reverse the order they are
triggered in.

> Anyway, my main reason for recommending these 2 in particular as
> bona fide life cycle events is that the mirror Apache's life cycle.

And why is Apache's life cycle what we should be mirroring?

> I prefer it because it provided me with a significant epiphany after
> doing CGI the hard way for too long.  It's also more or less what we
> standardize on internally at cPanel.
>
> We use it to write internal applications and external facing APIs that
> require both authentication and authorization. One of our primary
> requirements is also raw speed, so even .05 seconds matters to us.
> This is the basis for my distaste for any sort of MOP layer.  However,
> I concede that in a persistent environment, start up times are
> amortized over the long term and in this way any cost eventually goes
> to 0.

It's interesting to hear that CGI::Application is a standard at cPanel!

Regarding performance, I recently benchmarked accessor generation time
for Moo vs Mouse vs Moose vs manual accessors (what CGI::App uses) and
raw hashes.

In a persistent environment like you are using Moose was generating
162,999 accessers *per second* on my laptop. I suspect that wouldn't be
your botteneck. :) This was only marginally slower than the "manual
accessors" benchmark, which would be close to CGI::Application,
delivering 187,617 accessors per second.

https://raw.github.com/gist/3431863/bf6ecdbe23ea8f97a316b2f4ac1fa211cf48ce86/gistfile1.pl

Since your application is performance-sensitive, I would be interested
to know where your profiling shows your performance bottlenecks to be.
Database access? Templating? I suspect the answer won't be in whether
your web framework is using Any::Moose or not.

>> Note that the Moose API also helps here as well, as it allows you to say
>> this:
>>
>>       # Add some additional functionality after 'setup' runs in the parent
>>       after 'setup' => sub {
>>           my $self = shift;
>>       };
>
> Thank you. Yeah, that syntax makes me cring actually :).
>
> However, this out of the box seems to provide the basis for a very
> well structured plugin system.

I noticed recently that their docs for this have disclaimers about who
it slows down the method calls some, and the more you stack before/after
hooks on a method call, the more it slows it down. I presume this is
because it's not just calling a list of method calls, but in fact is
progressively nesting method calls inside each other.

In other words, I expect our existing callback system to provide better
performance, although I like the idea of having the "after/before"
syntax available, as the performance difference won't matter in many
cases.

> I think what I was thinking of for a "more developed" plugin system is
> to provide a way to better manage when plugins are fired wrt hooks.
>
> For example, have an "after" or "before" type of modifier when
> registering a callback would be nice.  Even better would be a way to
> easily manage the handler queue associated with each hook.  It's not
> entirely clear to me how to ensure that handlerA will fire before
> handlerB; I also know from reading the documentation that a class
> level handler will fire before instance level handlers.  This might be
> by design or just a consequence of the implementation; however being
> able to control the handler execution order would be useful I believe.

I think we provide enough control, in that the developer can "use"
modules in the order they choose, or they could explicitly call
add_callback() in order they choose. (IE: use but don't import anything
from the Authz and Authen plugins and then explicitly register the
callbacks yourself, in the order you choose):

   use CGI::Application::Plugin::Authentication ();
   use CGI::Application::Plugin::Authorization ();

   __PACKAGE__->add_callback('prerun', 
\&CGI::Application::Plugin::Authentication::prerun_callback() );
   __PACKAGE__->add_callback('prerun', 
\&CGI::Application::Plugin::Authorization::prerun_callback() );

(Although, in this case, I think just putting the 'use' lines in the
desired order is sufficient.)

>>> 6*. the last mile - in application frameworks, I am unsure of any that
>>> take the finite state machine model to its logical max. This thought
>>> may be way out there, but defining things like runmodes only takes you
>>> so far. Going a step further, perhaps done through more feature dispatching
>>> or routing, it'd be really nice to be able to define the application
>>> runmodes in terms of a transition function (e.g., current runmode,
>>> input, resulting runmode). In otherwords, support defining an
>>> application to the fullest extent possible though some sort of runmode
>>> dispatch table annotations.
>>
>> I'll have to wait for the more specific proposal on this point. :)
>
> Well, I think a proposal of this magnitude would be a pretty large
> undertaking and be subverted by any use of a MOP (or maybe not); the
> idea here is to do the opposite of Moose and have a step that takes an
> application definition and generates some some fairly efficient/low
> level Perl that is maximally explicit rather than maximally implicit
> (as Moose provides); this would minize a lot of overhead. Custom code
> would be provided through module hooks so that what you're generating
> is the framework code and not the custom app code.  IDK, stay with me
> because it's stull mulling around in my head.

It turns out Stevan Little has implemented a web framework as a finite
state machine, if that's what you have in mind:

   https://metacpan.org/release/Web-Machine

My first impression was that it was suprisingly complex, as there are
dozens of state possibilities to account for. It didn't initially seem
appealing to me.

Thanks again for the continued feedback!

     Mark


More information about the cgiapp mailing list