Flexible URL routing

Gaelyk provides a flexible and powerful URL routing system: you can use a small Groovy Domain-Specific Language for defining routes for nicer and friendlier URLs.

Configuring URL routing

To enable the URL routing system, you should configure the RoutesFilter servlet filter in web.xml:

Note: We advise to setup only one route filter, but it is certainly possible to define several ones for different areas of your site. By default, the filter is looking for the file WEB-INF/routes.groovy for the routes definitions, but it is possible to override this setting by specifying a different route DSL file with a servlet filter configuration parameter:
Warning: The filter is stopping the chain filter once a route is found. So you should ideally put the route filter as the last element of the chain.

Defining URL routes

By default, once the filter is configured, URL routes are defined in WEB-INF/routes.groovy, in the form of a simple Groovy scripts, defining routes in the form of a lightweight DSL. The capabilities of the routing system are as follow, you can:

Let's see those various capabilities in action. Imagine we want to define friendly URLs for our blog application. Let's configure a first route in WEB-INF/routes.groovy. Say you want to provide a shorthand URL /about that would redirect to your first blog post. You could configure the /about route for all GET requests calling the get method. You would then redirect those requests to the final destination with the redirect named argument:

    get "/about", redirect: "/blog/2008/10/20/welcome-to-my-blog"

If you prefer to do a forward, so as to do URL rewriting to keep the nice short URL, you would just replace redirect with forward as follows:

    get "/about", forward: "/blog/2008/10/20/welcome-to-my-blog"

Original URI which was accessed such as /about can be found using request.originalURI attribute

Note: In addition to redirect and forward, you can also do a redirect301, which is a permanent redirect.

If you have different routes for different HTTP methods, you can use the get, post, put and delete methods. If you want to catch all the requests independently of the HTTP method used, you can use the all function. Another example, if you want to post only to a URL to create a new blog article, and want to delegate the work to a post.groovy Groovlet, you would create a route like this one:

    post "/new-article", forward: "/post.groovy"     // shortcut for "/WEB-INF/groovy/post.groovy"
Note: When running your applications in development mode, Gaelyk is configured to take into accounts any changes made to the routes.groovy definition file. Each time a request is made, which goes through the route servlet filter, Gaelyk checks whether a more recent route definition file exists. However, once deployed on the Google App Engine cloud, the routes are set in stone and are not reloaded. The sole cost of the routing system is the regular expression mapping to match request URIs against route patterns.

Incoming email and jabber messages

Two special routing rules exist for defining handlers dedicated to receiving incoming email messages and jabber messages.

    email  to: "/receiveEmail.groovy"
    jabber to: "/receiveJabber.groovy"

jabber chat, to: "/receiveJabber.groovy" // synonym of jabber to: "..." // for Jabber subscriptions jabber subscription, to: "/subs.groovy" // for Jabber user presence notifications jabber presence, to: "/presence.groovy"

Note: Those two notations are actually equivalent to:
    post "/_ah/mail/*", forward: "/receiveEmail.groovy"
    post "/_ah/xmpp/message/chat/", forward: "/receiveJabber.groovy"
Should upcoming App Engine SDK versions change the URLs, you would still be able to define routes for those handlers, till a new version of Gaelyk is released with the newer paths.
Note: Make sure to read the sections on incoming email messages and incoming jabber messages.

Using wildcards

You can use a single and a double star as wildcards in your routes, similarly to the Ant globing patterns. A single star matches at least one character up to a slash (/[^\/]+/), where as a double start matches an arbitrary path. To match all files by extension, use /**/*.fileext. For instance, if you want to show information about the blog authors, you may forward all URLs starting with /author to the same Groovlet:

    get "/author/*", forward: "/authorsInformation.groovy"

This route would match requests made to /author/johnny as well as to /author/begood.

In the same vein, using the double star to forward all requests starting with /author to the same Groovlet:

    get "/author/**", forward: "/authorsInformation.groovy"

This route would match requests made to /author/johnny, as well as /author/johnny/begood, or even /author/johnny/begood/and/anyone/else.

Warning: Beware of the abuse of too many wildcards in your routes, as they may be time consuming to compute when matching a request URI to a route pattern. Better prefer several explicit routes than a too complicated single route.

Warmup requests

When an application running on a production instance receives too many incoming requests, App Engine will spawn a new server instance to serve your users. However, the new incoming requests were routed directly to the new instance, even if the application wasn't yet fully initialized for serving requests, and users would face the infamous "loading request" issue, with long response times, as the application needed to be fully initialized to be ready to serve those requests. Thanks to "warmup requests", Google App Engine does a best effort at honoring the time an application needs to be fully started, before throwing new incoming requests to that new instance.

Warmup requests are enabled by default, and new traffic should be directed to new application instances only when the following artefacts are initialized:

Note: Please have a look at the documentation regarding "warmup requests". Please also note that you can also enable billing and activate an option to reserve 3 warm JVMs ready to serve your requests.

So to benefit from "warmup requests", the best approach is to follow those standard initialization procedures. However, you can also define a special Groovlet handler for those warmup requests through the URL routing mechanism. Your Groovlet will be responsible for the initialization phase your application may be needing. To define a route for the "warmup requests", you can procede as follows:

    all "/_ah/warmup", forward: "/myWarmupRequestHandler.groovy"

Using path variables

Gaelyk provides a more convenient way to retrieve the various parts of a request URI, thanks to path variables.

In a blog application, you want your article to have friendly URLs. For example, a blog post announcing the release of Groovy 1.7-RC-1 could be located at: /article/2009/11/27/groovy-17-RC-1-released. And you want to be able to reuse the various elements of that URL to pass them in the query string of the Groovlet which is responsible for displaying the article. You can then define a route with path variables as shown in the example below:

    get "/article/@year/@month/@day/@title", forward: "/article.groovy?year=@year&month=@month&day=@day&title=@title"

The path variables are of the form @something, where something is a word (in terms of regular expressions). Here, with our original request URI, the variables will contains the string '2009' for the year variable, '11' for month, '27' for day, and 'groovy-17-RC-1-released for the title variable. And the final Groovlet URI which will get the request will be /WEB-INF/groovy/article.groovy?year=2009&month=11&day=27&title=groovy-17-RC-1-released, once the path variable matching is done.

Note: If you want to have optional path variables, you can append questionmark ? to the end of the path variable.
    get "/article/@year?/@month?/@day?/@title?", forward: "/article.groovy"
The definition above is the same like if you write following routes to display all the articles published on some year, month, or day:
    get "/article/@year/@month/@day/@title", forward: "/article.groovy?year=@year&month=@month&day=@day&title=@title"
    get "/article/@year/@month/@day",        forward: "/article.groovy?year=@year&month=@month&day=@day"
    get "/article/@year/@month",             forward: "/article.groovy?year=@year&month=@month"
    get "/article/@year",                    forward: "/article.groovy?year=@year"
    get "/article",                          forward: "/article.groovy"
If you want to create sticky optional variable at the end of the route, use distinguished prefix such as page- in following example.
    get "/article/@category?/@subcategory?/page-@page?", forward: "/article.groovy"
This will produce following routes:
    get "/article/@category/@subcategory/page-@page", forward: "/article.groovy?page=@page&subcategory=@subcategory&category=@category"
    get "/article/@category/page-@page",              forward: "/article.groovy?page=@page&category=@category"
    get "/article/page-@page",                        forward: "/article.groovy?page=@page"
    get "/article/@category/@subcategory",            forward: "/article.groovy?subcategory=@subcategory&category=@category"
    get "/article/@category",                         forward: "/article.groovy?category=@category"
    get "/article",                                   forward: "/article.groovy"
Optinonal parameters currently only works for destinations defined as String.
Also, note that routes are matched in order of appearance. So if you have several routes which map an incoming request URI, the first one encountered in the route definition file will win. This can be changed by passing index parameter to the route definition. Index parameter lesser than zero will be matched before all the other routes defined in the routes script. Be sure the index is unique. Do not assing index between zero and number of your routes in the routes file

Validating path variables

The routing system also allows you to validate path variables thanks to the usage of a closure. So if you use path variable validation, a request URI will match a route if the route path matches, but also if the closure returns a boolean, or a value which is coercible to a boolean through to the usual Groovy Truth rules. Still using our article route, we would like the year to be 4 digits, the month and day 2 digits, and impose no particular constraints on the title path variable, we could define our route as follows:

    get "/article/@year/@month/@day/@title",
        forward: "/article.groovy?year=@year&month=@month&day=@day&title=@title",
        validate: { year ==~ /\d{4}/ && month ==~ /\d{2}/ && day ==~ /\d{2}/ }
Note: Just as the path variables found in the request URI are replaced in the rewritten URL, the path variables are also available inside the body of the closure, so you can apply your validation logic. Here in our closure, we used Groovy's regular expression matching support, but you can use boolean logic that you want, like year.isNumber(), etc.

In addition to the path variables, you also have access to the request as well as all GAE services from within the validation closure. For example, if you wanted to check that a particular attribute is present in the request, like checking a user is registered to access a message board, you could do:

    get "/message-board",
        forward: "/msgBoard.groovy",
        validate: { request.registered == true }

Another example would be to verify if the current user is an admin before allowing the route to kick in. The GAE services are available under the same variable names as in groovlets.

    get "/only-admin",
        forward: "/secured.groovy",
        validate: { users.isUserAdmin() }

Capability-aware routing

With Google App Engine's capability service, it is possible to programmatically decide what your application is supposed to be doing when certain services aren't functionning as they should be or are scheduled for maintenance. For instance, you can react upon the unavailability of the datastore, etc. With this mechanism available, it is also possible to customize your routes to cope with the various statuses of the available App Engine services.

Note: Please make sure to have a look at the capabilities support provided by Gaelyk.

To leverage this mechanism, instead of using a simple string representing the redirect or forward destination of a route, you can also use a closure with sub-rules defining the routing, depending on the status of the services:

    import static com.google.appengine.api.capabilities.Capability.*
    import static com.google.appengine.api.capabilities.CapabilityStatus.*

    get "/update", forward: {
        to "/update.groovy"
        to("/maintenance.gtpl").on(DATASTORE)      .not(ENABLED)
        to("/readonly.gtpl")   .on(DATASTORE_WRITE).not(ENABLED)

In the example above, we're passing a closure to the forward parameter. There is a mandatory default destination defined: /update.groovy, that is chosen if no capability-aware sub-rule matches.

Important: The sub-rules are checked in the order they are defined: so the first one matching will be applied. If none matches, the default destination will be used.

The sub-rules are represented in the form of chained method calls:

  1. A destination is defined with the to("/maintenance.gtpl") method.
  2. Then, an on(DATASTORE) method tells which capability should the rule be checked against.
  3. Eventually, the not(ENABLED) method is used to check if the DATASTORE is not in the status ENABLED.
Tip: If you're using Groovy 1.8-beta-2 and beyond, you'll be able to use an even nicer syntax, with fewer punctuation marks:
    import static com.google.appengine.api.capabilities.Capability.*
    import static com.google.appengine.api.capabilities.CapabilityStatus.*

    get "/update", forward: {
        to "/update.groovy"
        to "/maintenance.gtpl" on DATASTORE       not ENABLED
        to "/readonly.gtpl"    on DATASTORE_WRITE not ENABLED

The last method of the chain can be either not(), as in our previous examples, or is(). For example, you can define a sub-rule for the case where a scheduled maintenance window is planned:

    // using Groovy-1.8-beta-2+ syntax:
    to "/urlFetchMaintenance.gtpl" on URL_FETCH is SCHEDULED_MAINTENANCE

The following capabilities are available, as defined as constants in the Capability class:

The available status capabilities, as defined on the CapabilityStatus enum, are as follows:

Ignoring certain routes

As a fast path to bypass certain URL patterns, you can use the ignore: true parameter in your route definition:

    all "/_ah/**", ignore: true

Caching groovlet and template output

Gaelyk provides support for caching groovlet and template output, and this be defined through the URL routing system. This caching capability obviously leverages the Memcache service of Google App Engine. In the definition of your routes, you simply have to add a new named parameter: cache, indicating the number of seconds, minutes or hours you want the page to be cached. Here are a few examples:

    get "/news",     forward: "/new.groovy",     cache: 10.minutes
    get "/tickers",  forward: "/tickers.groovy", cache: 1.second
    get "/download", forward: "/download.gtpl",  cache: 2.hours

The duration can be any number (an int) of second(s), minute(s) or hour(s): both plural and singular forms are supported.

Note: byte arrays (the content to be cached) and strings (the URI, the content-type and last modified information) are stored in Memcache, and as they are simple types, they should even survive Google App Engine loading requests.

It is possible to clear the cache for a given URI if you want to provide a fresher page to your users:

Note: There are as many cache entries as URIs with query strings. So if you have /breaking-news and /breaking-news?category=politics, you will have to clear the cache for both, as Gaelyk doesn't track all the query parameters.

Namespace scoped routes

Another feature of the URL routing system, with the combination of Google App Engine's namespace handling support, is the ability to define a namespace, for a given route. This mechanism is particularly useful when you want to segregate data for a user, a customer, a company, etc., i.e. as soon as you're looking for making your application multitenant. Let's see this in action with an example:

    post "/customer/@cust/update", forward: "/customerUpdate.groovy?cust=@cust", namespace: { "namespace-$cust" }

For the route above, we want to use a namespace per customer. The namespace closure will be called for each request to that route, returning the name of the namespace to use, in the scope of that request. If the incoming URI is /customer/acme/update, the resulting namespace used for that request will be namespace-acme.

Note: Make sure to have a look at the namespace support also built-in Gaelyk.

Routes indexes

Especially for writing plugins you may wont to change the default behaviour where the routes are evaluated one by one by its poisition in routes.groovy file. Under normal circumstances the index equals the line number of the route but can change this by assinging it in route definition:

    get "/home", forward: "/home.groovy", index: -1

Also for plugins you can assign the first route index by calling startRoutingAt method so default indexes won't start at zero but the given number:

	startRoutingAt -1000
	// this route will have index of -1000
    get "/home", forward: "/home.groovy"