Clean encapsulation for old URI redirects in Catalyst apps

Aristotle on 2008-06-15T05:42:24

At work, I develop and maintain a web application that I recently ported to Catalyst. One of the problems that showed up after the transition was broken links. The few most important ones were fixed at the source, but we have quite a few of them as literal strings in user content that sits in the database (possible but not easy to edit programmatically), and of course there are links on external sites (foremost being Google, of course) that we don’t control. So I needed to find a way to recognise the old URIs and redirect them to wherever they map to in the new structure – basically, a job for mod_rewrite.

However, I run the app standalone using Catalyst::Engine::HTTP::Prefork (andyg++), so mod_rewrite itself is out. There is an Apache reverse proxy in front, but I don’t want these redirects to depend on any particular deployment configuration, and I want them to be easily testable. So they need to be part of the application.

Where do I put them?

At first I did this in the regular way, creating some new controllers and writing actions in them. Catalyst’s Chained dispatch provides quite detailed control over URI structures, so this mostly worked. But it turned out to be quite clumsy: it took a lot of code for a relatively simple task, cluttered up the Catalyst dispatch table, and didn’t really work right. This is because the URI structure of the old app was slightly polymorphic in a way that could only awkwardly be made to fit into Catalyst, and dispatch became sensitive to the load order of controllers.

No good.

I puzzled over how to handle this, shuffling actions around in a number of ways before inspiration struck me. See, I have an e404 action in my root controller. The default action forwards there unconditionally, and many other places in the application do so conditionally, eg. when the request URI maps to a database record that doesn’t exist. Now you can already see where this is going: rather than just showing the 404 page, as this action used to do, it now runs a bunch of pattern matches against $c->req->path to see if the URI corresponds to the old apps’ structure. If it does, bits and pieces of the URI are captured and used to construct a new-style URI using $c->uri_for, and this is sent to the client as a permanent redirect. Basically, the 404 handler now makes a last-ditch attempt to salvage the request.

This works very well: all of the logic is isolated in a single spot where it is invisible to Catalyst’s dispatcher. It’s a minor design decision but nevertheless pleases me as it turned out so very right.