Python Dev Web Ser
Python Dev Web Ser
So you want to start developing a Python application do ya? Let’s assume we are planning to use a smaller framework like Flask which is a
framework I’m really loving at the moment.
Update (6th of April 2014): In the past, I had found Flask’s in-built development server to be a bit unstable which is why I put together this
entry but this is no longer the case. As such, I now recommend using the dev server that comes with Flask for development purposes.
However, if you prefer to use another server instead, please read on.
In this article, I’ll show you how to setup various web servers to serve Flask applications using WSGI which may also be suitable for
production use later on.
CherryPy
CherryPy’s web server is very well regarded and was one of the rst I looked at. My main criticism of CherryPy was its documentation
which I found extremely di cult to read through and grasp. With a bit of digging around, I managed to get everything to work. You must
use the Paste library for logging.
1 #!/usr/bin/env python
2
3 from flask import Flask
4 import cherrypy
5 from paste.translogger import TransLogger
6
7 app = Flask(__name__)
8 app.debug = True
9
10
11 @app.route("/")
12 def hello():
13 return "Hello World!"
14
15
16 def run_server():
17 # Enable WSGI access logging via Paste
18 app_logged = TransLogger(app)
19
20 # Mount the WSGI callable object (app) on the root directory
21 cherrypy.tree.graft(app_logged, '/')
22
23 # Set the configuration of the web server
24 cherrypy.config.update({
25 'engine.autoreload_on': True,
26 'log.screen': True,
27 'server.socket_port': 5000,
28 'server.socket_host': '0.0.0.0'
29 })
30
31 # Start the CherryPy WSGI web server
32 cherrypy.engine.start()
33 cherrypy.engine.block()
34
35 if __name__ == "__main__":
36 run_server()
With a bit of extra e ort, we can customise the access logging from Paste to be consistent with CherryPy if desired:
1 #!/usr/bin/env python
2
3 import time
4
5 from flask import Flask
6 import cherrypy
7 from paste.translogger import TransLogger
8
9 app = Flask(__name__)
10 app.debug = True
11
12
13 @app.route("/")
14 def hello():
15 return "Hello World!"
16
17
18 class FotsTransLogger(TransLogger):
19 def write_log(self, environ, method, req_uri, start, status, bytes):
20 """ We'll override the write_log function to remove the time offset so
21 that the output aligns nicely with CherryPy's web server logging
22
23 i.e.
24
25 [08/Jan/2013:23:50:03] ENGINE Serving on 0.0.0.0:5000
26 [08/Jan/2013:23:50:03] ENGINE Bus STARTED
27 [08/Jan/2013:23:50:45 +1100] REQUES GET 200 / (192.168.172.1) 830
28
29 becomes
30
31 [08/Jan/2013:23:50:03] ENGINE Serving on 0.0.0.0:5000
32 [08/Jan/2013:23:50:03] ENGINE Bus STARTED
33 [08/Jan/2013:23:50:45] REQUES GET 200 / (192.168.172.1) 830
34 """
35
36 if bytes is None:
37 bytes = '-'
38 remote_addr = '-'
39 if environ.get('HTTP_X_FORWARDED_FOR'):
40 remote_addr = environ['HTTP_X_FORWARDED_FOR']
41 elif environ.get('REMOTE_ADDR'):
42 remote_addr = environ['REMOTE_ADDR']
43 d = {
44 'REMOTE_ADDR': remote_addr,
45 'REMOTE_USER': environ.get('REMOTE_USER') or '-',
46 'REQUEST_METHOD': method,
47 'REQUEST_URI': req_uri,
48 'HTTP_VERSION': environ.get('SERVER_PROTOCOL'),
49 'time': time.strftime('%d/%b/%Y:%H:%M:%S', start),
50 'status': status.split(None, 1)[0],
51 'bytes': bytes,
52 'HTTP_REFERER': environ.get('HTTP_REFERER', '-'),
53 'HTTP_USER_AGENT': environ.get('HTTP_USER_AGENT', '-'),
54 }
55 message = self.format % d
56 self.logger.log(self.logging_level, message)
57
58
59 def run_server():
60 # Enable custom Paste access logging
61 log_format = (
62 '[%(time)s] REQUES %(REQUEST_METHOD)s %(status)s %(REQUEST_URI)s '
63 '(%(REMOTE_ADDR)s) %(bytes)s'
64 )
65 app_logged = FotsTransLogger(app, format=log_format)
66
67 # Mount the WSGI callable object (app) on the root directory
68 cherrypy.tree.graft(app_logged, '/')
69
70 # Set the configuration of the web server
71 cherrypy.config.update({
72 'engine.autoreload_on': True,
73 'log.screen': True,
74 'server.socket_port': 5000,
75 'server.socket_host': '0.0.0.0'
76 })
77
78 # Start the CherryPy WSGI web server
79 cherrypy.engine.start()
80 cherrypy.engine.block()
81
82 if __name__ == "__main__":
83 run_server()
Gevent
http://www.gevent.org/ appears to be one of the fastest WSGI web servers out there and provides all the features we are after too!
1 #!/usr/bin/env python
2
3 from flask import Flask
4 import gevent.wsgi
5 import gevent.monkey
6 import werkzeug.serving
7
8 gevent.monkey.patch_all()
9 app = Flask(__name__)
10 app.debug = True
11
12
13 @app.route("/")
14 def hello():
15 return "Hello World!"
16
17
18 @werkzeug.serving.run_with_reloader
19 def run_server():
20 ws = gevent.wsgi.WSGIServer(listener=('0.0.0.0', 5000),
21 application=app)
22 ws.serve_forever()
23
24 if __name__ == "__main__":
25 run_server()
Gunicorn
Gunicorn is a production-ready web server for Python. I must commend the designer of the site who shows that even Python-related sites
can look beautiful! Unfortunately, Gunicorn (being a server aimed at production use) makes it a lot harder to get auto-restart capabilities as
this functionality is not natively included.
To make it all happen with Gunicorn, we’re going to need supervisor and watchdog to monitor for changes and trigger a restart of
Gunicorn.
These tools rely on several C libraries, so there’s a bit more to it than just using pip:
1 [supervisord]
2 logfile=test.log
3 loglevel=debug
4 nodaemon=true
5
6 [program:test]
7 autostart=true
8 command=gunicorn --pid /tmp/flask-project.pid --workers 4 --log-level debug -b 0.0.0.0:5000 test:app
9
10 [program:test-reloader]
11 autostart=true
12 autorestart=false
13 command=watchmedo shell-command --patterns="*.py;*.html;*.css;*.js" --recursive --command='kill -HUP $(cat /tmp/flask
The Python script stays super clean and simple which is nice:
1 #!/usr/bin/env python
2
3 from flask import Flask
4
5 app = Flask(__name__)
6 app.debug = True
7
8
9 @app.route("/")
10 def hello():
11 return "Hello World!"
1 supervisord -c test.conf
Overall though, I don’t see myself using Gunicorn for development purposes due to the added complexity involved. Another point worth
noting is that print statements to stdout do not appear on the console with Gunicorn, unlike the rest of the web servers tested here.
Rocket
Rocket is a newer pure Python WSGI web server which is also production ready. I thought it would be worth giving it a try too.
1 #!/usr/bin/env python
2
3 import logging
4 import sys
5
6 from flask import Flask
7 from rocket import Rocket
8
9 app = Flask(__name__)
10 app.debug = True
11
12
13 @app.route("/")
14 def hello():
15 return "Hello World!"
16
17
18 def run_server():
19 # Setup logging
20 log = logging.getLogger('Rocket')
21 log.setLevel(logging.INFO)
22 log.addHandler(logging.StreamHandler(sys.stdout))
23
24 # Set the configuration of the web server
25 server = Rocket(interfaces=('0.0.0.0', 5000), method='wsgi',
26 app_info={"wsgi_app": app})
27
28 # Start the Rocket web server
29 server.start()
30
31 if __name__ == "__main__":
32 run_server()
Unfortunately, Rocket (much like Gunicorn) is primarily aimed at production deployments, so it doesn’t include an auto-restart feature.
Tornado
Tornado appears to be well respected too and has no C dependencies.
Note: The latest Tornado 3.x has a signi cantly changed API and therefore the code below will not work with it. I may look into rewriting
the code below to work with Tornado 3.x when I have a spare moment.
Installing Tornado is as easy as CherryPy:
1 #!/usr/bin/env python
2
3 from flask import Flask
4 import tornado.wsgi
5 import tornado.httpserver
6 import tornado.ioloop
7 import tornado.options
8 import tornado.autoreload
9
10 app = Flask(__name__)
11 app.debug = True
12
13
14 @app.route("/")
15 def hello():
16 return "Hello World!"
17
18
19 def run_server():
20 # Create the HTTP server
21 http_server = tornado.httpserver.HTTPServer(
22 tornado.wsgi.WSGIContainer(app)
23 )
24 http_server.listen(5000)
25
26 # Reads args given at command line (this also enables logging to stderr)
27 tornado.options.parse_command_line()
28
29 # Start the I/O loop with autoreload
30 io_loop = tornado.ioloop.IOLoop.instance()
31 tornado.autoreload.start(io_loop)
32 try:
33 io_loop.start()
34 except KeyboardInterrupt:
35 pass
36
37 if __name__ == "__main__":
38 run_server()
1 ...
2 from werkzeug.debug import DebuggedApplication
3 ...
4
5 def run_server():
6 # Enable the Werkzeug Debugger
7 app_debug = DebuggedApplication(app, evalex=True)
8 ...
Now simply ensure that you pass app_debug into sebsequent functions instead of app as we did above.
1 ...
2 import wsgilog
3
4 app = Flask(__name__)
5 app.debug = True
6
7 app_logged_wsgi = wsgilog.WsgiLog(app, tohtml=True, tofile='wsgi.log',
8 tostream=True, toprint=True)
9 ...
Now when initialising the web server, pass in app_logged_wsgi instead of app.
Final Words
To summarise, the following web servers failed to meet one or more criteria above:
Gunicorn: Does not display stdout via the print statement. Gunicorn is also more work to setup for a development server compared
to the rest.
Rocket: Doesn’t include auto-restart ability, but is less troublesome to work with in comparison to Gunicorn.
As far as Jython is concerned, I’m sorry to say that none of the web servers worked with it. I also tried them with 2.7b1 and still no dice.
LOG IN WITH
OR SIGN UP WITH DISQUS ?
Name
Cheers
Fotis
△ ▽ • Reply • Share ›
I lean towards using flask for the dynamic stuff due to its simple nature and extension modules like flask-login.
△ ▽ • Reply • Share ›
What I did is calling 'supervisorclt restart test' in the watchmedo command. Isn't it cleaner?
△ ▽ • Reply • Share ›
Building Git on SLES 10 SP3 - Fotsies Technology Blog Curing RSI (Repetitive Strain Injury) - Fotsies Technology Blog
4 comments • 6 years ago 6 comments • 6 years ago
fgimian — Fantastic, glad my guide was helpful! :) fgimian — Great job! Hope this can help many people :)
Building Ruby 1.9.3 and 2.0.0 with RVM and Rails on SLES 10 SP3 - Essential Python Libraries - Fotsies Technology Blog
Fotsies … 6 comments • 5 years ago
8 comments • 6 years ago fgimian — Yup, it's one fascinating implementation :)
fgimian — Hey there Deepak,I'm sorry but I don't have any experience with SLES 11.
Our company is using RHEL for all new builds …
Theme by beautiful-jekyll