Routes/matching other parts of request

6 views
Skip to first unread message

Ian Bicking

unread,
Jan 29, 2006, 4:55:14 PM1/29/06
to pylons-...@googlegroups.com
It occurs to me that if Routes could match other parts of the request
than the path, that would allow some interesting RESTish possibilities.
For instance:

# virtual host:
m.connect('/*path_info', HTTP_HOST='myhost.com', ...)

# REST verbs:
m.connect('/article', REQUEST_METHOD='GET', controller='article.get')
m.connect('/article', REQUEST_METHOD='POST', controller='article.post')

# variable verbs?
m.connect('/article', controller='article.:REQUEST_METHOD')

I don't know how that last one would work. It could potentially simply
be built into how 'controller' is interpreted too, but the first REST
example seems more reasonably at the Routes level. Also, this is mixing
restrictions with variable assignments. Maybe there's a better way to
phrase these as simple restrictions, and then this just turns into a
recipe instead of a particular feature of Routes.

I've also been thinking about how to interpret things like "controller"
and "action". Right now I'm just thinking that
controller_prefix.controller.action forms the path to the object that is
served (without saying anything about what that object is -- that isn't
really relevent here). Maybe one way would be to allow setting some of
these variables from the request in the route...

m.connect('/article', controller='article', action=':REQUEST_METHOD')

That's a little ambiguous to me, and I don't like overloading the simple
way strings are handled now with an expression embedded in the string
(in this case using a special character to indicate it is a key in the
environ). I also don't want to make this too complicated. But lessee...

m.connect('/article', controller='article',
action=getenv('REQUEST_METHOD'))

def getenv(key):
def setfunc(route):
return route.environ[key]
return setfunc

Of course, I don't think the route actually has an environ attribute.
But environ could also be an argument. I assume this function could
also raise a special exception to indicate that the route failed, thus
it could implement both restriction and dynamicism. I would actually
have to be bidirectional to generate URLs as well... what it's output
looks like in the other direction, I'm not sure. At that point it is
similar to a FormEncode validator; but all a FE validator really is is
two methods -- to_python and from_python. Creating URLs complicates the
other parts too, I realize. Like if REQUEST_METHOD is used for
dispatching, it might be required to indicate what method the URL will
be used for when generating it (e.g., if you are using the URL for a
post form action, the match might be different than for a simple link).

--
Ian Bicking | ia...@colorstudy.com | http://blog.ianbicking.org

Ben Bangert

unread,
Jan 30, 2006, 3:05:40 PM1/30/06
to pylons-...@googlegroups.com
On Jan 29, 2006, at 1:55 PM, Ian Bicking wrote:

> It occurs to me that if Routes could match other parts of the
> request than the path, that would allow some interesting RESTish
> possibilities. For instance:
>
> # virtual host:
> m.connect('/*path_info', HTTP_HOST='myhost.com', ...)

I can see the uses for this when using Routes as a dispatcher in pure
WSGI, obviously its usefulness is lower if not applicable at all
inside a web application, especially if you consider that the person
writing the webapp (thus the routes) may not be the person deploying
it so they'd have no way of knowing what hosts the end-user will have.

> # REST verbs:
> m.connect('/article', REQUEST_METHOD='GET',
> controller='article.get')
> m.connect('/article', REQUEST_METHOD='POST',
> controller='article.post')
> # variable verbs?
> m.connect('/article', controller='article.:REQUEST_METHOD')
>
> I don't know how that last one would work. It could potentially
> simply be built into how 'controller' is interpreted too, but the
> first REST example seems more reasonably at the Routes level.
> Also, this is mixing restrictions with variable assignments. Maybe
> there's a better way to phrase these as simple restrictions, and
> then this just turns into a recipe instead of a particular feature
> of Routes.

Yes, I would think that they should be in a http_requirements
variable like how regexp requirements are in a 'requirements'
variable for the route. Alternatively, to keep the system more
pluggable for requirements, rather than having Routes become aware of
HTTP protocol, there could be a 'requiment_check' variable for the
Route that the user supplies in the form of a callable.

This way you could do whatever requirement check you wanted to
enforce for the route, though for the function to get at the environ,
you'd have to pass it a reference in advance to whatever was going to
hold it, or have a thread-local available thats populated before the
matching... That shouldn't be too much of a problem I think.

The variable verbs does present more of a problem, the example given
there I would typically have the controller handle as it starts to
complicate Routes significantly and would be easy to handle in the
Controller.

I think this might illustrate why this type of refined requirement
parsing might be better left to the controller/action to determine. I
have no idea how url_for would be used within your templates since it
would now need to know from the context of its call (is it in a form
or link?) to figure out what URL generation possibilities exist. I
can also envision many ways in which it would be impossible during
the URL generation, for the end-function to determine its actual
context which could easily result in generation of URL's that won't
get to the intended location.

What would likely work better (and be less troublesome for Routes),
is some decorators for controllers/actions that implement REST
requirements, like:

controller TestController(BaseController):
@pylons.rest.restrict(method='POST')
def save(self):
# do something that should only be done on POST

For the other case you cited, dispatching to an action based on the
method, perhaps

@pylons.rest.dispatch_on(method='POST', action='_edit_post')
def edit(self):
# do this edit on GET

def _edit_post(self):
# this will be done when action is 'edit', but method is POST

Obviously the decorators would gain access to the environ depending
on your framework of choice, in this case Pylons. URL generation
should still be fine in both cases above as well.

Cheers,
Ben

Ian Bicking

unread,
Jan 30, 2006, 3:36:16 PM1/30/06
to pylons-...@googlegroups.com
Ben Bangert wrote:
>
>> # REST verbs:
>> m.connect('/article', REQUEST_METHOD='GET', controller='article.get')
>> m.connect('/article', REQUEST_METHOD='POST',
>> controller='article.post')
>> # variable verbs?
>> m.connect('/article', controller='article.:REQUEST_METHOD')
>>
>> I don't know how that last one would work. It could potentially
>> simply be built into how 'controller' is interpreted too, but the
>> first REST example seems more reasonably at the Routes level. Also,
>> this is mixing restrictions with variable assignments. Maybe there's
>> a better way to phrase these as simple restrictions, and then this
>> just turns into a recipe instead of a particular feature of Routes.
>
>
> Yes, I would think that they should be in a http_requirements variable
> like how regexp requirements are in a 'requirements' variable for the
> route. Alternatively, to keep the system more pluggable for
> requirements, rather than having Routes become aware of HTTP protocol,
> there could be a 'requiment_check' variable for the Route that the user
> supplies in the form of a callable.
>
> This way you could do whatever requirement check you wanted to enforce
> for the route, though for the function to get at the environ, you'd
> have to pass it a reference in advance to whatever was going to hold
> it, or have a thread-local available thats populated before the
> matching... That shouldn't be too much of a problem I think.

As comes up later in this email, the environ stored in that thread local
(which represents the current request) is not necessarily associated
with the environ that will used when the next route is calculcated, so
url_for will potentially be broken.

> I think this might illustrate why this type of refined requirement
> parsing might be better left to the controller/action to determine. I
> have no idea how url_for would be used within your templates since it
> would now need to know from the context of its call (is it in a form or
> link?) to figure out what URL generation possibilities exist. I can
> also envision many ways in which it would be impossible during the URL
> generation, for the end-function to determine its actual context which
> could easily result in generation of URL's that won't get to the
> intended location.

Maybe a set of variables/dictionary could be matched against, in
addition to the path by itself. I imagine that the mapper would be set
up with a set of default values (e.g., {"REQUEST_METHOD": "GET"}), and
match would be called with another dictionary of values (e.g.,
wsgi_environ), and url_for would take an optional dictionary of values
(which would be merged with the default values).

I'm thinking about this in terms of a fairly light object publishing
system, like CherryPy's, with Routes attached. But even moreso as an
RPC framework, where you use a route to point to an object, and that
object may not know much about HTTP (unlike a typical controller).

--
Ian Bicking / ia...@colorstudy.com / http://blog.ianbicking.org

Reply all
Reply to author
Forward
0 new messages