Появилась задача написать веб интерфейс управления устройством. Управлять устройством будет 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
ссылка на оригинал статьи http://habrahabr.ru/post/256481/
Добавить комментарий