New feature in Dancer2: to_app
Dancer2 0.151000 has made a major change to the dispatching mechanism and
provided a new method: to_app
. What was the change, why was this keyword
introduced, and why should we use it now?
The way things were
The original dispatching mechanism in Dancer2 would take all of your Dancer2 applications and wrap them with a loop that tries to match their routes. Once you know that a Dancer App is basically a unit of routes with its own set of engines (such as a template, a session storage, a logger, and a serializer - not all required), you might begin to see a problem here.
If you have two applications (meaning two packages that have use Dancer2
in
them) called MyApp::A
and MyApp::B
, and you call the dance
keyword
to create a full fledged PSGI application from them, Dancer will try to match
a request against each one. It will start with MyApp::A
and then MyApp::B
.
"Is it always in this order?", you might ask. Actually, it isn't. Since it is internally stored in an array (which is at least deterministic, right?), it is actually checked in the order in which you loaded them into memory.
If your handler is:
use MyApp::B; use MyApp::A; use Dancer2; dance;
It will now check MyApp::B
first and MyApp::A
last.
In fact, it will check MyApp::B
first, MyApp::A
second, and the last
will be whatever package this snippet of code you saw is in - in case it
has any routes configured in it before dance
was called.
Why is this bad?
So you might be wondering, "Okay, there's an order issue here, but why should I care? Eventually it reaches my route and works fine."
You are absolutely right - in most cases. However, if both applications have the same route defined, you won't necessarily know which route was actually hit.
If you're preloading applications into memory (which is a good idea in large infrastructures), your resulting application might hit someone else's route instead.
If you're using the AutoPage feature, you might snag on a different app's template. If one app has a Serializer defined, you might return serialized output. If different apps have a different Session engine, that's even worse. That's tantamount to a security risk - although we try to minimize that risk internally by moving session objects around so you don't hit a mistaken session.
What can we do?
The first change we made, back in Dancer2 0.150000, was to call the
psgi_app
keyword with specific application names, either by class name or
by a regular expression. This allowed you to create PSGI applications from
specific Dancer Apps:
# preload everything use MyApp::A; use MyApp::B; use MyApp::C; use MyApp::D; use MyApp::E; # an app for A, B, C my $abc_app = Dancer2->psgi_app([ qr{^MyApp::[ABC]$} ]); # an app for D, E my $d_app = Dancer2->psgi_app([ 'MyApp::D', 'MyApp::E' ]);
This allows us to load into memory all the applications we have and then create a PSGI application from only some of them.
Here is another example which also joins multiple apps into a single, bigger PSGI app with Plack::Builder:
use MyApp::A; use MyApp::B; use MyApp::C; use Plack::Builder; builder { mount '/' => Dancer2->psgi_app(['MyApp::A']); mount '/special' => Dancer2->psgi_app([ qr{^MyApp::[BC]$} ]); };
This was a major improvement, but it still didn't address the core problem.
Fine. I'll bite. What was the core problem?
I'm glad you asked!
The code problem was that the dispatching was the duty of the core dispatcher object (an instance of Dancer2::Core::Dispatcher) instead of the app object itself (Dancer2::Core::App). Since the routing should have been done in the app object (the lowest context for engines and routes), it made sense to move it there instead of the dispatcher object.
We've done so in Dancer2 0.151000, a version later.
While retaining the old behavior of the psgi_app
keyword, we introduced
one major addition: the to_app
class method.
to_app or not to_app
While perhaps not the same question Shakespeare asked, it is a noble one just the same.
The to_app
method creates a PSGI app from a single application - not from
all of your applications or a PSGI app that is restricted to specific ones.
This means you can now treat a Dancer2 App as its own unit that can be composed into any path you like.
The following:
MyApp->to_app;
gives the same behavior to the user as:
Dancer2->psgi_app(['MyApp']);
except it does it using quite a few workarounds.
Instead of using the awkward Dancer2->psgi_app()
, which
provides the same behavior as dance()
, you could use to_app
. This
won't require calling use Dancer2
in your handler, since it's a method
on your Dancer2 application class.
When you have multiple Dancer2 applications, you can compose them together using either Plack::App::URLMap or the wrapper syntax for it available in Plack::Builder:
use MyApp::Main; use MyApp::Admin; builder { mount '/' => MyApp::Main->to_app; mount '/admin' => MyApp::Admin->to_app; };
The effect of the mounting is that these applications will be completely separate and Plack::Builder will assure only the appropriate application handles a given request.
Instead of creating a loop around all of them, you provide each app with its own dispatching mechanism, in charge only for the routes defined inside it.
Another effect of mounting is that the mounting point (/admin
, for example)
is stripped from the path the application needs to match. This means that
MyApp::Admin
doesn't need to define /admin/view
, but just /view
.
This has become such a useful and important feature that if you try to call
psgi_app
on an application (instead of on Dancer2
or
Dancer2->runner
, it will effectively call to_app
underneath.
Conclusion
In short, yes. We do want to_app
. :)
Author
This article has been written by Sawyer X for the Perl Dancer Advent Calendar 2014.
Copyright
No copyright retained. Enjoy.
2014 // Sawyer X <xsawyerx@cpan.org>