文章目录
1 Intro
1.1 一些概念
1.1.1 基本特性/优势
web框架和异步网络库,由于使用了异步网络I/O,Tornado可以维持数以万计的开放连接,这适合长轮循,WebSocket和其他需要对客户保持长期活跃连接的应用。
1.1.2 Tornado三个组件
• web 框架(RequestHandler)
• HTTP客户端和服务端的实现(HTTPServer和AsyncHTTPClient)
• 异步网络库,包括IOLoop和IOStream,可以作为实现HTTP组件的构建模块,也可以用于实现其他协议。
1.1.3 Tornado和WSGI
WSGI是一个同步接口,而Tornado是一个基于单线程的异步执行的并发模型,许多Tornado突出的特性在WSGI模型中不可用(长轮循和websockets)。不过,HTTP Server和web 框架一起实现了一种全栈的WSGI,可以使用Tornado的作为容器(WSGIContainer)的HTTP Server来搭配其他WSGI框架。Tornado提供WSGIContainer在单线程中支持WSGI应用和原生的Tornado RequestHandlers。WSGI应用最好搭配专门的WSGI服务器(gunicorn,uwsgi)使用。
1.2 安装
• tornado为类Unix系统设计,最好在类Unix环境中执行。Windows环境不支持Tornado部分特性
pip install tornado
1.3 hello world
# handler/hello.py
import tornado
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world!")
# routing.py
from handler.hello import MainHandler
routing = [
(r"/", MainHandler),
]
# web.py
import asyncio
import tornado
from routing import routing
def make_app():
return tornado.web.Application(
routing
)
async def main():
app = make_app()
app.listen(8888)
await asyncio.Event().wait()
if __name__ == "__main__":
asyncio.run(main())
1.4 补充
- 实时web特性要求对每个用户维持一个几乎空闲的长连接,在传统的同步web server中,为实现这个特性将为每个用户单独开一个线程,开销很大。Tornado使用单线程事件循环来最小化并发连接的开销。
- 函数阻塞的原因:网络I/O,硬盘I/O,互斥量。
- 异步的接口:回调参数,返回占位符(Future,Promise,Deferred),投递到队列,回调注册。Tornado中的异步操作主要使用两种接口:一种是占位符(Futures),一种是IOLoop使用的回调。
2 web 框架
2.1 主协程
主协程main由asyncio.run()启动,且主协程mian运行结束则整个web应用退出执行。注意主协程中用asyncio.Event()创建了一个事件shutdown_event,并在在shutdown_event上一直阻塞,直到事件循环中某个协程主动调用asyncio.Event.set(shutdown_event),那么主协程main退出,整个事件循环终止。
import asyncio
import tornado
from routing import routing
def make_app():
return tornado.web.Application(
routing
)
async def fun(shutdown_event):
await asyncio.sleep(3)
asyncio.Event.set(shutdown_event)
async def main():
app = make_app()
app.listen(8888)
shutdown_event = asyncio.Event()
asyncio.create_task(fun(shutdown_event))
await shutdown_event.wait()
if __name__ == "__main__":
asyncio.run(main())
2.2 Application类
tornado.web.Application对象负责全局配置,如路由表(映射请求和handler)。路由表是一个列表,列表每一项包含URLSpec对象和一个RequestHandler对象。
初始化URLSpec对象,第一个参数是路径正则表达式(正则表达式中的捕获的分组将作为路径参数传递给RequestHadnler中定义的HTTP方法),第二个参数是RequestHandler,第三个参数是用于给RequestHandler对象初始化的字典,第四个参数是名称。
from tornado.web import url
app = Application([
url(r"/", MainHandler),
url(r"/story/([0-9]+)", StoryHandler, dict(db=db), name="story")
])
2.3 RequestHandler类
RequestHandler类的使用方式是继承它并重写以HTTP方法命名的成员函数,如get(),post()。在handler中,调用RequestHandler.render或者 RequestHandler.write来产生响应,注意在请求处理的全流程中,write方法可以多次调用来产生响应内容,并非是调用一次就终止全部处理流程。render()通过名称加载一个模板并且用给定参数渲染它。write()用于非模板输出(字符串,字节,JSON格式的字典)。
比较通用的方法是继承RequestHandler定义一个BaseHandler来重写诸如write_error,get_current_user等共用方法,然后再继承你自己的BaseHandler来构建应用。
2.3.1 请求处理的调用过程
initialize() -> prepare() -> HTTP method -> on_finish,在prepare中调用finish或者redirect将终止进一步处理。
2.3.2 一些成员(待补充)
RequestHandler.request来表示当前请求,RequestHandler.current_user表示当前用户。
2.3.3 一些方法(待补充)
- write_error:为错误页面产出HTML
- on_connection_close:当客户端失去连接时调用,应用可能探测连接情况并在失去连接后终止进一步处理,同时一些释放资源的操作也可以放在这里来做。不保证客户端一失去连接就检测出来,然后调用之。
- get_current_user:查看用户权限
- get_user_locale:返回当前用户的Locale对象(用于为用户管理当前语言和地区设置)
- set_default_headers:可用于设置额外的响应头
2.3.4 例子:重写prepare
tornado本身不解析JSON请求体,如果Content-Type为application/json,则可以在self.prepare方法中预先使用json内置库处理self.request.body
def prepare(self):
if self.request.headers.get("Content-Type", "").startswith("application/json"):
self.json_args = json.loads(self.request.body)
else:
self.json_args = None
2.4 获取请求参数
2.4.1 参数
http://localhost:8080/info/:uid?subject=Math&examType=其中
uid是路径参数(path argument),http://localhost:8080/info/:uid 定位了资源位置。整个url中,? 后面的部分是查询参数(query argument),查询参数subject的值为"Math",查询参数examType的值为"期中",查询参数之间用&分隔。
2.4.2 获取参数列表
使用get_query_arguments(name)获取查询参数列表,使用get_body_arguments()获取放在请求体中的参数列表。比如,http://localhost:8080/info/:uid?subject=Math&subject=English,调用get_query_arguments(“subject”),返回[‘Math’, ‘English’]。
2.4.3 获取某个参数
使用self.get_query_argument(name)获取名为name的查询参数,使用self.get_body_argument(name)获取名为name的请求体参数。下面的handler例子中,浏览器访问localhost:8888/my/form,get()方法先被调用,将html写入响应。响应页面中呈现一个简单的输入框和submit按钮,在输入框输入文本后点击submit按钮,则浏览器发起一个post请求,然后post()方法被调用。通过self.get_body_argument(“message”)来获取post请求的参数。"message"是form标签中的属性名。
routing = [
(r"/", MainHandler),
(r"/my/form", MyFormHandler),
]
class MyFormHandler(tornado.web.RequestHandler):
def get(self):
self.write('<html><body><form action="/my/form" method="POST">'
'<input type="text" name="message">'
'<input type="submit" value="Submit">'
'</form></body></html>')
def post(self):
print('xxx')
self.set_header("Content-Type", "text/plain")
self.write("You wrote " + self.get_body_argument("message"))
2.5 文件上传
2.5.1 方式一:通过表单参数上传文件
self.request.files为字典,键为请求参数名,值为文件列表,文件列表的每项是一个字典,结构为{“filename”:…, “content_type”:…, “body”:…}。如上图,self.request.files={‘file’: [{‘filename’: ‘file0’, ‘content-type’: ‘xxx’, ‘body’: ‘xxx’}, {‘filename’: ‘file1’, ‘content-type’: ‘xxx’, ‘body’: ‘xxx’}]}。注意,只有请求头的Content-Type设置为multipart/form时,才可用。
2.5.2 方式二:通过请求体上传文件
如果文件通过self.request.body来保存上载的数据,则上载的文件将默认全部缓存在内存中。如果需要处理大文件,可以考虑使用stream_request_body类装饰器。
import asyncio
import logging
from urllib.parse import unquote
import tornado
from tornado import options
@tornado.web.stream_request_body
class PUTHandler(tornado.web.RequestHandler):
def initialize(self):
self.bytes_read = 0
def data_received(self, chunk):
self.bytes_read += len(chunk)
def put(self, filename):
filename = unquote(filename)
mtype = self.request.headers.get("Content-Type")
logging.info('PUT "%s" "%s" %d bytes', filename, mtype, self.bytes_read)
self.write("OK")
2.6 错误处理
2.6.1 方式一:直接raise tornado.web.HTTPError
class MainHandler(tornado.web.RequestHandler):
def get(self):
raise tornado.web.HTTPError(404, "Not Found!")
2.6.2 方式二:重写write_error来自定义error page
class MainHandler(tornado.web.RequestHandler):
def get(self):
raise tornado.web.HTTPError(404, "Not Found!")
def write_error(self, status_code, **kwargs):
if status_code == 404:
self.write("<h1>Oops! Page not found.</h1>")
elif status_code == 500:
self.write("<h1>Internal Server Error. Please try again later.</h1>")
else:
self.write("<h1>An error occurred.</h1>")
2.6.3 方式三:设置status,写并返回响应
class MainHandler(tornado.web.RequestHandler):
def get(self):
# Simulate an error condition
self.set_status(404) # Set HTTP status to 404 (Not Found)
self.write("<h1>Oops! Page not found.</h1>") # Custom error message
2.6.4 tornado.web.Finish()
通常使用在请求生命周期中尽早返回响应的场景,比如鉴权,重定向等。
class MainHandler(tornado.web.RequestHandler):
def get(self):
# Check for some condition
user_authenticated =

最低0.47元/天 解锁文章
1762

被折叠的 条评论
为什么被折叠?



