lots of work: a new router for rails
So I've been kicking the idea of writing a new router for Rails for a long time. I started tinkering with web frameworks last year, and decided that I would write a bunch of components that one could assemble or integrate into their own frameworks. The yield of the primitive work I actually completed was fleet, and my ultimate goal was to build a router that was API-compatible with Rails and could be extracted into its own gem for others to use. It never got that far, but I did complete most of the work on the router.
Fast-forward a while and after a conversation with Pratik, I decided to finish it up and release it as a plugin. I haven't quite reached release stage just yet, but I thought I'd share what I have done.
Regex Inside
The current router operates in a very bizarre manner to me. You can read all about it on Jamis' blog, but suffice to say that it's a little crazy. Unfortunately, routing is a bit of a crazy business by nature (much like per-object permissions tends to get messy); routing requests is complex and prone to tons of edge cases.
I'm sure my approach doesn't satisfy all the edge cases, but here's how it works so far. Routes are read in from the route file (config/routes.rb) and pushed to two structures. One is an array of Route objects for path recognition (i.e., matching http://blah.com/pages/1 to PagesController#show with an :id of 1) and one is a structured tree (in this case, constructed in a Hash) for path generation (i.e., generating "pages/1" from the right parameters). This separate structure is important because it lets me exploit the best features of the two types of data structures: arrays rock at ordered recall and hashes are great at direct access in an arbitrary structure.
When all the routes have been read, one big regular expression is constructed. But here's the trick: I use parentheses to separate the captures. That doesn't sound tricky, but consider this for a moment. Each route is in a sequential data structure and the regular expression is built sequentially from this structure. So you'll end up with a regular expression something like this: /(pages\/)(pages\/new)(pages\/\d+)(pages\/\d+\/edit)/ and so on. When I run scan, I'm given an array of matches and misses. So for pages/3, it'd look something like: [nil, nil, "pages/3", nil, nil]. I can then compare the matched index with the index in the routes array to fetch the proper array. One match and we have our Route object.
Paraming My Meters
Parameters pose a unique challenge, but I use a similar approach. Route strings look something like this: 'params/:id'. When I read in the route, I parse out the parameter names into an array (e.g., ['params']). I then match the params out using a second regular expression that uses the same parentheses trick, match them up into a Hash, and merge in any constant values that may have been provided in the route file. Then, per what Rails expects, I set the parameters (including controller name and action in the request object) and return the controller class.
One optimization I make here is skipping the whole parameters parsing stuff if the route doesn't have any dynamic segments. It's a micro-optimization to be sure, but it does help.
Generator to the MAX
Generation is fairly straightforward. The structure that's generated when the routes are read in looks like this: @route_structure[:request_method][:controller]][:action][:routes_that_match_that]. So for link_to('hello', :controller => 'pages', :action => 'show', :id => 3), it'll look something like: @route_structure['get']['pages']['show'] and the :id will be subbed into the subsequently generated string.
Named routes (when they're implemented) will be generated methods that will simply return a string with the parameters in the right place. These should be smoking fast when they're done, since they skip the routing system altogether after startup.
Soooo...?
Why do you care? Well in my ghetto benchmarks (using ab and one resource route set), my router is about 25% faster. I don't know about RAM usage, but I'd imagine it's better there, too. Hopefully I can optimize it some more to get more speed and more conservative memory usage out of it, but any improvement is better than nothing!
I'll try to release it as a plugin next week, and hopefully people can start banging on it then. Once I get it stable and optimized, I want to add a few features to it that aren't Rails-standard. I'll keep you posted!
- Technology:


Recent comments
1 year 23 weeks ago
1 year 23 weeks ago
1 year 25 weeks ago
1 year 27 weeks ago
1 year 42 weeks ago
1 year 45 weeks ago
1 year 45 weeks ago
1 year 45 weeks ago
1 year 46 weeks ago
1 year 48 weeks ago