Как стать автором
Поиск
Написать публикацию
Обновить

От Python скрипта до WSGI приложения

Время на прочтение4 мин
Количество просмотров50K
Появилась задача написать веб интерфейс управления устройством. Управлять устройством будет Raspberry Pi. Логика управления — python, соответственно и интерфейс хотелось бы python. Хочу поделится своим опытом.

  • 1. lighttpd mod_cgi и простой скрипт
  • 2. web.py на порту 8080
  • 3. WCGI интерфейс
  • 4. Простой сервер WSGI
  • 5. WSGI с использованием wsgiref
  • 6. WSGI c помощью flup
  • 7. web.py приложение с использованием flup
  • 8. Немного особенностей


1. Для решения задачи «в лоб» был поднят lighttpd c mod_cgi:

sudo apt-get install lighttpd
sudo nano /etc/lighttpd/lighttpd.conf

Отрывок lighttpd.conf:

#mod_cgi shoud be on
server.modules = (
    "mod_access",
    "mod_alias",
    "mod_compress",
    "mod_redirect",
    "mod_cgi",
    "mod_rewrite",
)
#rule enables cgi script
cgi.assign = (".py" => "/usr/bin/python")

/var/www/index.py:

print "Content-Type: text/html\n\n"
print "Hello World!"

теперь localhost/index.py отвечал бодрым «Hello World!»

Когда lighttpd встречает файл с расширением .py передает его на выполнение python-у и его результатом отвечает на запрос. Грубо говоря перенаправляет stdout.
После некоторых попыток написания интерфейса «с нуля», был рожден HtmlGenerator, который позволил не перегружать код html-тегами, весьма упростил, но все таки не решил проблемы в комплексе.

2. Решено было поэкспериментировать с веб фреймворками.
Под руку попался wep.py, простенький и маловесный.
code.py:

#! /usr/bin/python
#
import web
urls = ( '/', 'index',)

class index:
    def GET(self):
        return "Hello, world!"

if __name__ == "__main__":
    web.application(urls, globals()).run()

Минимальный код и на порту 8080 висит наше веб приложение
Казалось бы пробросить алиас на порт 8080, организовать авто запуск скрипта и все готово.
Да но нет, эксперименты на слабеньком компьютере показали что присутствие нашего скрипта заставляет машинку изрядно «дуться». Кроме того есть lighttpd с mod_cgi.

Как же связать простой скрипт и веб приложение.

3. Согласно описанию WSGI, для его реализации необходим интерфейс такого вида
#! /usr/bin/python
#
def myapp(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type','text/plain')]
    start_response(status, response_headers)
    return ['Hello World!\n']

Ничего военного, но и что-то не работает, чего-то не хватает.
Момент который был неясным после прочтения вики и других статей, что же все таки запустит наш интерфейс.

4. Для запуска WSGI приложения нужен сервер. Пример скрипта который может выступать в роли простого сервера WSGI:
wsgi.py:
#! /usr/bin/python
import os
import sys

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:
            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:
                    raise exc_info[0], exc_info[1], exc_info[2]
            finally:
                exc_info = None
        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:
                write(data)
        if not headers_sent:
            write('')
    finally:
        if hasattr(result, 'close'):
            result.close()

Теперь добавив к нашему интерфейсу его запуск получим скрипт который ответит уже на нашем lighttpd или apache, по адресу localhost/app.py
/var/www/app.py:
#! /usr/bin/python
include wsgi

def myapp(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type','text/plain')]
    start_response(status, response_headers)
    return ['Hello World!\n']

if __name__ == '__main__':
    wsgi.run_with_cgi(myapp)


5. Для python 2.7 доступен модуль wsgiref который может реализовать WSGI сервер
#! /usr/bin/python
import wsgiref.handlers

def myapp(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type','text/plain')]
    start_response(status, response_headers)
    return ['Hello World!\n']

if __name__ == '__main__':
    wsgiref.handlers.CGIHandler().run(myapp)


6. Реализация WSGI c помощью flup:
установим flup
sudo apt-get install python-flup

#! /usr/bin/python
import flup.server.fcgi

def myapp(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type','text/plain')]
    start_response(status, response_headers)
    return ['Hello World!\n']

if __name__ == '__main__':
    flup.server.fcgi.WSGIServer(myapp).run()


7. Простое web.py приложение с использованием flup:
/var/www/app.py:
#! /usr/bin/python
import web
urls = (  '/', 'index', )

class index:
    def GET(self):
        return "Hello World!"

if __name__ == '__main__':
    web.application(urls, globals()).run()

приложение станет доступным по адресу localhost/app.py

8. По умолчанию web.py использует flup, но можно обойтись и без него.
Для запуска web.py на wsgiref необходимо:
web.application(urls, globals()).cgirun()

B ссылках на скрипты web.py в конце не забывать ставить '/' (app.py/), иначе ответом будет «not found». По-хорошему необходимо создать rewrite правило:
# mod_rewrite configuration.
url.rewrite-once = (
    "^/favicon.ico$" => "/favicon.ico", 
    "^/(.*)$" => "app.py/$1" ,)

Для отладки в скриптов полезно добавить:
import cgitb
cgitb.enable()

тогда будут видны ошибки.

Остается опробовать:
modwsgi
paste
pylons

Полезные ссылки:
WSGI wiki
wep.py
WSGI — протокол связи Web-сервера с Python приложением
WSGI, введение
How to serve a WSGI application via CGI
WSGI.org
Сравнение эффективности способов запуска веб-приложений на языке Python
Теги:
Хабы:
Всего голосов 14: ↑12 и ↓2+10
Комментарии10

Публикации

Ближайшие события