djangodeploymentcgi

CGI deployment for a Django Webapp e.g. Strato.de Webhosting


iam trying to deploy my django app via cgi on strato.de . Strato only supports basic cgi so iam forced to use this method.

I already made a flask app run on strato via cgi with this tutorial ( btw great guide ). I tried to alter this and adept it to the django app to get it running on the webserver, but it looks like i am missing something.

If i call the website i run into an 500 Internal Server Error . ( I would apprieciate also some tipps how to debug this, I have no idea where to start ). Just to mention it: Everything works fine in develompent env.

Steps already done:

  1. I did check if all required packages are installed on the server listed in the requirements.txt file.
  2. I did create an cgi file to load the django app with the django cgi-handler pip install django-cgi
  3. Changed the settings.py for deployment (secret key settings, allowed host settings, debug = false)
  4. Read deployment guides and official documentation
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', '6ps8j!crjgrxt34cqbqn7x&b3y%(fny8k8nh21+qa)%ws3fh!q')
# SECURITY WARNING: don't run with debug turned on in production!
#DEBUG = True
DEBUG = os.environ.get('DJANGO_DEBUG', '') != 'False'

ALLOWED_HOSTS = ['*']

I did set allowed_hosts=['*'] because i read that django wont run if its empty [] .

Q: Is it possible that i have to set the Secret_key with some bash command so the app finds it ? Q: What exactly do i have to set allowed_hosts to ? Something like allowed_hosts=['www.website.de']

settings.py file:

"""
Django settings for commerce project.

Generated by 'django-admin startproject' using Django 3.0.2.

For more information on this file, see
https://docs.djangoproject.com/en/3.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""

import os
from urllib.parse import urlparse


# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
#SECRET_KEY = '6ps8j!crjgrxt34cqbqn7x&b3y%(fny8k8nh21+qa)%ws3fh!q'
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', '6ps8j!crjgrxt34cqbqn7x&b3y%(fny8k8nh21+qa)%ws3fh!q')
# SECURITY WARNING: don't run with debug turned on in production!
#DEBUG = True
DEBUG = os.environ.get('DJANGO_DEBUG', '') != 'False'

ALLOWED_HOSTS = ['*']


# Application definition

INSTALLED_APPS = [
    'trainer',
    'tinymce',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'denisevreden.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'denisevreden.wsgi.application'


# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

AUTH_USER_MODEL = 'trainer.User'

# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# The absolute path to the directory where collectstatic will collect static files for deployment.

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# rest of the code

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

# The URL to use when referring to static files (where they will be served from)
STATIC_URL = '/static/'

# Email settings

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.strato.de'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = *******
EMAIL_HOST_PASSWORD = *******

#csrf settings
CSRF_TRUSTED_ORIGINS = ['https://*.mydomain.com','https://*.127.0.0.1', 'https://localhost:8000']

# TinyMCE settings


TINYMCE_JS_URL = 'https://cdn.tiny.cloud/1/no-api-key/tinymce/5/tinymce.min.js'
TINYMCE_COMPRESSOR = False

TINYMCE_DEFAULT_CONFIG = {
    "height": "320px",
    "width": "960px",
    "menubar": "file edit view insert format tools table help",
    "plugins": "advlist autolink lists link image charmap print preview anchor searchreplace visualblocks code "
    "fullscreen insertdatetime  table paste code help wordcount spellchecker",
    "toolbar": "undo redo | bold italic underline strikethrough | fontselect fontsizeselect formatselect | alignleft "
    "aligncenter alignright alignjustify | outdent indent |  numlist bullist checklist | forecolor "
    "backcolor casechange permanentpen formatpainter removeformat | pagebreak | charmap emoticons | "
    "fullscreen  preview save print | insertfile image pageembed template link anchor codesample | "
    "a11ycheck ltr rtl | showcomments addcomment code",
    "custom_undo_redo_levels": 10,
}


manage.py file:

#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import dependencies
import os, sys


def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'denisevreden.settings')
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()

** cgi_handler.py:**

#!/usr/local/python/3.10.4/bin/python
import wsgiref.handlers

from denisevreden.wsgi import application

wsgiref.handlers.CGIHandler().run(application)

happy.cgi file :

#!/usr/bin/python3
import os
import sys
#!/usr/bin/env python
import os
import sys

# Add the path to the Django project to the system path
sys.path.append('/mnt/web315/a3/18/512337018/htdocs/.cgi-bin/Happy-dv')

# Set the environment variable to tell Django where the settings module is
os.environ['DJANGO_SETTINGS_MODULE'] = 'Happy-dv.settings'

# Import the CGIHandler class
from wsgiref.handlers import CGIHandler

# Run the application using the CGIHandler
CGIHandler().run(application)
try:
    import wsgiref.handlers
    from denisevreden.wsgi import application
    wsgiref.handlers.CGIHandler().run(application)


except Exception as err:
    print("Content-Typ: text/html\n\n")
    print(err)

dependencies.py file:

#!/usr/bin/python3
import os
import sys
#!/usr/bin/env python
import os
import sys

# Add the path to the Django project to the system path
sys.path.append('/mnt/web315/a3/18/512337018/htdocs/.cgi-bin/Happy-dv')

# Set the environment variable to tell Django where the settings module is
os.environ['DJANGO_SETTINGS_MODULE'] = 'Happy-dv.settings'

# Import the CGIHandler class
from wsgiref.handlers import CGIHandler

# Run the application using the CGIHandler
CGIHandler().run(application)
try:
    import wsgiref.handlers
    from denisevreden.wsgi import application
    wsgiref.handlers.CGIHandler().run(application)


except Exception as err:
    print("Content-Typ: text/html\n\n")
    print(err)

THanks


Solution

  • There is a CGI script available for Django.
    This however does have some serious drawbacks.
    Because of the way CGI works; it spawns a new process every time a request is made. This basically means it will start up django on every request. It might also have some serious security implications; as listed here.

    CGI software has the potential to create some enormous security holes on your system. If they are carelessly programmed, they allow evildoers out there on the Internet to enter Unix commands into a form and have them executed on your web server. This article covers the basics of making your CGI software secure.

    That aside; the script is publicly available in the following github respository: https://github.com/chibiegg/django-cgi/blob/master/django-python3.cgi

    The repo is rather old; so I cannot promise it will work out of the box.

    #!/home/user/.pyenv/versions/envname/bin/python
    # encoding: utf-8
    """
    django-.cgi
    
    A simple cgi script which uses the django WSGI to serve requests.
    
    Code copy/pasted from PEP-0333 and then tweaked to serve django.
    http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side
    
    This script assumes django is on your sys.path, and that your site code is at
    /djangoproject/src. Copy this script into your cgi-bin directory (or do
    whatever you need to to make a cgi script executable on your system), and then
    update the paths at the bottom of this file to suit your site.
    
    This is probably the slowest way to serve django pages, as the python
    interpreter, the django code-base and your site code has to be loaded every
    time a request is served. FCGI and mod_python solve this problem, use them if
    you can.
    
    In order to speed things up it may be worth experimenting with running
    uncompressed zips on the sys.path for django and the site code, as this can be
    (theorectically) faster. See PEP-0273 (specifically Benchmarks).
    http://www.python.org/dev/peps/pep-0273/
    
    Make sure all python files are compiled in your code base. See
    http://docs.python.org/lib/module-compileall.html
    
    """
    
    
    import os, sys
    
    # Change this to the directory above your site code.
    # sys.path.append("/home/user/local/lib/python3.4/site-packages")
    # sys.path.append("/djangoproject/src")
    
    def run_with_cgi(application):
    
        environ                      = dict(os.environ.items())
        environ['wsgi.input']        = sys.stdin.buffer
        environ['wsgi.errors']       = sys.stderr.buffer
        environ['wsgi.version']      = (1,0)
        environ['wsgi.multithread']  = False
        environ['wsgi.multiprocess'] = True
        environ['wsgi.run_once']     = True
    
        if environ.get('HTTPS','off') in ('on','1'):
            environ['wsgi.url_scheme'] = 'https'
        else:
            environ['wsgi.url_scheme'] = 'http'
    
        headers_set  = []
        headers_sent = []
    
        def write(data):
            if not headers_set:
                 raise AssertionError("write() before start_response()")
    
            elif not headers_sent:
                 # Before the first output, send the stored headers
                 status, response_headers = headers_sent[:] = headers_set
                 sys.stdout.buffer.write(('Status: %s\r\n' % status).encode("ascii"))
                 for header in response_headers:
                     sys.stdout.buffer.write(('%s: %s\r\n' % header).encode("ascii"))
                 sys.stdout.buffer.write(('\r\n').encode("ascii"))
    
            sys.stdout.buffer.write(data)
            sys.stdout.buffer.flush()
    
        def start_response(status,response_headers,exc_info=None):
            if exc_info:
                try:
                    if headers_sent:
                        # Re-raise original exception if headers sent
                        raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
                finally:
                    exc_info = None     # avoid dangling circular ref
            elif headers_set:
                raise AssertionError("Headers already set!")
    
            headers_set[:] = [status,response_headers]
            return write
    
        result = application(environ, start_response)
        try:
            for data in result:
                if data:    # don't send headers until body appears
                    write(data)
            if not headers_sent:
                write('')   # send headers now if body was empty
        finally:
            if hasattr(result,'close'):
                result.close()
    
    # Change to the name of your settings module
    os.environ['DJANGO_SETTINGS_MODULE'] = 'application.settings'
    from django.core.wsgi import get_wsgi_application
    run_with_cgi(get_wsgi_application())