Currently, before
runs once we have matched a route and after
runs only after the processing of a
matched route. This is a useful semantic. It's perhaps not the best default semantic. Of note, it
means that if you use before
with a piece of authentication or authorization middleware, it will
be too late, since the route matching may include matching on the session. Similarly, after
can
not be used to rewrite errors into appropriate behavior (for example, 401 into a redirect to an error
page), because if a route was not matched due to failing authorization then we'd not run the after
.
Thus, instead of writing:
my $app = route {
before Some::Auth::Middleware.new(...);
after { redirect '/login' if .status == 401 }
get -> LoggedIn $user, 'foo' {
...
}
}
One must write:
my $app = route {
before Some::Auth::Middleware.new(...);
after { redirect '/login' if .status == 401 }
delegate <*> => route {
get -> LoggedIn $user, 'foo' {
...
}
}
}
This is awkward to have to explain. It will become a FAQ, and the solution will be grudgingly copied and pasted. Heck, I designed the thing and I'm tired of working around it and explaining it, and this is when we only have a small userbase of patient early adopters. :-)
A separate problem is that before
and after
only apply to things textually after them. While the
above was by-design behavior that I'm now reconsidering, this one is just an implementation accident.
The current semantics of before
and after
will remain available, but take the names before-matched
and after-matched
- the past tense of "matched" hopefully providing a clue that they are applying
to matched routes. The include
semantics will be preserved too - that is, the before-matched
and after-matched
of an include
d route block will only apply to those rules inside of the block.
The only difference will be that ordering no longer matters - that is, both before-matched
and
after-matched
will apply to every route in the enclosing route
block, not just those that are
textually after it.
The names before
and after
will be recycled. They will quite literally mean "insert this into the
pipeline before the route processing" and "insert this into the pipeline after the route processing".
Whether a route in the route
block matches or not will not be relevant. This means that the example
given earlier for authorization will Just Work.
Unfortunately, we're left with a new design problem. Consider the following:
my $app = route {
get -> 'foo', 'bar' { ... }
include route {
before Blah::Blah::Middleware.new;
get -> 'foo' { ... }
}
}
}
Recall that include
is much like a textual include, and - most significantly - the matching is done
by a single matcher. Thus, before
and after
would have to mean adding to the pipeline before and
after the route
block that does the include
. This is either:
- Sensible enough, given that we describe
include
as "textualish". You could even use it to factor out a set of middleware applications. - Confusing. For one, we do make
before-matched
andafter-matched
only affect things in the included block, and that feels right. But there's no way we could do that for the newbefore
andafter
semantics.
I'm worried people will write stuff like:
my $admin-stuff = route {
before Some::Auth::Middleware;
get -> Admin $user { ... }
get -> Admin $user, 'users' { ... }
}
my $app = route {
...
include admin => $admin-stuff;
}
And then get surprised that before
and after
apply to the entire outer route block. Probably they
actually wanted to use delegate
:
my $admin-stuff = route {
before Some::Auth::Middleware;
get -> Admin $user { ... }
get -> Admin $user, 'users' { ... }
}
my $app = route {
...
delegate <admin *> => $admin-stuff;
}
Probably the most conservative option is to produce an error if the target of an include
has
before
or after
, explaining why that's a problem, and suggesting either delegate
or a
switch to before-matched
and after-matched
.
jnthn - I kind of understand the inside/outside problem here. Also the tension between the simplicity of a textual include and then the temporal nature of request/pipeline processing. Aargh - the waterbed of complexity - messing up the smooth sheets again! ;-)
Just a couple of thoughts. Apache mod_perl was a beast - one redeeming feature however, was the clear Apache request cycle - that mod_perl integrated with. A middleware developer could write hooks for different phases of the cycle.
As in Perl 6 - maybe phasers could help with including something textually but operating at a different time? The middleware 'phasers' could fire in response to events in the request processing cycle. The potential downside here is the hidden complexity of stacked phase handlers and their interactions etc. Hope this helps in some way though ...
Thanks for Cro.