Python Web Framework Tornado Run and Deploy

This example shares details of the operation and deployment of the Python Web framework, Tornado, for your reference, as follows

I. Operation and Deployment

Because Tornado has its own HTTPServer built in, it runs and deploys differently from other Python web frameworks. Instead of configuring a WSGI container to run your application, you need to write a main() function to start the service:

​
    def main():
     app = make_app()
     app.listen(8888)
     IOLoop.current().start()
    
    if __name__ == '__main__':
     main()
    

Configure your operating system or process manager to run this program to start the service. Note that it may be necessary to increase the maximum number of file handles allowed to open per process (to avoid "Too many open files" errors). To increase this limit (for example, set to 50000), you can use the ulimit command to modify/etc/security/limits.conf or set minfds in your supervisor configuration.

2. Processes and Ports

Because of Python's GIL (Global Interpreter Lock), it is necessary to run multiple Python processes in order to make full use of multiple CPU machines. Usually, it is best to run one process per CPU.

Tornado contains a built-in multi-process mode to start multiple processes at once, which requires a slight change in the main function:

​
    def main():
     app = make_app()
     server = tornado.httpserver.HTTPServer(app)
     server.bind(8888)
     server.start(0) # forks one process per cpu
     IOLoop.current().start()
    

This is the easiest way to start multiple processes and have them share the same port, although it has some limitations. First, each child process will have its own IOLoop, so it is important (or even indirect) that no global IOLoop instances are touched before fork. Second, in this model, it is difficult to do zero-downtime updates. Finally, since all processes share the same ports, it is even more difficult to monitor them individually.

For more complex deployments, it is recommended to start separate processes and have them listen to different ports individually, and supervisord's Process Groups feature is a good way to do this. When each process uses a different port, an external load balancer, such as HAProxy or nginx, usually requires a single address for outgoing visitors.

3. Running behind the load balancer

When running in a load balancer such as nginx, it is recommended that you pass xheaders=True to the HTTPServer's constructor. This tells Tornado to use HTTP headers like X-Real-IP to get the user's IP address instead of considering all traffic to be from the load balancer's IP address.

This is a raw nginx configuration file that is structurally similar to the configuration we used in FriendFeed. This assumes that nginx and Tornado servers are running on the same machine and that 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;
      }
     }
    }
    

4. Static Files and File Cache

In Tornado, you can specify a special static_in your application Path to provide static file services:

​
    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)
    

These settings automatically hand over all requests starting with/static/to the static directory, for example http://localhost:8888/static/foo.png Foo will be provided through the specified static directory. Png file. We also automatically provide/robots from the static directory. Txt and/favicon.ico (although they do not start with a/static/prefix).

In the settings above, we explicitly configure Tornado to get the apple-touch-icon from the StaticFileHandler root. Png file, although the file is in the static file directory. (The regular expression capture group must tell StaticFileHandler the requested file name and call the capture group to pass the file name to the handler as an argument to the method) You can do the same thing, such as providing sitemap from the root of the site. XML file. Of course, you can also avoid forging the apple-touch-icon at the root by using <link/>tags in your HTML. Png.

To improve performance, it is generally a good idea for browsers to proactively cache static resources so that they do not send unnecessary If-Modified-Since or Etag requests that might be blocked when rendering a page, and Tornado uses static content versioning to support this.

To use these features, use static_in your template URL method instead of typing the URL of a static file directly into your HTML:

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

Static_ The url() function translates the relative path into a URI similar to/static/images/logo.png?v=aae54. The v parameter is logo. Hash of PNG content, and its existence causes the Tornado service to send cache headers to the user's browser, which will make the browser cache content indefinitely.

Because the parameter v is file content based, if you update a file and restart the service, it will send a new v value, so the user's browser will automatically pull out the new file. If the contents of the file remain unchanged, the browser will continue to use the locally cached copy without checking for updates from the server, significantly improving rendering performance.

In production, you may want to provide static files through a better static server, such as nginx, and you can configure any web server to recognize through static_url() provides the version label and sets the cache header accordingly. Here is part of the nginx-related configuration we use in FriendFeed:

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

5. Debug mode and automatic overloading

If you pass debug=True to the constructor configured for the Application, the application will run in debug/development mode. In this mode, for ease of development, some functionality will be enabled (each can also be used as a separate tag, and if they are specifically specified, they will receive a separate priority):

1. autoreload=True: The application will observe if its source file has changed and will overload itself whenever any file has changed. This reduces the need to manually restart services in development. However, in debug mode, some errors, such as syntax errors in import, can cause the service to shut down and cannot be recovered automatically. 2. compiled_template_cache=False: Templates will not be cached. 3. static_hash_cache=False: Static file hashes (used by the static_url function) will not be cached. 4. serve_traceback=True: When an exception is not caught in RequestHandler, an error page containing call stack information is generated. Autoreload mode is incompatible with HTTPServer's multi-process mode. You cannot give HTTPServer.start passes parameters other than 1 (or calls tornado.process.fork_processes) when you use automatic overload mode.

Auto-overloading in debug mode can be located in tornado as a separate module. Autoreload. The following can be used in combination to provide additional robustness in case of syntax errors: setting autoreload=True detects file modifications at app runtime, and starting python-m tornado.autoreload myserver. Py to catch any syntax errors or other startup errors.

Overloading will lose any Python interpreter command line parameters (-u). Because it uses sys.executable and sys.argv re-executes Python. Additionally, modifying these variables will cause overload errors.

On some platforms, including Windows and before Mac OSX 10.6, processes could not be updated "in place", so when code updates are detected, the old service exits and a new service is started. This has been known to confuse some IDE s.

6. WSGI and Google App Engine

Tornado usually runs independently and does not require a WSGI container. However, in some environments (such as Google App Engine), only WSGI is run, and applications cannot run their own services independently. In this case, Tornado supports a limited mode of operation, does not support asynchronous operations, but allows a subset of Tornado's capabilities in a WSGI-only environment. The following features are not supported in WSGI mode, including protocol, @asynchronous decorator, AsyncHTTPClient, auth module, and WebSockets.

You can use tornado.wsgi.WSGIAdapter converts a Tornado Application into a WSGI application. In this example, configure your WSGI container to discover the application object:

​
    import tornado.web
    import tornado.wsgi
    
    class MainHandler(tornado.web.RequestHandler):
     def get(self):
      self.write("Hello, world")
    
    tornado_app = tornado.web.Application([
     (r"/", MainHandler),
    ])
    application = tornado.wsgi.WSGIAdapter(tornado_app)
    

This is the whole content of this article, and I hope it will be helpful for everyone to learn.

 

Keywords: Python

Added by pdmiller on Sat, 29 Jan 2022 15:14:46 +0200