File: running.rst

package info (click to toggle)
python-tornado 6.2.0-3%2Bdeb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 3,396 kB
  • sloc: python: 27,837; javascript: 156; sh: 99; ansic: 58; xml: 49; makefile: 48; sql: 23
file content (283 lines) | stat: -rw-r--r-- 10,932 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
Running and deploying
=====================

Since Tornado supplies its own HTTPServer, running and deploying it is
a little different from other Python web frameworks.  Instead of
configuring a WSGI container to find your application, you write a
``main()`` function that starts the server:

.. testcode::

    import asyncio

    async def main():
        app = make_app()
        app.listen(8888)
        await asyncio.Event().wait()

    if __name__ == '__main__':
        asyncio.run(main())

.. testoutput::
   :hide:

Configure your operating system or process manager to run this program to
start the server. Please note that it may be necessary to increase the number 
of open files per process (to avoid "Too many open files"-Error). 
To raise this limit (setting it to 50000 for example)  you can use the
``ulimit`` command, modify ``/etc/security/limits.conf`` or set
``minfds`` in your `supervisord <http://www.supervisord.org>`_ config.

Processes and ports
~~~~~~~~~~~~~~~~~~~

Due to the Python GIL (Global Interpreter Lock), it is necessary to run
multiple Python processes to take full advantage of multi-CPU machines.
Typically it is best to run one process per CPU.

The simplest way to do this is to add ``reuse_port=True`` to your ``listen()``
calls and then simply run multiple copies of your application.

Tornado also has the ability to start multiple processes from a single parent
process (note that this does not work on Windows). This requires some
alterations to application startup.

.. testcode::

    def main():
        sockets = bind_sockets(8888)
        tornado.process.fork_processes(0)
        async def post_fork_main():
            server = TCPServer()
            server.add_sockets(sockets)
            await asyncio.Event().wait()
        asyncio.run(post_fork_main())

.. testoutput::
   :hide:

This is another way to start multiple processes and have them all
share the same port, although it has some limitations.  First, each
child process will have its own ``IOLoop``, so it is important that
nothing touches the global ``IOLoop`` instance (even indirectly) before the
fork.  Second, it is difficult to do zero-downtime updates in this model.
Finally, since all the processes share the same port it is more difficult
to monitor them individually.

For more sophisticated deployments, it is recommended to start the processes
independently, and have each one listen on a different port.
The "process groups" feature of `supervisord <http://www.supervisord.org>`_
is one good way to arrange this.  When each process uses a different port,
an external load balancer such as HAProxy or nginx is usually needed
to present a single address to outside visitors.


Running behind a load balancer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When running behind a load balancer like `nginx <http://nginx.net/>`_,
it is recommended to pass ``xheaders=True`` to the `.HTTPServer` constructor.
This will tell Tornado to use headers like ``X-Real-IP`` to get the user's
IP address instead of attributing all traffic to the balancer's IP address.

This is a barebones nginx config file that is structurally similar to
the one we use at FriendFeed. It assumes nginx and the Tornado servers
are running on the same machine, and the four Tornado servers are
running on ports 8000 - 8003::

    user nginx;
    worker_processes 1;

    error_log /var/log/nginx/error.log;
    pid /var/run/nginx.pid;

    events {
        worker_connections 1024;
        use epoll;
    }

    http {
        # Enumerate all the Tornado servers here
        upstream frontends {
            server 127.0.0.1:8000;
            server 127.0.0.1:8001;
            server 127.0.0.1:8002;
            server 127.0.0.1:8003;
        }

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        access_log /var/log/nginx/access.log;

        keepalive_timeout 65;
        proxy_read_timeout 200;
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        gzip on;
        gzip_min_length 1000;
        gzip_proxied any;
        gzip_types text/plain text/html text/css text/xml
                   application/x-javascript application/xml
                   application/atom+xml text/javascript;

        # Only retry if there was a communication error, not a timeout
        # on the Tornado server (to avoid propagating "queries of death"
        # to all frontends)
        proxy_next_upstream error;

        server {
            listen 80;

            # Allow file uploads
            client_max_body_size 50M;

            location ^~ /static/ {
                root /var/www;
                if ($query_string) {
                    expires max;
                }
            }
            location = /favicon.ico {
                rewrite (.*) /static/favicon.ico;
            }
            location = /robots.txt {
                rewrite (.*) /static/robots.txt;
            }

            location / {
                proxy_pass_header Server;
                proxy_set_header Host $http_host;
                proxy_redirect off;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Scheme $scheme;
                proxy_pass http://frontends;
            }
        }
    }

Static files and aggressive file caching
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can serve static files from Tornado by specifying the
``static_path`` setting in your application::

    settings = {
        "static_path": os.path.join(os.path.dirname(__file__), "static"),
        "cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
        "login_url": "/login",
        "xsrf_cookies": True,
    }
    application = tornado.web.Application([
        (r"/", MainHandler),
        (r"/login", LoginHandler),
        (r"/(apple-touch-icon\.png)", tornado.web.StaticFileHandler,
         dict(path=settings['static_path'])),
    ], **settings)

This setting will automatically make all requests that start with
``/static/`` serve from that static directory, e.g.
``http://localhost:8888/static/foo.png`` will serve the file
``foo.png`` from the specified static directory. We also automatically
serve ``/robots.txt`` and ``/favicon.ico`` from the static directory
(even though they don't start with the ``/static/`` prefix).

In the above settings, we have explicitly configured Tornado to serve
``apple-touch-icon.png`` from the root with the `.StaticFileHandler`,
though it is physically in the static file directory. (The capturing
group in that regular expression is necessary to tell
`.StaticFileHandler` the requested filename; recall that capturing
groups are passed to handlers as method arguments.) You could do the
same thing to serve e.g. ``sitemap.xml`` from the site root. Of
course, you can also avoid faking a root ``apple-touch-icon.png`` by
using the appropriate ``<link />`` tag in your HTML.

To improve performance, it is generally a good idea for browsers to
cache static resources aggressively so browsers won't send unnecessary
``If-Modified-Since`` or ``Etag`` requests that might block the
rendering of the page. Tornado supports this out of the box with *static
content versioning*.

To use this feature, use the `~.RequestHandler.static_url` method in
your templates rather than typing the URL of the static file directly
in your HTML::

    <html>
       <head>
          <title>FriendFeed - {{ _("Home") }}</title>
       </head>
       <body>
         <div><img src="{{ static_url("images/logo.png") }}"/></div>
       </body>
     </html>

The ``static_url()`` function will translate that relative path to a URI
that looks like ``/static/images/logo.png?v=aae54``. The ``v`` argument
is a hash of the content in ``logo.png``, and its presence makes the
Tornado server send cache headers to the user's browser that will make
the browser cache the content indefinitely.

Since the ``v`` argument is based on the content of the file, if you
update a file and restart your server, it will start sending a new ``v``
value, so the user's browser will automatically fetch the new file. If
the file's contents don't change, the browser will continue to use a
locally cached copy without ever checking for updates on the server,
significantly improving rendering performance.

In production, you probably want to serve static files from a more
optimized static file server like `nginx <http://nginx.net/>`_. You
can configure almost any web server to recognize the version tags used
by ``static_url()`` and set caching headers accordingly.  Here is the
relevant portion of the nginx configuration we use at FriendFeed::

    location /static/ {
        root /var/friendfeed/static;
        if ($query_string) {
            expires max;
        }
     }

.. _debug-mode:

Debug mode and automatic reloading
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you pass ``debug=True`` to the ``Application`` constructor, the app
will be run in debug/development mode. In this mode, several features
intended for convenience while developing will be enabled (each of which
is also available as an individual flag; if both are specified the
individual flag takes precedence):

* ``autoreload=True``: The app will watch for changes to its source
  files and reload itself when anything changes. This reduces the need
  to manually restart the server during development. However, certain
  failures (such as syntax errors at import time) can still take the
  server down in a way that debug mode cannot currently recover from.
* ``compiled_template_cache=False``: Templates will not be cached.
* ``static_hash_cache=False``: Static file hashes (used by the
  ``static_url`` function) will not be cached.
* ``serve_traceback=True``: When an exception in a `.RequestHandler`
  is not caught, an error page including a stack trace will be
  generated.

Autoreload mode is not compatible with the multi-process mode of `.HTTPServer`.
You must not give `HTTPServer.start <.TCPServer.start>` an argument other than 1 (or
call `tornado.process.fork_processes`) if you are using autoreload mode.

The automatic reloading feature of debug mode is available as a
standalone module in `tornado.autoreload`.  The two can be used in
combination to provide extra robustness against syntax errors: set
``autoreload=True`` within the app to detect changes while it is running,
and start it with ``python -m tornado.autoreload myserver.py`` to catch
any syntax errors or other errors at startup.

Reloading loses any Python interpreter command-line arguments (e.g. ``-u``)
because it re-executes Python using `sys.executable` and `sys.argv`.
Additionally, modifying these variables will cause reloading to behave
incorrectly.

On some platforms (including Windows and Mac OSX prior to 10.6), the
process cannot be updated "in-place", so when a code change is
detected the old server exits and a new one starts.  This has been
known to confuse some IDEs.