Django Middleware Munging
We’ve been hitting the code pretty hard of late at Loggly, and the beta is really starting to take shape on the development servers. There’s lots to do, of course, so we’ve taken to using Unfuddle to track tickets, host our repository for code commits. Later on we’ll use Unfuddle’s APIs to help track customer’s feature requests and tickets. Here’s a screen cap of our latest commit timeline:
One of the things you’ll notice when you use Unfuddle is the presence of a subdomain in the URLs you use on the site. Our subdomain is ‘loggly’ on Unfuddle, and we log into our project area by going to http://loggly.unfuddle.com/ (no, you can’t check our code out). This type of customer segmentation allows for multiple unique usernames per customer, but doesn’t require a unique username site-wide. For non-SEO sections of the site, this is a perfect solution.
We are taking a similar approach with Loggly, where a user will sign up for an account and define a unique customer identifier (we’re kicking around calling this a “mill”), which will then be mapped to a subdomain on the system. So, for example, if Foobar, Inc. were to sign up for a Loggly account, they would access the site via http://foobar.loggly.com/, and then could create any number of user/pass combinations they wanted to access their company’s log resources.
The only problem with this approach is that we use Django, and their built in auth system (which is fantastic, BTW) doesn’t really have facilities for this type of functionality. While we could certainly hack the Django auth system by writing our own multi-tenant auth module, it would take away from more pressing issues – like launching the beta!
Enter the Middleware Solution
One way to solve this is by munging the subdomain and username together, which provides a unique system-wide username. If, for example, you were to log in as steve under foobar.loggly.com, then we’d stick them together to be something like “foobar_steve”. Obviously we can’t have everyone remembering this long monstrosity for their username, so we’ll need to munge the subdomain off the URL and the username the user types in to get the correct combination to send off to the auth system.
Thankfully Django provides a super-easy way to add middleware to a project. By injecting a small piece of code into the request from the user’s browser, we are able to do our on-the-fly transformation before the auth system takes over. Nobody is the wiser because we can modify the display name code in the profile model to show the “normal” username to the user. Here’s what the result looks like:
settings.py: ... MIDDLEWARE_CLASSES = ( 'loggly.profile.MungeMiddle.MungeForMillMiddleware', ... ) ... MungeMiddle.py: class MungeForMillMiddleware: def process_request(self, request): if request.POST.has_key('username'): data = request.POST.copy() user = "%s_%s" % (request.META['HTTP_HOST'].split('.'), data['username']) data['username'] = user request.POST = data
When a request comes in, we pull out the POST data and make a copy of it with .copy(). We then munge up the username with the subdomain out of HTTP_HOST, and then set the POST data to forward on to the rest of the stack. We don’t do this for all requests, just ones with the username set, so it’s lightweight enough for production use. We end up sticking the shorteded version of the username into the profile table, and use it for display.
So there you have it. A 5 minute fix for a 5 hour problem. I’m sure there are more elegant solutions to doing subdomain segmentation with Django’s out-of-the-box auth system, but frankly we don’t have time to stop and code them up. We’re bent on getting our beta out as soon as possible, and if it requires hacks like these to do it, then so be it! Release early, release often.