Установка Django на devio.us

Как, наверняка, всем известно, недавно появился проект Devio.us, который предлагает всем желающим бесплатный shell-хостинг на базе OpenBSD. В нагрузку к шеллу также предлагается домен третьего уровня, вида %username%.devio.us или devio.us/~%username%, одна база данных MySQL и 100 мегов места. За пару баксов в месяц эти показатели можно улучшить :)
На сервере установлен PHP (версии 5.2.12) как модуль апача (весьма, кстати, древнего – 1.3.29) и предлагается CGI-интерфейс. Ну и также доступны perl 5.10.1 и python 2.5.4. Целью проекта является привлечение новых членов в коммьюнити OpenBSD. При регистрации необходимо указать причину, по которой вам кровь из носу надо получить там аккаунт :) Процент отказов, к слову, довольно мал.
Итак, поигравшись с PHP, захотелось чего-то большего. И так как на сервере присутствует python, захотелось прикрутить туда Django.

Установка Django
Проведя ревизию модулей apache я выяснил, что ни mod_python, ни mod_wsgi не установлены. Грустно, но ладно. Для начала я просто залил туда дистрибутив django (версии 1.1.1)

$ scp Django-1.1.1.tar.gz devio.us:

и распаковал его

$ gunzip Django-1.1.1.tar.gz
$ tar xvf Django-1.1.1.tar
$ ln -s Django-1.1.1 django #  для дальнейшего удобства

Добавим переменную окружения PYTHONPATH для корректной работы Django

$ export PYTHONPATH='/home/boombick/django'
$ echo "export PYTHONPATH='/home/boombick/django'" >> ~/.profile

И создадим симлинк для django-admin

$ mkdir ~/bin
$ ln -s /home/boombick/django/django/bin/django-admin.py /home/boombik/bin/django-admin.py

Дальше все просто: создаем новый проект Django

$ django-admin.py startproject djtest
$ cd djtest

И на этом этапе я просто запустил dev-сервер из поставки Django на высоком порту (занять порт с номером ниже 1024 без привелегий root не получится)

$ python manage.py runserver 0.0.0.0:32000

Зайдя на http://boombick.devio.us:32000 я увидел приветственную страницу пустого проекта Django. Все получилось :)

Запуск Django через CGI
В принципе, можно было бы оставить все и так. Но.
1. Хотелось все-таки ходить к себе на страничку по 80 порту
2. На free-аккаунте нельзя детачить приложения. Т.е. нельзя оставить запущенным сервер и разлогиниться. Что, впрочем, логично, иначе сервер превратился бы в рассадник IRC-ботов :)
Можно, конечно, извратиться и держать логин постоянно, восстанавливая оборванное подключение и перезапуская сервер, но это не наш путь.

Итак.
Сначала я просто пробрасывал запросы к Django через PHP (в виде index.php) :) Эдакий PHP-Proxy, но мало того, что это запредельно криво, так еще и не очень понятно, что делать с POST-запросами. Выход был один – заставить Django работать через CGI.
Помучив гугл я нашел несколько разноненных мануалов и приступил к выполнению.
Для начала создадим в директории $HOME/public_html файл django.cgi вот с таким содержанием:

#!/usr/local/bin/python
# encoding: utf-8
"""
django.cgi

A simple cgi script which uses the django WSGI to serve requests.
Rewrited for use on devio.us shell hosting

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
/home/mycode/mysite. 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
sys.path.append("/home/boombick/django") # Поменяйте на путь к Django
sys.path.append("/home/boombick/djtest")  # Поменяйте на путь к вашему проекту

import django.core.handlers.wsgi

def run_with_cgi(application):

    environ                      = dict(os.environ.items())
    environ['wsgi.input']        = sys.stdin
    environ['wsgi.errors']       = sys.stderr
    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.write('Status: %s\r\n' % status)
             for header in response_headers:
                 sys.stdout.write('%s: %s\r\n' % header)
             sys.stdout.write('\r\n')

        sys.stdout.write(data)
        sys.stdout.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], 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'] = 'settings'
run_with_cgi(django.core.handlers.wsgi.WSGIHandler())

Затем меняем в /path/to/dajngo-project/settings.py параметр ROOT_URLCONF на urls вместо projectname.urls
Делаем файл исполняемым

$ chmod a+x public_html/django.cgi

И проверяем работоспособность :)

http://boombick.devio.us/django.cgi

djangocgi.png
Если все ок, то можно переходить к следующему шагу. А если нет и вы получаете 500 ошибку, то смотрите лог. Который, кстати, лежит в /var/www/logs/error_log В процессе установки очень удобно было его мониторить в отдельной консольке при помощи tail -f
Учтите только, что в этот лог падают сообщения о всех ошибках и вам надо отделять свои запросы от остальных :)

Настройка Apache
Теперь надо настроить Apache для переадресации всех запросов к нашему django.cgi. Я очень давно не ковырялся с Apache (в последнее время использую исключительно nginx) и с трудом и доками вспоминал синтаксис mod_rewrite
В результате родился вот такой вот файлик .htaccess, который надо положить в директорию public_html

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /django.cgi%{REQUEST_URI}?%{QUERY_STRING}

Проверяем
В ~/djtest/urls.py добавим правило для обработки всех адресов

from django.conf.urls.defaults import *
from views import test_page

urlpatterns = patterns('',
    (r'^(.*)$', test_page)
)

И создадим файл ~/djtest/views.py с, собственно, обработчиком

from django.http import HttpResponse       

def test_page(request, url):
    html = "WORKING! Url is %s" % url
    return HttpResponse(html)

Заходим на http://boombick.devio.us/foo/bar
django.png
Все работает :)

Благодарности
Хочу сказать большое спасибо ребятам из конференции django@conference.jabber.ru, а конкретно ne_formal за неоценимую помощь :) Без него этот процесс затянулся бы куда больше!

Важное замечание
Если вы читали комментарии в начале файла django.cgi, то не могли не заметить такой абзац:

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.

Что в вольном переводе выглядит как:
Это самый медленный путь для работы с django. На каждый запрос создается экземпляр интерпретатора Python. Если у вас есть возможность использовать FCGI или mod_python – используйте их!
Так что статью прощу рассматривать исключительно как proof of concept или выход из совсем уж безвыходной ситуации! :)
Enjoy!


 

Система Orphus

 


 

Comments: 3

  1. bosha May 4th, 2010 at 2:00 pm

    Удобно однако. Использовать для отладки\показухи django проекта :)

  2. adw0rd May 18th, 2010 at 5:04 pm

    Да, cgi и mod_python это жуткие тормоза…

  3. DremLIN October 21st, 2010 at 3:41 am

    Спасибо. Очень помогло на Free Hostia. Усе работает!

Add a Comment