От 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.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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *