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

Появилась задача написать веб интерфейс управления устройством. Управлять устройством будет 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.py8. По умолчанию 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 () тогда будут видны ошибки.Остается опробовать: modwsgipastepylons

Полезные ссылки: WSGI wikiwep.pyWSGI — протокол связи Web-сервера с Python приложениемWSGI, введениеHow to serve a WSGI application via CGIWSGI.orgСравнение эффективности способов запуска веб-приложений на языке Python

© Habrahabr.ru