Dashboard > Routes > Home > Routes 2 Manual (development version)
  Routes Log In | Sign Up   View a printable version of the current page.  
  Routes 2 Manual (development version)
Added by Mike Orr, last edited by Mike Orr on Jan 14, 2008  (view change)
Labels: 
(None)

Warning

This is an unfinished development document for discussion and experimental use only.

Where it contradicts the Routes 2 Spec, the latter prevails.

1   Introduction

Routes tackles an interesting problem that comes up frequently in web development, how do you map URLs to your application's actions? That is, how do you say that this should be accessed as "/blog/2008/01/08", and "/login" should do that? Many web frameworks have a fixed rule built in. saying that "/A/B/C" means to read file "C" in directory "B", or to call method "C" of class "B" in module "A.B". These work fine until you need to refactor your code and realize that moving a method changes its public URL and invalidates users' bookmarks. Likewise, if you want to reorganize your URLs and make a section into a subsection, you have to change your carefully-tested logic code.

Routes takes a different approach. You determine your URL hierarchy and and actions separately, and then link them together in whichever ways you decide. If you change your mind about a particular URL, just change one line in your route map and never touch your action logic. You can even have multiple URLs pointing to the same action; e.g., to support legacy bookmarks.

Routes is the primary dispatching system in the Pylons web framework, and an optional choice in CherryPy. It can be added to any framework without too much fuss, and used for an entire site or a URL subtree. It can also forward subtrees to other dispatching systems, such as a TurboGears2 controller in a Pylons application.

Routes 2 is a rewrite which makes Routes simpler and more deterministic, following the Python slogan "Explicit is better than implicit", (Need to explain this more thoroughly.)

Most of this manual is written from the user's perspective: how to use Routes in a framework that already supports it. The last section shows how to add Routes support to a new framework.

2   Vocabulary

Route
A rule that links one URL pattern to an application action.
Route map
A set of routes for a website (or for portion a portion of a site).
Matching
The act of choosing the appropriate route for an incoming URL. This produces a dict of route variables.
Generating
The opposite of matching; choosing a URL based on its name or route variables.
Route variables
Named portions of a URL. For instance, route "/blog/{year}/{month}/{day}" matches URL "/blog/2008/01/08" and defines three numeric variables. A route rule can also define extra variables not in the URL, which are passed to the framework as constants. Routes does not know what the variables mean, but the framework uses them to choose an action and pass it arguments. Three variables important to most frameworks are controller, action, and id.
Controller
Some frameworks (e.g., Pylons and TurboGears2) use a variable named "controller" to choose a class in a similar-named module to handle the web request. In these frameworks, all route rules should define this variable.
Action
Some frameworks (e.g., Pylons) use a variable named "action" to choose a method in the controller class to execute. In these frameworks, all route rules should define this variable.
ID
Some actions use a variable named "id" to choose a database record to operate on. This is merely a convention; the application writer may choose to follow it or not.
Default route
The route used if no other route matches. By default there is no default route, so Routes will raise an error if no route matches. This is not usually what you want, so your first job should be to set a default route.

3   Setting Up Routes

Your web framework's documentation will tell you where to define your routes. In Pylons it's the make_map function in module myapp.config.routing. We'll assume here that the route map is in a Python variable m, and that the default route has been assigned thus:

1
m.default("{controller}/{action}/{id}", action="index", id=None. _minimize=True)

This will match URLs like "/news/display/2" and "/help/corporate/address. Because _minimize is true, it will also match "/help/corporate" and "/help", filling in the missing variables from the keyword arguments. However, "{controller}" does not have a corresponding keyword argument, so this route will not match URL "/". So our second task is to define a route for the home page.

Note: matching minimized URLs will be done via redirects rather than calling the action directly. This will allow Routes to handle relative URLs properly.

1
m.home("", controller="main", action="index")

The attribute here, "home", is the route's name. Names are used in route generation, and will be discussed below. (Unlike Routes 1, names are required.) "default" is just a name too, but Routes uses it specially.

Again the first argument is the route path minus the leading slash. (Routes will politely ignore the slash if you specify it.) Here the route path is "/" because there is nothing after the slash. "controller" and "action" are constants because there's no "{controller}" or "{action}" in the route path, so they'll always have the values "main" and "index" in the routing variables. Let's look at a few more routes:

1
2
3
4
5
6
7
8
m.archives("archives/{year}/{month}/{day}", controller="archives",
          action="view", year=2004,
          requirements=dict(year=r"\d{2,4}", month=r"\d{1,2}"))
m.atom_feed("feeds/{category}/atom.xml", controller="feeds", action="atom")
m.history("archives/by_eon/{century}", controller="archives",
          action="aggregate", century=1800)
m.article("article/{section}/{slug}/{page}.html",
          controller="article", action="view")

So far, so good. The route rules are considered in order of definition until one matches.

The requirements argument is a dict of regular expression patterns which the corresponding parts of the URL must match, otherwise the URL will not match the route.

Because these routes do not have _minimize=True, they will not match URLs that are missing some variable components. So "/archives" and "/archives/2006" would not be matched. You'll have to add additional routes if you want to match these shorter routes, or turn on minimization. This may seem like a burden compared to Routes 1, which automatically matched the shorter ("minimized") URLs. But experience has shown that minimization is so eager it can choose the wrong route, an earlier one in the map, so in Routes 2 you have to explicitly enable minimization where you want it. Minimization does no harm in the default route because it's considered after all the others, so it's often used there.

3.1   Generation-only routes

Sometimes you want a route only for generation and not for matching. This can be useful with off-site URLs, or those that will be served by Apache or a Pylons "public" directory rather than an action. Add _match=False to make a route generation-only.

1
2
m.image("/images/{record_id{/{filename}.jpg", _match=False)
m.google("http://www.google.com/", _match=False)

3.2   Redirect routes

A redirect route tells the framework to send a redirect to a different URL. This saves you from having to write an action to do the same thing, and is useful for legacy bookmarks and search-engine links.

1
2
3
4
5
6
m.redirect("faq",  "/static/faq/index")
m.redirect("outsourced_department", "http://outsourcing-r-us.example.com/acme/")
m.redirect("favicon.ico", "/images/temp-icon.png", 302)
m.redirect("faq/{section}", "/static/faq/{section}.html")
m.redirect("note/{letter}", "/musical-notes/{letter}",
    requirements={"letter": r"[A-G]"})

The first argument is the route path. The second is the destination URL, where to redirect to. Routes converts relative destination URLs to absolute per the HTTP spec. Variables may be used to copy parts of the incoming URL to the destination URL. requirements and _minimize operate as usual. Other keyword arguments are returned as part of the match dict, though the framework will probably ignore them.

The third positional argument is the HTTP status, which defaults to 301. If specified, it must be an integer between 300 - 399. The following statuses are defined by HTTP 1.1:

  • 301: Moved permanently. User agent should update any bookmarks.
  • 302: Found. This is the typical one for "moved temporarily", and is equivalent to 303.
  • 303: See Other. Retry using the specified URL with the GET method.
  • 307: Temporary Redirect. Retry using the specified URL with the original method (GET or POST).

Statuses 300, 304, 305, and 306 are allowed but not useful with this method.

3.3   Failure routes and gone routes

A failure route tells the framework to send a 4xx or 5xx status to the browser.

1
2
m.fail("something", 404)
m.fail("something2", 500, "message")

The second argument, an integer between 400 - 599, is required. The most useful HTTP statuses are:

  • 400 Bad Request: the application does not like the URL.
  • 403 Forbidden: the application refuses to honor this request, and will not say why.
  • 404 Not Found: there is no such resource in this application.
  • 405 Method Not Allowed: user did GET to a POST-only resource, etc. (@@MO: can Routes do this automatically if a route failed due to a method constraint, and no other route matched? But what about the default route? Need another flag to indicate "fail all other methods"? RFC 2616 says the response must include a list of allowed methods.)
  • 410 Gone: the resource has been deleted and will not come back, and there's no redirect URL. User agents should delete any bookmarks to this URL.
  • 500 Internal Server Error: something happened and it's not the client's fault.
  • 503 Service Unavailable: the resource is down for maintenance or otherwise unavailable.

The third argument, if specified, is a suggested error message. The framework may incorporate this into the body of the response, but is free to ignore it. Typically this will be an HTML snippet explaining the reason for the error.

Because 410 ("Gone") is underused and many people don't even know about it, Routes provides a special syntax to encourage its use:

1
m.gone("employees/that_loser")

Note

Differences from Routes 1

  • Route names are required.
  • Minimization does not occur unless enabled for a particular route. The argument _minimize exists only in Routes 2.
  • _match replaces _static, with the opposite boolean value. Generation-only routes in Routes 2 can have variables, while static named routes in Routes 1 cannot.
  • Routes 1 does not have redirect routes or gone routes.

Site running on a free Atlassian Confluence Open Source Project License granted to Pylons. Evaluate Confluence today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.3.3 Build:#645 Feb 13, 2007) - Bug/feature request - Contact Administrators