Conoha WingのCGIでDjangoを動かす

DjangoをCGIで動かすのはパフォーマンスの観点からあまりオススメできないのですが、ちょっとした小さいプロジェクトを手軽に公開するのにConoha Wing上のCGIで動かすのは悪くない選択肢です。

作業の流れ

  • Pythonのバージョンを上げる
  • Djangoプロジェクトを作成する+venv環境
  • index.cgiの設置と.htaccessでの設定
  • Django settings.pyの最低限の設定

Pythonのバージョンを上げる

Conoha WingへSSH接続してpythonのバージョンを確認してみます。

$ python -V
Python 3.6.11 # 古い…

実はConoha Wingには比較的新しいバージョンのpythonがインストールされていますので確認してみます。

$ which python
/usr/local/bin/python
$ ls -l /usr/local/bin/python
lrwxrwxrwx 1 root root 31  9月 11  2020 /usr/local/bin/python -> /opt/alt/python36/bin/python3.6
$ ls /opt/alt/ | grep python
python27
python311
python36
python37
python38
$ /opt/alt/python311/bin/python3 -V
Python 3.11.5

Python 3.11を使っていきます。

Djangoプロジェクトを作成する+venv環境

Djangoプロジェクトを保管するディレクトリをホームディレクトリに作成します。

$ mkdir djangoproject
$ cd djangoproject
$ pwd
/home/aruita/djangoproject

Django等のライブラリをローカルに保存するのにvenvを使用します。

$ /opt/alt/python311/bin/python3 -m venv venv
$ . venv/bin/activate
(venv) $ python -V
Python 3.11.5
(venv) $ which python
~/djangoproject/venv/bin/python

Djangoをインストールします。

(venv) $ pip install django
...
Successfully installed asgiref-3.8.1 django-5.0.4 sqlparse-0.4.4

mysiteという名前のプロジェクトを作ります。

(venv) $ pwd
/home/aruita/djangoproject
(venv) $ django-admin startproject mysite
(venv) $ cd mysite
(venv) $ pwd
/home/aruita/djangoproject/mysite

index.cgiの設置と.htaccessでの設定

次に、公開ディレクトリにindex.cgiを作成します。公開URLはexample.comとします。適時読み替えてください。

(venv) $ cd ~/public_html/example.com
(venv) $ vi index.cgi

コードはgithubに公開されている django-python3.cgi を参考にします。
https://github.com/chibiegg/django-cgi/blob/master/django-python3.cgi

#!/home/aruita/djangoproject/venv/bin/python
# 仮想環境Pythonのフルパス
import os, sys

sys.path.append("/home/aruita/djangoproject/mysite") # Djangoプロジェクトのルートディレクトリ

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()

os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings' # Djangoプロジェクトのsettingsモジュール
from django.core.wsgi import get_wsgi_application
run_with_cgi(get_wsgi_application())

index.cgiに実行権限を付与、.htaccessによる設定を行います。

(venv) $ chmod +x index.cgi
(venv) $ vi .htaccess
RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f 
RewriteRule ^(.*)$ index.cgi/$1 [L] 

Django settings.pyの最低限の設定

ブラウザで example.com へアクセスするとDisallowedHostのエラーになるので、settings.py内のALLOWED_HOSTSにドメインを追加します。

(venv) $ vi ~/djangoproject/mysite/mysite/settings.py
#ALLOWED_HOSTS = []
ALLOWED_HOSTS = ['example.com',]

これで動作します。