From Python script to WSGI application
- Tutorial
There was a task to write a web-based device management interface. The device will be controlled by the Raspberry Pi. The control logic is python, respectively, and the interface would like python. I want to share my experience.
1. To solve the problem "in the forehead" lighttpd with mod_cgi was raised:
Excerpt lighttpd.conf:
/var/www/index.py:
now localhost / index.py answered peppy "Hello World!"
When lighttpd encounters a file with the extension .py passes it to python for execution and its result responds to the request. Roughly speaking, redirects stdout.
After some attempts to write the interface “from scratch”, HtmlGenerator was born , which allowed us not to overload the code with html tags, made it very simple, but still did not solve the problem in the complex.
2. It was decided to experiment with web frameworks. Wep.py , simple and lightweight ,
came to hand . code.py:
The minimum code and our web application hangs on port 8080.
It would seem to forward an alias to port 8080, arrange auto-launch of the script, and you're done.
Yes, but no, experiments on a weak computer showed that the presence of our script makes the machine pretty "sulk". In addition there is lighttpd with mod_cgi.
How to connect a simple script and a web application.
3. According to the WSGI description , an interface of this kind is required for its implementation
Nothing military, but something does not work, something is missing.
A moment that was unclear after reading the wiki and other articles that all the same will launch our interface.
4. To start the WSGI application, you need a server. An example script that can act as a simple WSGI server:
wsgi.py:
Now, having added its launch to our interface, we will get a script that will respond already on our lighttpd or apache, at localhost / app.py
/var/www/app.py:
5. For python 2.7, the wsgiref module is available which can implement the WSGI server
6. Implementing WSGI with flup:
install flup
7. A simple web.py application using flup:
/var/www/app.py:
the application will be available at localhost / app.py
8. By default, web.py uses flup, but you can do without it.
To run web.py on wsgiref you need to:
In the links to web.py scripts at the end, do not forget to put '/' (app.py/), otherwise the answer will be “not found”. In a good way, you need to create a rewrite rule:
For debugging in scripts, it is useful to add:
then errors will be visible.
It remains to try:
modwsgi
paste
pylons
Useful links:
WSGI wiki
wep.py
WSGI - protocol for communication of a Web server with a Python application
WSGI, introduction
How to serve a WSGI application via CGI
WSGI.org
Comparison of the effectiveness of ways to run web applications in Python
- 1. lighttpd mod_cgi and a simple script
- 2. web.py on port 8080
- 3. WCGI interface
- 4. Simple WSGI Server
- 5. WSGI using wsgiref
- 6. WSGI using flup
- 7. web.py application using flup
- 8. A few features
1. To solve the problem "in the forehead" lighttpd with mod_cgi was raised:
sudo apt-get install lighttpd
sudo nano /etc/lighttpd/lighttpd.conf
Excerpt 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!"
now localhost / index.py answered peppy "Hello World!"
When lighttpd encounters a file with the extension .py passes it to python for execution and its result responds to the request. Roughly speaking, redirects stdout.
After some attempts to write the interface “from scratch”, HtmlGenerator was born , which allowed us not to overload the code with html tags, made it very simple, but still did not solve the problem in the complex.
2. It was decided to experiment with web frameworks. Wep.py , simple and lightweight ,
came to hand . 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()
The minimum code and our web application hangs on port 8080.
It would seem to forward an alias to port 8080, arrange auto-launch of the script, and you're done.
Yes, but no, experiments on a weak computer showed that the presence of our script makes the machine pretty "sulk". In addition there is lighttpd with mod_cgi.
How to connect a simple script and a web application.
3. According to the WSGI description , an interface of this kind is required for its implementation
#! /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']
Nothing military, but something does not work, something is missing.
A moment that was unclear after reading the wiki and other articles that all the same will launch our interface.
4. To start the WSGI application, you need a server. An example script that can act as a simple WSGI server:
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()
Now, having added its launch to our interface, we will get a script that will respond already on our lighttpd or apache, at 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. For python 2.7, the wsgiref module is available which can implement the WSGI server
#! /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. Implementing WSGI with flup:
install 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. A simple web.py application using 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()
the application will be available at localhost / app.py
8. By default, web.py uses flup, but you can do without it.
To run web.py on wsgiref you need to:
web.application(urls, globals()).cgirun()
In the links to web.py scripts at the end, do not forget to put '/' (app.py/), otherwise the answer will be “not found”. In a good way, you need to create a rewrite rule:
# mod_rewrite configuration.
url.rewrite-once = (
"^/favicon.ico$" => "/favicon.ico",
"^/(.*)$" => "app.py/$1" ,)
For debugging in scripts, it is useful to add:
import cgitb
cgitb.enable()
then errors will be visible.
It remains to try:
modwsgi
paste
pylons
Useful links:
WSGI wiki
wep.py
WSGI - protocol for communication of a Web server with a Python application
WSGI, introduction
How to serve a WSGI application via CGI
WSGI.org
Comparison of the effectiveness of ways to run web applications in Python