Routes planning and the road to Routes 2.0 13

Posted by ben Wed, 29 Aug 2007 17:04:24 GMT

Routes has been a wonderfully successful project of mine as its used not just in Pylons but quite a few other small WSGI apps. It’s even been integrated as a CherryPy URL dispatching option for those using TurboGears 1.x or plain CherryPy. It’s come a long way since 1.0 and has diverged on quite a few fronts from the Rails version that it was originally duplicating in functionality, which has me thinking that perhaps its time to consider 2.0 and what that will mean.

First, I think there’s quite a few behaviors that don’t make sense and probably never have. The Route minimizing functionality sounds neat and fulfills the Rails ideal of ‘keeping urls pretty’ yet suffers from a fundamental flaw… they result in multiple valid URL’s for the same page. James Tauber covers some of the implications of this in addition to the issue with relative URL’s not working right either. The example he cites is even worse for Routes since Routes can easily result in multiple URL’s mapping to the same controller action.

Routes 2.0

To solve the multiple URL issue, and also address some Named Routes confusion that recently hit the Pylons mailing list, Routes 2.0 will do a few things differently than Routes 1.X. Many things will be significantly more explicit and more predictable as a result. In addition, I want to add more functionality so that Routes can be the end-all of URL generation in addition to URL dispatching, even for use purely generating links conditionally that aren’t even used for URL dispatching.

Some of the key changes planned for Routes 2.0:

  • Routes recognition and generation will always be explicit Currently, there’s an explicit option that removes the keyword controller/action/id defaults, 2.0 will not have these implicit defaults.
  • Defaults don’t cause minimization Defaults just mean they’ll be used, they won’t result in Route minimization which increases the amount of URL’s that end up matching to a single controller action.
  • Trailing slashes shouldn’t be an issue Without routes being minimized, a route such as ’/home/index’ will always be ’/home/index’ instead of being minimized to /home/. This will resolve the trailing slash issue.
  • Named Routes will always use the route named A named route currently just means that the defaults for that route will be used during route generation. This currently can cause confusion because people believe that the named route actually forces that route to be used during generation.
  • Generation-only routes A new option, which will result in routes that are used purely for generation. This option will likely be used primarily for static resources which may be on other servers, or may need the domain rotated so that the browser can do parallel resource loading. For example, one would be able to provide a list of domains to be used, and the generated links will rotate as desired on the page to split page resources over multiple domains.
  • Redirect routes Sometimes, (especially when replacing legacy apps), its desirable to slowly migrate URL scheme’s from the old to the new rather. While URL’s should never change, sometimes the system being replaced has horrid URL’s that violate all URL recommendations. Being able to provide a smooth migration path from the old URL’s to the new ones is handy, and permanent redirects are respected by many search crawlers as well.

Migration and Compatibility

Routes 1.8 will include options to turn on behavior that will be the default in Routes 2.0, and if you like how Routes 1.X works there’s no need to worry, it will still be maintained for the foreseeable future. It’s currently extremely stable, and has a massive unit test suite to ensure it operates as designed.

Add Your Desired Feature Here

What other features are Routes users currently looking for?

Comments

Leave a comment

  1. Avatar
    Christian Wyglendowski 43 minutes later:

    Maybe converting arguments to certain types? For example, if you have a date as part of an URL, it would be nice if “year”, “month” and “day” variables gleaned from the url could be easily converted to ints. Maybe that feature is already in, and I am just missing it – I am a fairly new Routes convert.

  2. Avatar
    Ben Bangert about 1 hour later:

    @Christian: Nope, thats not in there right now. You can specify regular expressions to limit specific sections of the URL, but it won’t auto-coerce at the moment.

    I’ve had a few requests for automatic coercion so I’ll add that to the desired featured list. :)

  3. Avatar
    Mike Orr about 2 hours later:

    +1 to explicitly support route generation as a first-class feature rather than an add-on. url_for(“name”) should use only that route, otherwise why would the user have supplied the name in the first place?

    The _static argument also needs refactoring. The leading underscore makes people uncomfortable using it. Why not a separate method like .static()? Also, why the restriction that static routes don’t participate in generation? What I want is generated static routes that don’t participate in matching, which is a different thing. If we clear up the confusion about url_for(“name”) choosing the wrong route, the need for the current _static behavior will go away, right?

    One use for generation-only routes is to point to static files (those served by StaticURLParser or Apache). You need variables if you have several such files that follow a naming pattern. Yet you don’t want them eligible for matching because the application can’t serve them anyway.

    I name all my routes even if I’m not sure I’ll ever generate them. This seems like a practice that should be encouraged in the tutorials because it avoids common mistakes and ambiguities.

    Redirect routes are definitely important. People look for a redirect map in Pylons and expect it to be part of the routing, and wonder why they have to write action methods for a bunch of legacy URLs.

    +1 to force url_for(“name”) to use the named route. Otherwise why would the user specify the name?

  4. Avatar
    Neil Blakey-Milner about 2 hours later:

    I wonder if those automatic coercion options could accept FormEncode schemas, and work for both generation (from_python) and dispatching (to_python).

    That would potentially make life very interesting.

  5. Avatar
    Ben Bangert about 3 hours later:

    @Mike: Yes, static routes definitely could use a better way to add them. I like the map.static() suggestion, and as you mention they should be fully capable of actual URL generation. PylonsHQ routing has a few odd %s hacks due to them not handling full fledged generation which I’d love to get rid of.

    Another consideration to encourage named routes, is to use a getattr override to let you name them during the map call. Ie, instead of:

    map.connect(‘routename’, ’:controller/blah’)

    You’d be able to do:

    map.routename(’:controller/blah’)

    I believe Rails does this as well, and it is rather nice to get the route name away from the immediately following string.

    Redirect routes should be easier to add now due to Routes having a middleware component. Frameworks not using Routes middleware would likely need to support some other hook so they know when Routes needs to send out a redirect.

  6. Avatar
    Robert Brewer about 4 hours later:

    All great news! Routes is a great product and I’m glad to see your plans for making it even better. :)

  7. Avatar
    Div Shekhar about 4 hours later:

    Explicit alternative for the request helper functions. E.g.:

    from routes import request_config2
    config = request_config2(mapper = m, mapper_dict = result, host = hostname, protocol = port, redirect = redir_func)
    t_redirect_to = config.redirect_to
    t_url_for = config.url_for
    

    I’d prefer not to use the implicit singleton.

  8. Avatar
    Matt Russell about 17 hours later:

    In map.resource, I would like the ability specify a custom identifier pattern template for new, edit and other actions that take an identifier.

    currently, using connect:

    map.connect(”:/controller/:action/:id”)

    I’d like to be able to customize route generation such as:

    map.resource(“thing”, “things, ident_pattern=)”)

    Does this make sense? Basically it’s to make urls harder to auto parse using a counter. For example to stop a hacker taking a url such as /members/:id and using a counter to get every thing on the system.

  9. Avatar
    Matt Russell about 17 hours later:

    Hur, that didn’t show up right.

    Should read: (ignore any backslash if they appear)
    ident_pattern=":\(id\)\-:\(slug\)
    
    
  10. Avatar
    Mike Orr 1 day later:

    I’m not fond of the getattr override. It makes the name look like a method name, and it’s not parallel with url_for. If we make the name mandatory as the first argument, the ambiguity between some routes having a name first argument and others having a URL first argument disappears.

    If anonymous routes are necessary, they can be done by a separate method. That would mean three connect methods: named, anonymous, and static. (I assume all static routes will be named.)

    I’d also consider getting rid of the default /:controller/:action/:id route, although I guess that’s a Pylons issue rather than a Routes issue. Any good application should have named routes, it seems like, and my URLs at least never match that pattern anyway. Maybe we can leave it in with a comment that you really should set up your own custom routes and disable it unless it happens to match your desired URL layout.

  11. Avatar
    Div Shekhar 26 days later:

    > I’d also consider getting rid of the default /:controller/:action/:id route

    +1

    Some of us don’t use the controller/action pattern :)

  12. Avatar
    Jacob Smullyan 29 days later:

    Even if you want to get rid of minimizing generated urls by default, I hope it remains possible to perform that minimization if we want it when generating urls. I would do so. Why even implement defaults in url parsing if you don’t want to generate links that use them?

  13. Avatar
    Jamie Becker 4 months later:

    Ok, here are a few wishlist ideas:

    1) Make it possible to remove routes from Pylons and replace it with another third party routing middleware. I realize that this would immediately break url_for() and some of the other helpers, but just injecting the appropriate environment variables (what are they? just routes_dict?) should fix that. I removed it today, but Pylons has no simple way to do remove routes (use_routes=False doesn’t quite work) and I had to subclass BaseWSGIApp, override resolve() because it had references to pylons.routes_dict. Perhaps we could wrap other middleware with the appropriate injectors to provide information to url_for and other helpers—they’re very helpful.

    2) This is why I’m removing routes and writing my own router in resolve(): how about making the following pattern possible with restcontroller?

    /controller/[id]

    where id is a normal ID, such as a base64 hash, an email, etc.

    Right now, an email address (such as jamie@goawayspampleasesirjamiebecker.com) or a base64 hash with ==’s in it will throw an exception “Cannot find action.”

    This is a serious problem, and it’s also a problem with Rails routes. And, yet, it’s also the correct RESTful way of building some types of collections. We cannot always assume that [id]s will only contain US-ASCII letters and numbers. Of course, there’s no need to support unicode (because that’s not legal in URL’s anyway, and should be url quoted) but without support special characters, we can’t support url quoting either.

    Not to mention the security aspects… using numeric ID’s leaks information. Let’s say you GET your userid 113. Hey, I wonder who 114 is? Almost as bad as using guessable session ID’s. It’s fine for things that should be guessable, like 2008/11/1, or emails, but it shouldn’t be required. Using it for a numeric primary key in a database is usually risky.

    2) Make it possible to do domain-based routing based on one of the domain keys. This way, ‘domain1.com/wiki’ and ‘domain2.com/wiki’ could go to two entirely different controllers and mitigate the need for vhost setups. We currently use mod_rewrite for this, but we’d like to move this back into the controller for dynamic content.

    So those are my three wishes. If the second and third was fulfilled, I’d have less need for the first. For now, I’ll hack around it. ;-)

    Kudos—Pylons stays out of your way, which makes it the best web framework I’ve ever seen.

Comments