Tornado是一款非阻塞的web服务器框架。
官方文档:https://www.tornadoweb.org/en/stable/guide.html
安装:pip install tornado
一、快速搭建web应用
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import tornado.web
import tornado.ioloop
import tornado.options
# 编写视图类
class HealthCheck(tornado.web.RequestHandler):
def get(self):
self.write({"code": 0, "err": "ok", "data": {}})
# 路由映射
app = tornado.web.Application([
(r"/health_check", HealthCheck), # 访问路径对应的视图类
], debug=True)
# 启动服务
if __name__ == '__main__':
# 单进程
# app.listen(9527)
# current返回当前线程循环实例,然后启动循环,同时开启监听
# tornado.ioloop.IOLoop.current().start()
# 多进程 【linux上部署】
import tornado.httpserver
http_server = tornado.httpserver.HTTPServer(app)
http_server.bind(9527)
http_server.start(4)
tornado.ioloop.IOLoop.current().start()
二、钩子函数
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import tornado.web
import tornado.ioloop
class HealthCheck(tornado.web.RequestHandler):
def set_default_headers(self):
print("执行【set_default_headers】钩子函数:设置默认的响应头")
def initialize(self, python, life) -> None:
self.python = python
self.life = life
print("执行【initalize】钩子函数:获取路由中的字典,作为参数,供视图http方法使用")
def prepare(self):
print("执行【prepare】钩子函数:在处理视图之前,可以处理些问题")
def get(self):
try:
# write并不会马上返回给请求方,而是放在缓存里,如果后面都执行完了,才会返回
self.write({"code": 0, "err": "ok", "data": {}})
print(f"执行视图函数……{self.python}|{self.life}")
import time
time.sleep(2)
raise TypeError("值异常")
except Exception as ex:
# 如果视图异常了,可以通过send_error来主动抛出异常,然后通过write_error进行处理异常
self.send_error(status_code=501, error_msg=ex.args[0])
def on_finish(self) -> None:
"""请求处理完后干的事情,不是异步的,前一个请求虽然很快接到响应,
但后一个请求进来需要等日志写完后才能处理新的请求"""
import time
time.sleep(5)
print("执行【on_finish】钩子函数:视图处理完后做的事情,比如记录日志")
def write_error(self, status_code: int, **kwargs) -> None:
"""视图中出现异常时执行"""
print("执行异常处理【write_error】钩子函数")
self.write(dict(code=status_code, desc=kwargs["error_msg"]))
# 路由映射
app = tornado.web.Application([
(r"/", HealthCheck, {"python": "3.7.8", "life": dict(sale_price=20000)}), # 访问路径对应的视图类
], debug=True)
if __name__ == '__main__':
"""python """
# 单进程
try:
app.listen(9527)
# current返回当前线程循环实例,然后启动循环,同时开启监听
tornado.ioloop.IOLoop.current().start()
except OSError as e:
print(e)
tornado.ioloop.IOLoop.current().stop()
# 如果视图函数不报异常,钩子函数执行顺序
执行【set_default_headers】钩子函数:设置默认的响应头
执行【initalize】钩子函数:获取路由中的字典,作为参数,供视图http方法使用
执行【prepare】钩子函数:在处理视图之前,可以处理些问题
执行视图函数……3.7.8|{'sale_price': 20000}
执行【on_finish】钩子函数:视图处理完后做的事情,比如记录日志
# 如果视图出现异常,执行顺序为
执行【set_default_headers】钩子函数:设置默认的响应头
执行【initalize】钩子函数:获取路由中的字典,作为参数,供视图http方法使用
执行【prepare】钩子函数:在处理视图之前,可以处理些问题
执行视图函数……3.7.8|{'sale_price': 20000}
执行【set_default_headers】钩子函数:设置默认的响应头
执行异常处理【write_error】钩子函数
执行【on_finish】钩子函数:视图处理完后做的事情,比如记录日志
三、tornado.options 全局参数定义应用
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# 提供了全局参数的定义,储存和转换,如加载命令行参数,自定义config文件
import tornado.options
if __name__ == '__main__':
# 方式1. 接收命令行参数
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--port", type=int, default=8600)
args = parser.parse_args()
print(args.port)
# 方式2. tornado自带的处理参数
# 定义1
tornado.options.define(
# 自定义的命令行参数名,唯一性,会作为options的属性
name="port",
# 变量默认值,默认None
default=8600,
# 变量值的类型,支持str,int,float,datetime,bool等,默认转化为对应的类型
type=int,
# 变量说明信息
help="set service port",
# 变量值举例
metavar="8000|9000|9100"
)
# 定义2,
tornado.options.define(name="hobby", default=[], type=str, multiple=True,
help="开启multiple为True接收多个值,以列表接收",
metavar="playBall|watchTv")
# 定义3,
# tornado.options.define_logging_options()
# 获取【命令行方式传入】 python Main.py --port=8000 --hobby=money,watchTv
tornado.options.parse_command_line()
# 查看使用方法
tornado.options.options.print_help()
# 使用
print(tornado.options.options.port)
print(tornado.options.options.hobby)
四、request对象及路由参数解析
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# author:SunXiuWen
# datetime:2021/6/17 0017 15:59
import json
import tornado.web
import tornado.ioloop
"""
self.request.body 所有请求方法的原生数据,二进制
self.request.body_arguments 所有请求方法中的body部分带的数据
self.request.query_arguments 所有请求方法url后拼接的数据
"""
class HealthCheck(tornado.web.RequestHandler):
def get(self):
# 获取get请求url后拼接的参数:http://localhost:8600?version= 1.0 &version=20000
# 获取单个参数,如果变量名相同,后面的覆盖前面的
print(self.get_query_argument(
# 请求参数变量名
"version",
default="定义获取不到时默认值,默认为None",
# 是否对值进行左右去空,默认为True
strip=True))
# 获取相同参数同时存在的多个值,如xxx?version=1&version=2
print(self.get_query_arguments("version"))
self.write({"code": 0, "err": "ok-get", "data": {}})
def post(self):
# TODO:非json请求的报文
# print(self.get_body_argument("version"))
# print(self.get_body_arguments("version"))
# # 或者,下面两个方法同时支持get和post方法
# print(self.get_argument("version"))
# print(self.get_arguments("version"))
# TODO: json请求报文
print(self.request.body) # 获取原生post请求来的参数
print(self.request.query_arguments) # 获取的都是原生的url后拼接的参数
print(self.request.body_arguments) # 获取非json传过来的原生body参数
self.write({"code": 0, "err": "ok-post", "data": {}})
class Index(tornado.web.RequestHandler):
def initialize(self, types) -> None:
"""路由中的字典会传到initalize方法当成参数"""
self.types = types
print(self.types)
def get(self):
bytes_to_str = self.request.body.decode(encoding="utf-8")
self.write(bytes_to_str)
class Order(tornado.web.RequestHandler):
def get(self, num=12, args_name=333, p2=4444):
"""url后面拼接的正则以参数的形式传给视图,同django与flask一样"""
self.write(f"your want to get {num} page|the {args_name}|{p2}")
def post(self, page=1):
"""查看request对象的属性"""
print(self.request.method) # 获取请求方法名 POST
print(self.request.host) # 获取被请求主机名即服务器名 127.0.0.1:32003
print(self.request.uri) # 获取完整资源路径,资源状态标志位 /order/222222
print(self.request.path) # 请求地址
print(self.request.full_url()) # 请求完整地址 http://localhost:9527/order/222222?xxx=33
print(self.request.query) # 请求参数,包含get的参数
print(self.request.version) # HTTP/1.1
print(self.request.headers) # 获取http请求头信息
print(self.request.body) # 获取请求原始二进制报文
print(self.request.remote_ip) # 客服端地址 127.0.0.1
print(self.request.files) # 获取上传的文件
self.write(f"ok:{page}")
app = tornado.web.Application([
(r"/health_check", HealthCheck),
(r"/index", Index, {"types": 1}),
(r"/order/([0-9]+)", Order),
(r"/order1/(?P<args_name>\w+)/(?P<p2>\w+)", Order)
], debug=True)
if __name__ == '__main__':
app.listen(9527)
tornado.ioloop.IOLoop.current().start()
五、response响应
from tornado import ioloop
from tornado import web
# 项目配置
settings = {
"debug": True, # 开启debug模式
}
# 视图类必须要直接或者间接继承于 web.RequestHandler
from typing import Union
from tornado.util import unicode_type
from tornado import escape
from tornado.escape import utf8
class HttpRequest(web.RequestHandler):
def write(self, chunk: Union[str, bytes, dict, list]) -> None:
if self._finished:
raise RuntimeError("Cannot write() after finish()")
if not isinstance(chunk, (bytes, unicode_type, dict, list)):
message = "write() only accepts bytes, unicode, and dict objects"
raise TypeError(message)
if isinstance(chunk, (dict, list)):
chunk = escape.json_encode(chunk)
self.set_header("Content-Type", "application/json; charset=UTF-8")
chunk = utf8(chunk)
self._write_buffer.append(chunk)
class Home(HttpRequest):
def get(self):
# write 会自动识别
# self.write("<h1>hello world</h1>") # 响应html文档
# self.write({"msg":"ok"}) # 响应json数据
# 注意,json的数据格式也可以是列表,tornado中默认不支持返回list,所以如果返回list,则需要重写write
self.write([1, 2, 3])
import time
time.sleep(3)
self.write("客户端会先接受到上面的【1,2,3】等3秒后又会接到这个数据")
# 重新设置响应头的内容 set_header[修改]
self.set_header("Content-Type", "text/json; charset=gbk")
# 自定义响应头 add_header[添加]
self.add_header("test", "xxxx")
self.add_header("python", "yyyy")
# self.clear_header("Server") # 从响应头中删除指定名称的响应头信息
def post(self):
# self.set_status(404,"No User") # 第二个参数表示响应描述,如果不设置,则显示原来对应的
# self.send_error(500,reason="服务器炸了!")
self.send_error(404, msg="服务器炸了!", info="快报警") # 要使用send_error必须先声明send_error方法
def write_error(self, status_code, **kwargs):
self.write("<h1>完蛋啦...</h1>")
self.write("<p>错误信息:%s</p>" % kwargs["msg"])
self.write("<p>错误描述:%s</p>" % kwargs["info"])
def put(self):
# 页面响应
self.redirect("http://www.oldboyedu.com")
# 路由列表
urls = [
(r"/", Home),
]
if __name__ == "__main__":
# Application是tornado web框架的核心应用类,是与服务器对应的接口,里面保存了路由映射表
app = web.Application(
urls,
**settings,
)
# 设置监听的端口和地址
# 启动http多进程服务
app.listen(port=8000, address="0.0.0.0")
# ioloop,全局的tornado事件循环,是服务器的引擎核心,start表示创建IO事件循环,等待客户端连接
ioloop.IOLoop.current().start()
# tornado对响应数据的处理,有三种模式
self.write(xxx) 将chunk数据块写入缓存,默认允许有多个write(),等视图所有的逻辑处理完了,最后都会将缓存的里数据,发送给前端
self.flush() 表示立即将write写入缓存的数据发给前端,不等待视图是否执行完毕
self.finish() 表示视图发给前端的数据到此为止,后面如果还有write将不支持
# 同时也支持页面渲染
self.rnder("index.html") 返回渲染完的html
self.redirect('/test') 301临时重定向
self.redirect('/test',permanent=True) 302永久重定向
六、应用分发及路由分发
flask、django为了实现应用解耦,都有实现应用与路由分发,tornado默认是没有的,需要自己编写,下面提供一个自己用的
1. 目录结构
├─app_admin
│ ├─controllers
│ │ └─__init__.py
│ │ └─account.py
│ └─__init__.py
│ └─__urls__.py
├─app_check
│ ├─controllers
│ │ └─__init__.py
│ │ └─bill.py
│ └─__init__.py
│ └─__urls__.py
├─settings.py
├─Main.py
2. 以app_admin应用为例
# 在app_admin/controllers/account.py中编写视图
import tornado.web
class Login(tornado.web.RequestHandler):
def get(self):
self.write("welcome to learn tornado!")
# 在app_admin/urls.py文件中配置本应用路由
from app_admin.controllers import account
patterns = [
(r"/login", account.Login),
]
# 在app_admin/__init__.py中导入urls.py文件的路由配置列表,便于之后总入口的归集
from .urls import patterns
3. settings.py文件中配置各自的二级域名及应用路由地址
# 分发app的二级域名配置
routes = (
{
"host_pattern": "www.tornado.com", # 二级域名,阿里云平台可以自己配,本地需要改hosts文件做对应的dns映射
"route_path": "app_admin.urls",
"route_name": "patterns"
},
{
"host_pattern": "admin.tornado.com",
"route_path": "app_check.urls",
"route_name": "patterns"
}
)
4. 入口Main.py文件中进行加载就可以实现应用分发,路由分发,且支持二级域名
import tornado.web
import tornado.ioloop
class HealthCheck(tornado.web.RequestHandler):
def set_default_headers(self):
"""设置默认响应头"""
self.set_header("Content-type", "application/json; charset=utf-8")
def get(self):
self.write({"code": 0, "err": "ok", "data": {}})
def on_finish(self) -> None:
"""请求处理完后干的事情,不是异步的,前一个请求虽然很快接到响应,
但后一个请求进来需要等日志写完后才能处理新的请求"""
# print(self.get_query_argument("a"))
# print(self.get_arguments("b"))
import time
time.sleep(5)
print("记录日志")
# 路由分发加载方法:二级域名
def load_route(application_obj):
import settings
for route in settings.routes:
print(route)
host_pattern = route["host_pattern"]
route_path = route["route_path"]
route_name = route["route_name"]
m = __import__(route_path)
pattern_list = getattr(m, route_name)
# 动态将路由添加到路由配置字典中
application_obj.add_handlers(host_pattern, pattern_list)
app = tornado.web.Application([
(r"/health_check", HealthCheck),
])
load_route(app)
if __name__ == '__main__':
# 1. 接收命令行参数
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--port", type=int, default=8600)
args = parser.parse_args()
# 单进程
app.listen(args.port)
# current返回当前线程循环实例,然后启动循环,同时开启监听
tornado.ioloop.IOLoop.current().start()
七、cookie的应用
import tornado.web
import tornado.ioloop
class AuthCheck(tornado.web.RequestHandler):
def get(self):
# 获取cookie
if not self.get_cookie("api-token"):
# 配置cookie
self.set_cookie("api-token", "xxxxxxxx")
self.write("login.html")
else:
self.write("index.html")
class AuthCheckPlus(tornado.web.RequestHandler):
def get(self):
# 一般来说cookie是明文存放在浏览器不安全,很容易被伪造,因此,可以通过加密签名的方式防止,tornado有提供方法
# 需要在路由中配置一个密钥cookie_secret="xxxx"
if not self.get_secure_cookie("api-token"):
# 配置加密的cookie
self.set_secure_cookie("api-token", "xxxx")
self.write("login.html")
else:
self.write("index.html")
app = tornado.web.Application([
("/login", AuthCheck),
("/login/plus", AuthCheckPlus),
], cookie_secret="11111111111")
if __name__ == '__main__':
app.listen(9527)
tornado.ioloop.IOLoop.current().start()
八、模板语法
快速构建
main.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# datetime:2021/8/4 0004
import os
import datetime
import tornado.web
import tornado.ioloop
import tornado.options
settings = {
"debug": True,
# 静态文件url地址前缀【注意尾部加/】
"static_url_prefix": "/static/",
# 静态文件保存路径
"static_path": os.path.join(os.path.dirname(__file__), 'static'),
# html文件路径
"template_path": os.path.join(os.path.dirname(__file__), 'templates'),
}
tornado.options.define("port", default=9527, type=int, help="define a port for this web")
class Index(tornado.web.RequestHandler):
@staticmethod
def salary_format(data):
return "%.2f" % data
def get(self):
person_info = dict(name="boss", salary=20000, company=["beijing", "tencent"])
self.render('index.html', datetime=datetime, info=dict(name="boss"), salary_format=self.salary_format,
**person_info)
urls = [
tornado.web.url(f"/", Index, {}, name="index"),
]
if __name__ == '__main__':
tornado.options.parse_command_line()
app = tornado.web.Application(urls, **settings)
app.listen(tornado.options.options.port)
tornado.ioloop.IOLoop.current().start()
templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>当前时间:{{datetime.datetime.now()}}</p>
<p>name={{name}}</p>
<p>salary={{salary}}</p>
<p>{{info['name']}}</p>
<p>{{"-".join(company)}}</p>
<p>{{salary_format(salary)}}</p>
</body>
</html>
2. 模板语法
# 变量: 支持切片及python相应的基本方法,也可以传的是函数
{{ 变量 }}
{{ 变量[index] }}
# 内置变量
request
# 控制语句
# 判断
{% if ... %}
{% elif ... %}
{% else ... %}
{% end %}
# 遍历
{% for ... in ... %}
{% end %}
# 循环
{% while ... %}
{% end %}
# 内置标签
# 导包
{% from ... import ... %}
{% import ... %}
# 加载其他模板
{% include ... %}
# 输出原始数据
{% raw ... %}
# 语句/局部变量
{% set 变量名=变量值 %}
# 异常处理
{% try %}...{% except %}...{% else %}...{% finally %}...{% end %}
# 模板继承
{% extends *filename* %}
{% block 模板块名称} {% end %}
# 内置函数
# 输出转义数据,tornado在配置中允许通过autoescape=None设置全局转义
{{ escape(text) }}
# 静态文件存储路径
{{ static_url("style.css") }}
# 路由反解析
reverse_url("路由别名")
# CSRF防范机制,CSRF也叫XSRF
# tornado开启csrf必须在配置中进行设置 xsrf_cookies = True
# 补充:
# 在前后端分离项目中,客户端可以通过cookie来读取XSRFToken,cookie名称为_xsrf,请求头必须名称:X-XSRFToken
# 在视图方法中可以通过 self.xsrf_token 来获取 XSRFTokentoken
{% module xsrf_form_html() %}
扫码二维码 获取免费视频学习资料
- 本文固定链接: http://phpxs.com/post/11276/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料
查 看2022高级编程视频教程免费获取