Serving Multiple Hosts from a Single Django Instance

Fredrik Lundh | December 2006

The Django framework has excellent support for running multiple sites on a single Django installation, via the sites framework (dead link). This framework is based on a global site identifier setting (SITE_ID), which is then used by different components to distinguish between data for different sites in the database.

However, SITE_ID is a global setting, so the site framework requires you to run multiple instances of Django; at least one for each site.

This article describes a more light-weight approach, which lets you serve multiple distinct host names from a single instance, by switching between different URL configurations on the fly. This approach doesn’t provide full separation, so it’s mostly usable for servering related material under different URL:s, such as a company site with one or more product subsites, etc.

Patching the Django code base #

Update: This patch was added to the Django trunk in revision 4237.

First, a simple patch. This allows a middleware component to specify a request-specific URL configuration, instead of always having to rely on the global ROOT_URLCONF setting:

 
Index: django/core/handlers/base.py
===================================================================
--- django/core/handlers/base.py	(revision 4234)
+++ django/core/handlers/base.py	(working copy)
@@ -60,7 +60,10 @@
             if response:
                 return response
 
-        resolver = urlresolvers.RegexURLResolver(r'^/', settings.ROOT_URLCONF)
+        # Get urlconf from request object, if available.  Otherwise use default.
+        urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
+
+        resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
         try:
             callback, callback_args, callback_kwargs = resolver.resolve(request.path)
 

The MultiHost Middleware #

To set a host specific URL configuration, you need to add a simple middleware component. Here’s the code:

 
# File: multihost.py

##
# A simple middleware component that lets you use a single Django
# instance to server multiple distinct hosts.
##

from django.conf import settings
from django.utils.cache import patch_vary_headers

class MultiHostMiddleware:

    def process_request(self, request):
        try:
            host = request.META["HTTP_HOST"]
            if host[-3:] == ":80":
                host = host[:-3] # ignore default port number, if present
            request.urlconf = settings.HOST_MIDDLEWARE_URLCONF_MAP[host]
        except KeyError:
            pass # use default urlconf (settings.ROOT_URLCONF)

    def process_response(self, request, response):
        if getattr(request, "urlconf", None):
            patch_vary_headers(response, ('Host',))
        return response

Here, the process_request hook will look up the host name in the global HOST_MIDDLEWARE_URLCONF_MAP dictionary (more on this below), and use the corresponding value instead of ROOT_URLCONF for this request. If the host is not found, the default ROOT_URLCONF value is used instead.

The process_response hook is used to make caching work; it tells Django that the output from this server will depend on the HTTP Host header field.

Configuration #

To enable multihost support, you need to do the following:

  • Add an URL configuration file for each application.

  • Add a HOST_MIDDLEWARE_URLCONF_MAP dictionary to the settings.py file, which maps host names to the URL configuration files you just created.

  • Add a reference to the MultiHost middleware to the MIDDLEWARE_CLASSES list. This middleware must be placed after the cache middleware, if present.

For example, if you’re going to serve company.com and blog.example.com, you can add company_urls.py and blog_urls.py files to your application directory, and add the following entry to your settings.py file:

# File: settings.py

...

HOST_MIDDLEWARE_URLCONF_MAP = {
    "company.com": "mydjango.app.company_urls",
    "blog.example.com": "mydjango.app.blog_urls",
}

...

Error Handling #

Note that Django will still use the default mechanisms to handle 404 and 500 errors for all sites. If you want to use different templates for your sites, set the handler404 and handler500 variables in your URL configuration files:

# File: company_urls.py

handler404 = "mydjango.app.company_view.handler404"
handler500 = "mydjango.app.company_view.handler500"

urlpatterns = (
   ...
)
 
# File: company_view.py

from django.views.defaults import page_not_found, server_error

def handler404(request):
    return page_not_found(request, "company_404.html")

def handler500(request):
    return server_error(request, "company_500.html")

The custom handlers used in this example simply call Django’s default handlers, with custom templates.

 

A Django site. rendered by a django application. hosted by webfaction.