23
Oct
2008

Filed under: django, proxy, ssl, wsgi

3 comments

Django WSGI handler and SSL proxies

When serving both HTTP and HTTPS in my preferred deployment setup — nginx in front of Apache with mod_wsgi running Django — I had to figure out how to let Django know whether a request was secure or not.

The default Django WSGI handler checks the wsgi.url_scheme environment variable, but if you don't connect nginx and Apache with HTTPS, mod_wsgi can't know that the original request was encrypted.

This can cause problems if you issue redirects in your Django views; they'll be constructed for HTTP. Even if you're rewriting the sensitive parts of your URL space (cart, checkout, what have you) in your web server config, or with middleware, the browser won't include the session cookie when redirected, because you are of course marking your session cookies secure, right?

So your logged-in visitor is looking at his cart, hits the checkout button, and is confronted with a login page. Bzzzt. No sale. I'm sure you can think of other scenarios, too.

The fix is simple: have the front-end proxy indicate via a request header that Django should consider the request secure:

proxy_set_header X-Url-Scheme $scheme;

Then tell Django to check that with these WSGIHandler and WSGIRequest implementations:

Yeah. Never mind about those. You don't need to delve into the Django handler code. As Graham points out in his comment below, you can convey the request scheme much more simply by just manipulating the environment in the WSGI application script. Here's his version from below; it's a little easier to read here than in the comments.

import os, sys 

sys.path.append('/usr/local/django') 
os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings' 

import django.core.handlers.wsgi 
_application = django.core.handlers.wsgi.WSGIHandler() 

def application(environ, start_response): 
    environ['wsgi.url_scheme'] = environ.get('HTTP_X_URL_SCHEME', 'http') 
    return _application(environ, start_response) 

You do still need to make sure that your front-end proxy doesn't blindly pass the header through from clients — always have it set or remove it.

Comments (3)

Graham Dumpleton — 28 October 2008 0:03
Huh, why so complicated? You shouldn't need to go fiddling code for WSGIRequest or WSGIHandler. Why are you doing that?

It should be able to all be done in WSGI wrapper around Django WSGI entry point.

Minimum should be:

proxy_set_header X-Url-Scheme $scheme;

on the nginx side and then the WSGI wrapper would be:

import os, sys

sys.path.append('/usr/local/django')
os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'

import django.core.handlers.wsgi
_application = django.core.handlers.wsgi.WSGIHandler()

def application(environ, start_response):
environ['wsgi.url_scheme'] = environ.get('HTTP_X_URL_SCHEME', 'http')
return _application(environ, start_response)

You seem to be creating extra work for yourself.

See conversation at:

http://groups.google.com/group/modwsgi/browse_frm/thread/94f952720c878506

john — 28 October 2008 7:35
Graham, dude, where do you find the time to correct all the misunderstanding of mod_wsgi on the web? :^)

You're right, of course; my solution was overcomplicated. Chalk it up to insufficient understanding of how mod_wsgi handles its environment; I'd had problems in embedded mode with timezones getting set incorrectly in different Django apps, so I thought it was safer to alter the request handling within Django.

Graham Dumpleton — 28 October 2008 8:09
Timezones can indeed big a big problem if trying to host multiple Django instances in same process, but in different sub interpreters. This is because timezones handled by C code level environment variables. Thus, one sub interpreter setting it, will affect all other sub interpreters. Only solution in that case is to use mod_wsgi daemon mode and delegate each Django instance to different daemon process group. Or, at least delegate only those with same timezone setting to a daemon process group.

I actually thought I had this documented somewhere, but can't find it. Must remember to add something about it.

Comments have been turned off for this article, but you can always contact us about it.