http://habrahabr.ru/post/256481/
Появилась задача написать веб интерфейс управления устройством. Управлять устройством будет 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 wikiwep.pyWSGI — протокол связи Web-сервера с Python приложениемWSGI, введениеHow to serve a WSGI application via CGIWSGI.orgСравнение эффективности способов запуска веб-приложений на языке Python