20

Dec

Filed in Code, Curse, Django, How-To's |

One issue I’ve personally had to overcome with Django, and languages which aren’t specifically designed for the web, is that you don’t have access to the environment everywhere. The environment I’m referring to, is the current request, and response objects.

In PHP there is no response object, at least not one as you would see in Django. If you wish to set a header or cookie, you simply do it (before output has been given). In .NET it’s more or less the same, you set it in a globally accessible environment variable. This makes it very easy for random tidbits of code to modify the headers.

We were recently implementing our new authentication service on Curse. The authentication service, of course, has to set several cookies. The issue we had to overcome was the authentication service may not, and usually wouldn’t, have access to a response object, as it hadn’t been created yet. The solution we came up with was simply to make the request.COOKIES variable have set methods on it.

The code itself is fairly confusing, as I attempted to override request.COOKIES originally with just a Cookie() object. This proved to be a backwards-compatible issue as it wouldn’t return a string by default with getitem (you would need to call cookie.value to get it’s value instead). To ensure backwards compatibility we had to subclass the Cookie, and Morsel methods, and override getitem to act the same.

All in all, we ended up with two middleware pieces, and a new class for request.COOKIES. The prehandler middleware should be your first middleware, and the post should be your last.

You can view the source at PasteThat.

from Cookie import SimpleCookie, Morsel
import copy
 
class CookiePreHandlerMiddleware(object):
    """
    This middleware modifies request.COOKIES and adds a set and delete method.
 
    `set` matches django.http.HttpResponse.set_cookie
    `delete` matches django.http.HttpResponse.delete_cookie
 
    This should be the first middleware you load.
    """
    def process_request(self, request):
        cookies = CookieHandler()
        for k, v in request.COOKIES.iteritems():
            cookies[k] = str(v)
        request.COOKIES = cookies
        request._orig_cookies = copy.deepcopy(request.COOKIES)
 
class CookiePostHandlerMiddleware(object):
    """
    This middleware modifies updates the response will all modified cookies.
 
    This should be the last middleware you load.
    """
    def process_response(self, request, response):
        if hasattr(request, '_orig_cookies') and request.COOKIES != request._orig_cookies:
            for k,v in request.COOKIES.iteritems():
                if request._orig_cookies.get(k) != v:
                    dict.__setitem__(response.cookies, k, v)
        return response
 
class StringMorsel(Morsel):
    def __str__(self):
        return self.value
 
    def __eq__(self, a):
        if isinstance(a, str):
            return str(self) == a
        elif isinstance(a, Morsel):
            return a.output() == self.output()
        return False
 
    def __ne__(self, a):
        if isinstance(a, str):
            return str(self) != a
        elif isinstance(a, Morsel):
            return a.output() != self.output()
        return True
 
    def __repr__(self):
        return str(self)
 
class CookieHandler(SimpleCookie):
    def __set(self, key, real_value, coded_value):
        """Private method for setting a cookie's value"""
        M = self.get(key, StringMorsel())
        M.set(key, real_value, coded_value)
        dict.__setitem__(self, key, M)
 
    def __setitem__(self, key, value):
        """Dictionary style assignment."""
        rval, cval = self.value_encode(value)
        self.__set(key, rval, cval)
 
    def set(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=None):
        self[key] = value
        for var in ('max_age', 'path', 'domain', 'secure', 'expires'):
            val = locals()[var]
            if val is not None:
                self[key][var.replace('_', '-')] = val
 
    def delete(self, key, path='/', domain=None):
        self[key] = ''
        if path is not None:
            self[key]['path'] = path
        if domain is not None:
            self[key]['domain'] = domain
        self[key]['expires'] = 0
        self[key]['max-age'] = 0

 

Trackbacks

(Trackback URL)

close Reblog this comment
blog comments powered by Disqus