Tech Blog by Ruddra

Deploy Django App in Sub Directory Using Openshift

The Problem

When you are using Openshift, you will be using routes to expose a route from a service. Let’s say you want to expose a path in example.com/dummy. When you do that for a django application(without any reverse proxy server), it usually becomes a problem, because the sub directory does not work well with Django’s urls. You will probably face error 404 page not found.

If you are not using NGINX/Apache or any other reverse proxy server to serve the django application, you will not be able to set X-SCRIPT-NAME. To me, it does not make sense to use reverse proxy in pods when Openshift provides routes(or ingress in kubernetes) using NGINX/HA-Proxy(depending on your setup).

In this situation, I have used the following solution:

Solution

Disable Namespace Ownership Check in Router

You need to patch the router in Openshift before starting any step(from Openshift 3.9). By disabling Ownership check, you can allow a service to use a xyz.com/abc even if there is another service taking xyz.com. You can do it like this(from documentation):

oc adm router ... --disable-namespace-ownership-check=true
oc env dc/router ROUTER_DISABLE_NAMESPACE_OWNERSHIP_CHECK=true

FYI: this also works in OKD as well.

Set FORCE_SCRIPT_NAME

I have set FORCE_SCRIPT_NAME in settings.py. Usually I fetched the value from environment varibles, which I have set in the Deployment Config:

FORCE_SCRIPT_NAME = os.environ.get('DJANGO_FORCE_SCRIPT_NAME', '/subdirectory')  # without trailing slash

Using FORCE_SCRIPT_NAME will configure django’s url in path /subdirectory. But it is not sufficient to serve the django application in sub path, as SCRIPT_NAME in requests are not updated, also it will show some erratic behaviour such as showing double entry of /subpath in url, 404 Page not found issues etc. To fix this, lets go to the second step:

Update wsgi.py

Lets update the wsgi.py file like this:

import os
from django.core.wsgi import get_wsgi_application
from django.conf import settings


os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dummy.settings')
_application = get_wsgi_application()

# Here is the important part
def application(environ, start_response):
    script_name = getattr(settings, 'FORCE_SCRIPT_NAME', None)
    if script_name:
        environ['SCRIPT_NAME'] = script_name
        path_info = environ['PATH_INFO']
        if path_info.startswith(script_name):
            environ['PATH_INFO'] = path_info[len(script_name):]

    scheme = environ.get('HTTP_X_SCHEME', '')
    if scheme:
        environ['wsgi.url_scheme'] = scheme

    return _application(environ, start_response)

In above code, we are getting the value of script_name from FORCE_SCRIPT_NAME, then putting that value in wsgi environ(environment variable).

Big Thanks To

I got the implementation idea from this stackoverflow answer. Big thanks to @whp.

That is it for today, hope it helps. If you have a better approach to resolve this problem please make a comment in comment section below.