Delicious-Style URLs in Grails, or, Not-Matching Regexes

I’m building a Grails app where I want to use delicious-style URLs. The domain name is going to be typelink.net, so to get to user jjustice’s public page on it, you would go to http://typelink.net/jjustice. Grails makes it very easy to set up RESTful URLs like that:

"/$userName"{
	controller = "myController"
	action = "listByUser"
}

But one problem is that if I set it to map /anything at the root level of the app to a certain controller, then I can’t get to any other controllers or assets, because it thinks everything is a request for a user.

Conflicting with other controllers, like /login for a login page, wasn’t too big a deal to fix, because I could just create a UrlMapping for /login that was a higher precedence than my username mapping. The bigger problem was my CSS, JS, or images directories that are also at the root level–the app will think I’m trying to get to a user named “images.” And I can’t set up a URL mapping for them that I know of, because there’s no way to say “map this to just returning static assets”–mappings always send you to a controller.

The solution I found was to use a “matches” constraint on the URL mapping, to only match when the username isn’t one of my static asset directories or other controllers. But finding a regex for this was challenging, because there’s no doesntMatch constraint – only a positive matches constraint.

Turns out the regex (at least in Perl and Java) to not-match a pattern in a string is:


/^((?!pattern).)*$/

?! is a lookahead zero-width assertion, which only matches if the string following it does not match this pattern. So this overall pattern means that pattern must not match at any point in the string.

That wasn’t quite enough for me, though. I didn’t want to match “css,” but it was okay if a user’s name was “fredlikescss”. So all I wanted was to make sure the whole string did not match the pattern. To do that, I did:


/^((?!^pattern$).)*$/

In other words, at every point in the string, make sure that “pattern” doesn’t match the entire string.

Finally, I needed to add several patterns, because I wanted to make sure it wasn’t css, js, or images, and you can only specify one matches constraint for a URL mapping. To do that:


/^((?!^pattern1$)(?!^pattern2$)(?!^pattern3$).)*$/

So, in my case, the specific pattern was:


/^((?!^css$)(?!^js$)(?!^images$)(?!^session$)(?!^page$)(?!^share$).)*$/

Where css, js, and images are my static asset directores, and session, page, and share are my other controllers that I don’t want to map to usernames.

So my whole URL mapping for the usernames ended up being (copy-paste to see the whole string)

"/$userName"{
	controller = "myController"
	action = "listByUser"
	constraints {
		userName(matches:/^((?!^css$)(?!^js$)(?!^images$)(?!^session$)(?!^page$)(?!^share$).)*$/)
	}
}

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: