Tornado
环境:
- Tornado: v5.1
- Python: v3.6
参考:
用户指南
介绍
Tornado是一个Python Web框架和异步(asynchronous)网络库。通过使用非阻塞(non-blocking)网络I/O,Tornado可以扩展到上万个连接,因此非常适合长轮询(long polling)、WebSocket需要长期连接到的每个用户的应用程序。
Tornado大致可以分为四个主要组件:
- Web框架:包含
RequestHandler
,它的子类用于创建web应用,并支持各种类。 - HTTP的Client和Server的实现:
HTTPServer
和AsyncHTTPClient
。 - 异步网络库(
IOLoop
和IOStream
),用于HTTP组件的构建块,并且还可实现其它协议。 - 协程库(
tornado.gen
),允许异步代码写的更直接而不用链式回调(chaining callbacks)的方式。
Tornado web框架和HTTP server一起为WSGI提供了一个全栈(full-stack)式选择。为了充分利用Tornado的特性,你需要一起使用Tornado Web框架和HTTP Server。
异步和非阻塞I/O
实时(real-time) Web功能需要为每个用户提供一个长时间空闲(mostly-idle)的长连接。在传统的同步(synchronous) web server,这意味着为每个用户提供一个线程(thread),这是非常昂贵的。 要尽可能减少并发连接(concurrent connections)的开销,Tornado使用一个单线程事件循环。这意味着所有应用程序代码都应该是异步非阻塞的,因为在同一时间只有一个操作是活跃的。 异步和非阻塞这两个术语是非常相关的,并经常交换使用,但它们不是完全相同的事情。
阻塞
一个函数在等待某些事情的返回的时候会被阻塞(block)。函数阻塞的原因有很多,如:网络IO、磁盘IO、互斥锁…事实上,每个函数在运行和使用CPU的时候或多或少会被阻塞。
一个函数可以在某些方面阻塞,在另外一些方面不阻塞。在Tornado下,我们通常讨论网络IO阻塞,尽管各种阻塞也被最小化。
异步
异步(asynchronous)函数在完成之前返回,在应用中触发下一个动作之前通常会在后台执行一些工作(和正常的同步函数在返回之前就执行完所有的事情不同)。这里列举了几种风格的异步接口:
- 回调参数
- 返回一个占位符
- 传送给一个队列
- 回调注册表
栗子
一个同步(synchronous)函数的栗子:
|
|
一个异步(asynchronous)重写的函数:
|
|
协程(coroutines)有点不可思议,但它们在内如是这样的:
|
|
任何可用协程做的都可传递到回调(callback)对象周围,但协程提供了一个重要的简化让你以相同的方式组织你的代码。这对于错误处理(error handling)尤其重要,在协程预期的tyr/except
块工作,这是难以实现的回调。
在Tornado中,协程(Coroutines)是推荐的编写异步代码的方式。协程使用Python的await
或yield
关键字来暂停(suspend)和恢复(resume)来代替回调链。
协程几乎与同步(synchronous)代码一样简单,但不带线程(thread)的开销。它们使得并发(concurrency)更简单。
栗子:
|
|
原生与装饰的协程
Native vs decorated coroutines
Python 3.5介绍了async
和await
关键字。
只要可能,原生协程是推荐的形式。仅需要与旧版本的Python兼容时使用装饰的协程。Tornado文档中一般会使用原生形式。
这两种形式之间的转换一般是简单的:
|
|
两种协程形式的不同:
- 原生协程通常更快
- 原生协程可以使用
async for
和async with
语句,这使得一些模式更简单 - 除非
await
和yield
它们,原生协程不会运行所有。装饰的协程一经调用就运行在后台(background)。请注意,这两种协程使用await
或yield
都很重要,以便任何异常都有地方可去 - 装饰的协程有与
concurrent.futures
包额外的集成,允许直接yieldedexecutor.submit
的结果。对于原生协程,使用IOLoop.run_in_executor
代替 - 通过生成一个列表或字典,装饰的协程支持一些速记。在原生协程中使用
tornado.gen.multi
- 装饰的协程可以支持与其它软件包的整合。要在原生协程中访问此功能,使用
tornado.gen.convert_yielded
- 装饰的协程总是返回一个
Future
对象。原生协程返回一个awaitable对象
如何工作
本节介绍装饰的协程的操作。原生协程在概念上相似,但多了几分复杂。因为与Python runtime额外集成。
包含yield
的函数是一个生成器(generator)。所有的生成器都是异步的,调用它们时返回一个生成器对象,而不是运行到完成。@gen.coroutine
装饰器(decorator)通过yield
表达式与生成器进行通信,通过协程调用返回一个Future
。
一个协程装饰器的内循环的简单栗子:
|
|
装饰器从生成器接收一个Future
,等待(不会阻塞)选择那些完成的Future
,解包Future
并将结果发送回生成器的yield
表达式。大多数异步代码不直接接触Future
类,除了由一个异步函数立即传递Future
到yield
表达式。
如何调用协程
协程在正常方式下不抛出异常:它们抛出的任何异常都将在awaitable
对象直到它被yielded。这意味着以正确的方式调用协程是重要的,或者可能有被你忽略的错误。
|
|
在几乎所有情况下,调用协程的任何函数都必须是一个协程本身,并在调用中使用await
和yield
关键字。当你重写superclass中定义的方法时,查看文档看协程是否被允许。
|
|
有时,你可能想fire and forget协程,而无需等待其结果。在这种情况下,推荐使用IOLoop.spawn_callback
,这使得IOLoop
负责调用。如果失败,IOLoop
将记录stack trace。
|
|
函数使用@gen.coroutin
在这种方式下建议使用IOLoop.spawn_callback
,但它需要函数使用async def
。
最后,在程序的顶层,如果IOLoop
尚未运行,就可以启动IOLoop
,运行协程,然后用IOLoop.run_sync
方法停止IOLoop
。这经常用来启动一个面向批处理程序的main()
函数。
|
|
协程模式
Coroutine patterns
调用阻塞函数
Calling blocking functions
从协程调用阻塞函数最简单的方式是使用IOLoop.run_in_executor
,它返回与协程兼容的Future
:
|
|
Parallelism
multi
函数接收列表和字典,其值是Futures
,并等待所有并行(parallel)的Futures
:
|
|
在装饰的协程,可yield
列表或字典:
|
|
Interleaving
有时保存Future
是有用的而不立即yielding,因此你可以在等待之前启动其它操作。
|
|
这是一个比较容易做装饰的协程,因为它们在调用时立即启动:
|
|
Looping
在原生协程,可使用async for
。在不同版本的Python中,looping is tricky with coroutines,因为没有办法获得对for
或while
循环的每次迭代结果的yield。你需要从访问结果分隔循环条件。
|
|
在后台运行
Running in the background
PeriodicCallback
通常不与协程使用。相反,协程可以包含While True:
循环并使用tornado.gen.sleep
:
|
|
有时,一个更复杂的循环可能是可取的。例如,前一个循环每60+N
秒运行,N是do_something
的运行时间。要准确每60秒运行,使用上面的interleaving模式:
|
|
Queue
Queue example - a concurrent web spider
Tornado的tornado.queues
模块实现了协程异步 生产者(producer)/消费者(consumer)模式,类似于由Python标准库的queue
模块为线程(thread)实现的模式。
yields Queue.get
协程暂停直到队列中有项。如果队列设置了最大大小集(yield Queue.put
)协程暂停,直到有另一个项。
Queue
维护未完成的任务计数,从0开始。put
递增计数,task_done
递减它。
web-spider栗子,队列开始仅包含base_url。当worker获取它解析的链接和队列放出新的页面,然后调用task_done
递减计数。最终,worker取出其url没有过的页面,也没有留在队列中工作。因此,worker调用task_done
递减计数器归零。主协程,它等待join
,取消暂停和完成。
|
|
Tornado web程序结构
Structure of a Tornado web application
一个Tornado web程序通常由一个或多个RequestHandler
子类组成,Application
对象是哪些路由进入的请求的处理程序(handler),main()
函数来启动server。
一个最小化的hello world栗子:
|
|
Application对象
Application
对象是负责全局配置,包括映射请求到处理程序(handler)的路由表。
路由表是URLSpec
对象的列表(或元组),其中每一个包含(至少)一个正则表达式和一个处理类(handler class)。顺序匹配,第一匹配规则被使用。如果正则表达式中包含捕获组,这些组的路径参数将被传递给处理程序(handler)的HTTP方法。如果字典作为URLSpec
的第三个参数传递,它提供将初始化参数传递给RequestHandler.initialize
。最后,URLSpec
可以有一个名称,这将允许它与RequestHandler.reverse_url
使用。
栗子:
|
|
Application
构造器采用许多关键字参数,可用于定制应用程序的行为和启用可选功能。查看Application.settings
获取完整列表。
RequestHandler子类
Subclassing RequestHandler
大部分Tornado Web应用程序的工作是RequestHandler
子类完成的。主入口点的处理程序子类(handler subclass)是正在处理的HTTP方法(get()
, post()
)的方法命名。例如,每个handler可以定义这些方法中的一种或多种,以处理不同的HTTP动作。如上所述,这些方法将于对应于匹配的路由规则的捕获组参数来调用。
在处理程序内部,调用如RequestHandler.render
或RequestHandler.write
来产生响应(response)。render()
通过名称加载一个模板,并与给定的参数来渲染它。write()
被用于非基于模板(non-template-based)输出。它接受字符串,字节和字典(字典被编码为json)。
ReqestHandler
中的许多方法都设计在子类中重写(overridden),并在整个application中使用。这是常见的定义BaseHandler
类,覆盖方法如write_error
, get_current_user
,并为你所有指定的handler继承BaseHandler
而不是RequestHandler
。
处理请求输入
Handling request input
request handler可以访问表示与self.quest
获取当前请求的对象。查看HTTPServerRequest
类来获取完整的列表。
通过HTML表单中使用的格式请求的数据将为你解析,并在如get_query_argument
和get_body_argument
方法中可用。
|
|
由于HTML表单的编码是模糊的,以元素中的一个参数是否为单一值(single value)或一个列表,RequestHandler
有独特的方法,以允许application表明它是否期望一个列表。对于列表,使用get_query_arguments
和get_body_arguments
来代替它们的singular counterparts。
通过表单上传的文件在self.request.files
可用,它映射名称(html input type="file"
元素)到一个文件列表。每个文件是{"filename":..., "content_type":..., "body":...}
格式的字典。files
对象仅表示文件是否以一种form wrapper上传(如multipart/form-data
内容类型)。如果不使用这种格式,原始上传数据在self.request.body
可用。默认情况下上传的文件在内存中完全缓冲(fully buffered)。如果你要处理的文件太大,但想在内存中舒适保存,可参考stream_request_body
类装饰器。
由于HTML格式编码的怪癖,Tornado并不试图统一参数和其它输入类型的形式。特别是,我们不解析JSON请求主体。Applicaton希望使用JSON而不是form-encoding可以覆盖prepare
来解析它们的请求:
|
|
重写RequestHandler方法
Overriding RequestHandler methods
除了get()
, post()
…,在RequestHandler
某些其它方法被设计成在必要时由子类重写。在每次请求,调用以下顺序进行:
- 在每个请求上,一个新的
RequestHandler
对象被创建 initialize()
被调用,从Application配置的初始化参数。初始化通常应该只保存传入成员变量的参数,不产生任何输出或调用(如send_error
)prepare()
被调用。这是最有用的,由所有handler subclass共享的基类,作为prepare被无论哪个HTTP方法所调用。prepare
可产生输出,如果它调用finish
或redirect
,这里处理停止- 当其中一个HTTP方法被调用时:
get()
,post()
,put()
。如果URL正则中包含捕获组(capturing group),它们将被作为参数传递给该方法 - 当请求完成后,调用
on_finish()
。对于大多数handler这个在get()
返回后立即调用。在调用finish()
之后使用tornado.web.asynchronous
装饰器来装饰handler
在RequestHandler
文档中,所有的方法都设计来可重写。一些最常用的重写方法:
writre_error
: 输出HTML错误页面on_connection_close
: 当客户端断开连接时调用。应用可选择检测此情况并停止进一步的处理。注意,不能保证一个关闭的连接能够被及时发现get_current_user
get_user_locale
: 返回当前用户的Locale对象set_default_headers
: 用于在响应中设置其它header
错误处理
Error Handling
如果handler抛出一个异常,Tornado将调用RequestHandler.write_error
来生成一个错误页。tornado.web.HTTPError
可用来生成一个特定状态码。所有其它异常返回500状态。
debug模式下的默认错误页面包含一个stack trace和对错误的一行说明。要生成自定义错误页,重写RequestHandler.write_error
。可通过如write
和render
方法来产生输出。如果错误是由异常导致的,一个exc_info
将作为一个关键字参数传递。
也可通过调用set_status
产生与常规处理方法write_error
生成的错误页面,编写一个响应,并返回。特殊异常tornado.web.Finish
可抛出终止处理而不调用write_error
在简单返回不方便时。
对于404错误,使用default_handler_class
应用设置(Application setting)。此处理程序应重写prepare
,而不是像get()
方法更具体的方法,所以它与任何HTTP方法工作。如上所述应该产生错误页面: 要么抛出HTTPError(404)
和重写write_error
,或调用self.set_status(404)
和直接在prepare()
中产生响应。
重定向
Redirection
Tornado中有两种主要的方式重定向请求: RequestHandler.redirect
和RedirectHandler
。
可在RequestHandler
方法中使用self.redirect()
来重定向到别处。这有一个permanent
的可选参数,可用它来表示永久的重定向。permanent
的默认值为False
,其产生一个302 Found
HTTP响应码,适合像POST
请求成功之后使用。如果permanent
为true
, 则使用301 Moved Permanently
HTTP响应码,其用于重定向到一个规范友好的URL。
RedirectHandler
让你直接在Application路由表中配置重定向,栗子:
|
|
RedirectHandler
同样支持正则表达式取代。栗子:
|
|
不像RequestHandler.redirect
,RedirectHandler
默认使用永久重定向。这因为路由表在运行时不发生变化,被认定为时永久性的,而在处理中发现重定向可能改变其它逻辑的结果。要使用RedirectHandler
发送一个临时的重定向,将permanent=False
添加到RedirectHandler
初始化参数。
异步处理程序
Asynchronous handlers
某些处理方法(如prepare()
和HTTP的get()
, post()
…)可能会被重写为协程,使处理程序异步。
Tornado同样支持使用tornado.web.asynchronous
装饰器异步处理的回调风格,但这种风格已经过时,将在Tornado6中一处。新的应用应该使用协程来代替它。
使用协程的一个简单处理程序的栗子:
|
|
更多高级的异步的栗子,查考文档。
模板和UI
Templates and UI
Tornado包含了一个简单、快速、灵活的模板语言。想想Django和Jinja2。
Tornado还可与任何其它Python模板语言使用,虽然没有规定集成这些系统到RequestHandler.render
里。简单地渲染模板为字符串,并将其传递到RequestHandler.write
。
配置模板
Configuring templates
默认情况下,Tornado在引用它的.py
文件中的同一目录下查找模板文件。要把模板文件放在不同的目录中,使用template_path
应用设置。如果你有不同的模板路径用于不同的处理程序,请重写RequestHandler.get_template_path
。
要从非文件系统位置载入模板,子类tornado.template.BaseLoader
将在模板并传递一个实例作为template_loader
应用设置。
默认缓存编译的模板。要关闭这个缓存和重新加载模板,使用compiled_template_cache=False
或debug=True
应用设置。
模板语法
Template syntax
Tornado模板仅仅是HTML(或其它基于文本的格式)与Python控制序列和嵌入在标记内的表达式,想想Django模板和Jinja2。
表达式可以是任意Python表达式,包括函数调用。模板代码在包括以下对象和函数的命名空间执行(请注意,以下列表适用于使用RequestHandler.render
和render_string
渲染模板。如果你直接使用在RequestHandler
外的tornado.template
模块,那么许多内容是不存在的。)
escape
:tornado.escape.xhtml_escape
的别名xhtml_escape
:tornado.escape.xhtml_escape
的别名url_escape
:tornado.escape.url_escape
的别名json_encode
:tornado.escape.json_encode
的别名squeeze
:tornado.escape.squeeze
的别名linkify
:tornado.escape.linkify
的别名datetime
: Python的datetime
模块handler
: 目前的RequestHandler
对象request
:handler.request
的别名current_user
:handler.current_user
的别名locale
:handler.locale
的别名_
:handler.locale.translate
的别名static_url
:handler.static_url
的别名xsrf_form_html
:handler.xsrf_form_html
的别名reverse_url
:Application.reverse_url
的别名- 所有条目从应用的
ui_methods
和ui_modules
- 任何关键字参数传递给
render
或render_string
当你在构建一个真正的应用时,你会想要使用Tornado模板的所用功能,尤其是模板继承。阅读tornado.template
部分了解详细信息。
引擎盖下,Tornado模板直接转换为Python。模板中的表达式是逐字复制到Python函数中。我们不设法防止模板语言的任何东西。最后,如果你写的模板表达式内随机的东西,当你执行模板可能会获得随机的Python错误。
所有的模板输出默认被转义(escape),使用tornado.escape.xhtml_escape
函数。这个行为可通过全局地传递autoescape=None
给应用或tornado.template.Loader
构造器,对于模板文件指示{% autoescape None%}
或通过{% raw ... %}
代替{{ ... }}
。此外,在每一个可选择转义函数名的地方,可用None
代替。
虽然Tornado的自动转义为避免XSS漏洞有帮助,但它并不是在所有情况下都有效。例如在JS或CSS表达式的某些地方,可能需要额外的转义。此外,必须小心地使用HTML双引号"
和xhtml_escape
,可能包含不受信任的内容,或者必须为属性使用单独地转义函数。
UI模块
UI modules
Tornado支持UI模块,可以很容易地在你的应用中支持标准的、可重用的UI组件。UI模块都喜欢特殊的函数调用来渲染网页和组件,它们可以包装自己的CSS和JS。
例如,如果要实现一个博客,你想拥有的博客条目同时出现在博客主页和每个博客页面上,你可以编写一个Entry
模块在两个页面上渲染它们。首先,为你的UI模块创建一个Python模块:
|
|
在应用中设置ui_modules
告诉Tornado使用uimodules.py
:
|
|
在模板内,你可以使用{% module %}
调用模块,例如在home.html
中调用Entry
模块:
|
|
entry.html
中:
|
|
模块可以通过重写embedded_css
, embedded_javascript
, javascript_files
或css_files
方法来包含自定义的CSS和JS函数:
|
|
模块CSS和JS将包含一次,不管一个页面中这个模块使用了多少次。CSS总是包含在页面的<head>
,JS总是包含在</body>
标记之前在页面的页面结束标记。
当不需要附加的Python代码,模板文件本身可以用作一个模块。例如,前面的栗子可以改写在module-entry.html
模块:
|
|
经修订的模板模块将与下栗被调用:
|
|
该set_resources
功能尽在通过
{% module Template(...) %}
调用模板。不同于
{% include %}
,
模板模块具有从它们的包含模板的独特命名空间——它们只能看到全局模板命名空间和自己的关键字参数。
认证和安全
Authentication and security
Cookie和secure cookies
可以使用set_cookie
方法在用户浏览器中设置cookie:
|
|
cookie是不安全的,可以很容易地被客户修改。如果你需要设置cookie,请确定当前登录的用户,你需要签属(signed)你的cookie来防止伪造。Tornado支持使用set_secure_cookie
和get_secure_cookie
方法来签属(sign)cookie。要使用这些方法,你需要在创建应用时指定一个名为cookie_secret
的密钥键。你可以在应用中设置关键字参数来传递给应用。
|
|
签属的cookie含有时间戳和HMAC签名的cookie编码值。如果cookie是旧的,或者签名不匹配,get_secure_cookie
将会返回None
就像没有设置cookie那样。上面栗子的安全版本:
|
|
Tornado的secure cookie保证完整性,但不保密。也就是说,cookie不能被修改,但可以被用户看到。cookie_secret
是一个对称密钥并且必须保密——得到这个值的人都可以制作自己的签名的cookie。
默认情况下,Tornado的cookie在30天后过期。可对set_secure_cookie
使用expires_days
参数和max_age_days
来修改。
Tornado同样支持多个签名密钥来启用签名轮询。cookie_secret
必须与整数密钥版本作为关键字和相应的secret作为字典的值。将当前使用的签名密钥必须在应用中设置为key_version
,但在字典的所有其它键都允许cookie签名认证,如果设置在cookie中的是正确的密钥版本。要更新cookie,可通过查询get_secure_cookie_key_version
获取当前的签名密钥版本。
用户认证
User authentication
当前已认证的用户在每个request handler中可使用self.current_user
,在每个模板中为current_user
。默认情况下,current_user
为None
。
要在应用中执行用户身份认证,需要在request handler中重写get_current_user
以基于cookie的值确定当前用户。下面是一个让用户登录到应用,简单地指定一个昵称,然后将其保存到cookie中:
|
|
你可以要求用户在使用tornado.web.authenticated
Python装饰器处登录。如果请求的方法带有此装饰器,并且用户没有登录,则他们将被重定向到login_url
或其它设置。重写上面的栗子:
|
|
如果你使用authenticate
装饰器装饰一个post()
方法,并且用户没有登录,则Server会返回403响应。@authenticated
装饰器简单来说就是if not self.current_user: self.redirect()
的快捷键,并且可能不适用于非基于浏览器的登录方案。
第三方认证
Third party authentication
tornado.auth
模块实现了许多受欢迎的网站上提供的认证(authentication)和授权(authorization)协议,包括Google, FaceBook, Twitter…
下面是一个使用谷歌认证的示例,存储Google credential到cookie以便后续访问使用:
|
|
更多详细内容,请参考tornado.auth
文档。
跨站请求伪造保护
Cross-site request forgery protection
跨站请求伪造(Cross-site request forgery, XSRF),是Web应用的一个常见的问题。
防止XSRF普遍接受的解决方案是每个用户的cookie使用不可预测的值,此值包含网站上每个表单提交的额外参数。如果表单提交的cookie和值不匹配,则请求可能是伪造的。
Tornado内置了XSRF保护。要在你的站点中包含它,启用应用scrf_cookies
设置:
|
|
如果设置了xsrf_cookies
,Tornado Web Application将为所有用户设置_xsrf
cookie,并拒绝没有包含正确的_xsrf
值的所有POST
, PUT
, DELETE
请求。如果你打开了此设置,你需要一切形式的POST
提交中包含此字段。你可以使用特殊的UIModule vsrf_form_html()
,在所有模板中可用:
|
|
如果你提交AJAX POST
请求,你还需要构造JS来包括每个请求的_xsfr
值。所有包含_xsrf
请求AJAX POST的JQuery函数:
|
|
对于PUT
和DELETE
请求,XSRF token可能会通过HTTP X-XSRFToken
Header进行传递。使用xsrf_form_html
时,XSRF cookie被正常设置,但是在不使用任何形式的纯JS应用中,可能需要手动访问self.xsrf_token
。
如果你需要在每个handler中自定义XSRF行为,你可以重写RequestHandler.check_xsrf_cookie()
。例如,如果你有一个不使用cookie的API,你可能希望通过使check_xsrf_cookie
什么也不做来禁用XSRF保护。然而,如果你支持基于cookie和非基于cookie的认证,只要求当前请求使用cookie认证XSRF保护是重要的。
DNS重新绑定
DNS Rebinding
DNS重新绑定是一种攻击,可以绕过同源策略,并允许外部站点访问内部网络的资源。使用TLS的应用不容易受到这种攻击。没有使用TLS的应用依赖网络层的访问控制,应警惕通过验证的HTTP Header的Host
被DNS重新绑定。This means passing a restrictive hostname pattern to either a HostMatches
router or the first argument of Application.add_handlers
:
|
|
此外,应用的default_host
参数,和DefaultHostMatches
路由器不能在应用中使用,这可能受到DNS重新绑定,因为它有一个通配符主模式类似的效果。
运行和部署
Running and deploying
自从Tornado提供了自己的HTTPServer,运行和部署它便和其它Python Web框架有点不同。不同于配置WSGI,你只需要写一个main()
函数来启动Server:
|
|
请注意,这可能需要增加每个进程可打开的文件数(open files),可能修改ulimit
限制。
进程和端口
Processes and ports
由于Python的GIL(Global Interpreter Lock),有必要运行多个Python进程,以充分利用多CPU机器。通常,最好为每个CPU运行一个进程。
Tornado包含了一个内置的多进程模式,一次可启动多个进程。这需要稍微修改以下启动方式:
|
|
这是启动多个进程,并让它们使用相同的端口最简单的方法,虽然它有一定的局限性。首先,每个子进程都会有自己的IOLoop,因此在fork前没有事物触及IOLoop示例是很重要的。第二,在这个模型中很难做到零停机更新(zero-downtime updates)。最后,由于所有的进程共享同一端口更难以单独监控。
对于更复杂的部署,建议单独启动进程,并监听不同的端口。supervisord
是一个好办法。当每个进程使用了不同的端口,通常需要一个外部的负载均衡器(如HAProxy, Nginx)以单独的访问地址提供给访问者。
运行在负载均衡器后
Running behind a load balancer
当运行在如Nginx这样的负载均衡器之后,建议传递xheaders=True
给HTTPServer构造器。这将告诉Tornado使用X-Real-IP
用户Header,来获取用户IP地址,而不是负载均衡器的IP地址。
一个栗子:
|
|
静态文件和侵略性的文件缓存
Static files and aggressive file caching
你可以通过在应用中指定static_path
来设置Tornaodo提供静态文件:
|
|
此设置会自动设置以/static/
的所有请求到静态目录,如http://localhost:8888/static/foo.png
将从指定的静态目录提供静态文件。同样还有/robots.txt
和/favicon.ico
,即便它们并未以/static/
为前缀。
在上面的设置,我们已明确的配置Tornado从StaticFileHandler
提供apple-touch-icon.png
。
要提高性能,通常是浏览器缓存静态资源,因此浏览器将不会发送不必要的If-Modified-Since
或Etag
请求,这可能会阻止页面的渲染。Tornado支持这一开箱即用的静态内容版本。
要使用此功能,在你的模板中使用static_url
方法,而不是在你的HTML中直接输入静态文件:
|
|
static_url()
函数会将相对路径转换为如/static/images/logo.png?v=aae54
这样的URI。v
参数是logo.png
的哈希内容,它的存在使得Tornado Server发送cache header到用户浏览器,这将使浏览器无限期缓存内容。
由于v
参数使基于文件的内容,如果你更新文件并重启Server,它将发送一个新的v
值,因此用户浏览器会自动获取新的文件。如果文件的内容没有改变,浏览器将继续使用本地缓存的副本而没有检查Server上的更新,显著提供渲染性能。
在生产环境,你可能希望从像Nginx这样更优化的静态文件服务器提供静态文件。你可以配置几乎所有的Web Server识别由static_url
使用的标签,并设置相应的cache header。
栗子:
|
|
Debug模式和自动重载
Debug mode and automatic reloading
如果将debug=True
传递给Application
构造器,应用将运行在debug/development模式下。在此模式下,一些便于开发调试的功能将被启用:
autoreload=True
:应用会监视更改的源文件并在发生变化时自动重载。这样减少了在开发过程中手动重启服务。然后,某些错误可能导致无法启动。compiled_template_cache=False
:模板不会被缓存。static_hash_cache=False
:静态文件哈希值(由static_url
函数使用)将不会被缓存。serve_traceback=True
:当RequestHandler中的异常没有被捕获,将会生成一个包含stack trace的错误页面。
自动重载模式不兼容HTTPserver
的多进程模式。如果你正在使用自动重载模式,你不要给HTTPServer.start
一个或多于一个参数(或调用tornado.process.fork_processes
)。
调式模式的自动重载功能是可用作为tornado.autoreload
独立(standalone)模块。这两个可以组合使用,以提供对语法错误的额外稳健:在应用中设置autoreload=True
来在运行时检测改变,并使用python -m tornado.autoreload myserver.py
启动来在启动时捕获任意语法错误或其它错误。
重载将失去任何Python解释器命令行参数(如-u
),因为它使用sys.executable
和sys.argv
来重新执行Python。此外,修改这些变量将导致重载行为不正确。
WSGI
Tornado通常是为了独立运行,而不用WSGI容器。然而,在一些环境中(如Google App Engine),只允许WSGI,应用程序无法运行自己的Server。在这种情况下,Tornado支持操作的限制模式,不支持异步操作,但允许在只有WSGI环境的Tornado功能的子集。未在WSIG模式允许的功能包括协程、@asynchronous
装饰器,AsyncHTTPclient
、auth
模块和WebSockets。
你可以使用tornado.wsgi.WSGIAdapter
将一个Tornado Application转换为WSGI application。
栗子:
|
|