[Catalyst] Thanks! (was: [ANNOUNCE] Catalyst-Runtime 5.90030)

Aristotle Pagaltzis pagaltzis at gmx.de
Sat Apr 13 04:07:37 GMT 2013


* Dimitar Petrov <mitakaa at gmail.com> [2013-04-12 19:25]:
> The Catalyst team is proud to announce that the latest version of
> Catalyst (5.90030) is released on CPAN.

Oh frabjous day!

>   - make $app->uri_for and related methods return something sane, when
>     called as an application method, instead of a context method.  Now
>     if you call MyApp::Web->uri_for(...) you will get a generic URI
>     object that you need to resolve manually.

Calloo!

Just to give you and idea of what this did for me, let me show here some
sample code.

Background: I recently split my MyApp into MyApp and MyPublicApp, both
loaded within the same app.psgi, with different requests dispatched to
each using Plack::App::URLMap. This cleaned up a previously very hairy
and precarious dispatch situation wherein I had a few custom attributes
to make sure that requests would dispatch only along certain chains in
the app depending on the hostname they were for and whether or not they
used SSL. It was a massive win. But I needed a way to generate URIs for
MyApp from within MyPublicApp’s templates.

At first I somewhat punted on the question, and just started with this:

    sub uri_for_fileticket {
        my $c = shift;
        my ( $file_id, $ticket_id, $filename ) = @_;
        sprintf 'https://%s/file/%d/ticket/%d/%s', (
           $c->config->{'vhosts'}{'application'},
           $file_id,
           $ticket_id,
           URI::Escape::uri_escape( $filename ),
        );
    }

And in some places I didn’t use uri_for at all, just reluctantly hard-coded
the URIs.

But soon I ran into needing to link to other actions with variable arguments,
and I sure wasn’t going to write more of the above.

Cue the first, appalling hack:

    package MyPublicApp;
    use MyApp ();
    use HTTP::Request ();
    use HTTP::Response ();
    use HTTP::Message::PSGI ();
    sub uri_for_app_action { # XXX terrible hack magick:
        # make a mock request to the backend app
        # for a special action which captures the context into the PSGI env
        # where we can then pick it up and use it to generate URIs
        my $c = shift;
        state $app = MyApp->psgi_app;
        my $env = HTTP::Request->new( GET => 'http://localhost/ctx' )->to_psgi;
        my $res = HTTP::Response->from_psgi( $app->psgi_app->( $env ) );
        my $hostname = $c->config->{'vhosts'}{'application'};
        my $c = delete $env->{'myapp.ctx'}; # break the ref cycle to avoid leaking memory
        $c->uri_for_action( @_ )->rel->abs( "https://$hostname/" );
    }

    package MyApp::Controller::Root;
    sub ctx : Chained {
       my $self = shift;
       my ( $c ) = @_;
       $c->engine->env->{'myapp.ctx'} = $c;
       $c->res->status( 404 );
       $c->res->body( '' );
    }

Yeughw… eughw. Yick. Yew. But it worked.

Note that omitted here are various other bits elsewhere that make sure
to skip auth, logging, etc. when dispatch goes to this `/ctx` action.

At some point this action and its assorted supporting bits mutated into
an `if` block within `around dispatch` within MyApp.pm, which at least
removed some litter from the code.

The terrible ugliness of this hack kept bugging me though. Finally I had
enough and decided see whether Catalyst exposed enough enough public
methods to reimplement the relevant-to-me bits of `uri_for` to do its
job without depending on a context and without too much hassle. Well…
see for yourself.

    sub uri_for_app_action {
       my $c = shift;
       my ( $path, $captures ) = @_;
       state $dispatcher = MyApp->dispatcher;
       my $action = $dispatcher->get_action_by_path( $path );
       my @args = splice @$captures, $dispatcher->expand_action( $action )->number_of_captures;
       unshift @args, '' if @args; # ensure leading "/" if any args, omit if none
       return 'https://'
           . $c->config->{'vhosts'}{'application'}
           . $dispatcher->uri_for_action( $action, $captures )
           . join '/', map { s/\?/%3F/g; $_ } my @copy = grep { defined } @args;
    }

OK, could’ve been worse I suppose. And at least this did get me rid of
the `/ctx` thing. Good thing I only use Chained dispatch, which meant
there was lots of crud in `uri_for` that my emulation of it didn’t need
to bother duplicating, otherwise this could not have been a win.

And now? I just upgraded to 5.90030 and immediately took advantage:

    sub uri_for_app_action {
        my $c = shift;
        MyApp->uri_for_action( @_ )
            ->abs( 'https://' . $c->config->{'vhosts'}{'application'} );
    }

Much better!! So, so, so much, much, much better! Just as it should have
been (able to be) from the start, that is how it is now.

>   - Added cpanfile as a way to notice we are a dev checkout.

Callay!

(You don’t want to see the hack I had in Makefile.PL to make it quicker
to skim and write the dep list. :-) Though that one was, of course, not
anywhere near as bad as the above.)

Thanks a whole bunch,
-- 
Aristotle Pagaltzis // <http://plasmasturm.org/>



More information about the Catalyst mailing list