文章目录
tornado之hello world
tornado 服务启动比Django简单很多,有点像flask:
from tornado import (
web,
ioloop
)
class MainHandler(web.RequestHandler):
def get(self, *args, **kwargs):
self.write("hello, world")
if __name__ == '__main__':
app = web.Application([
("/", MainHandler)
])
app.listen(8888)
ioloop.IOLoop.current().start()
-
自定义类必须继承
web.RequestHandler
, 当客户端发起不同的http方法的时候,只需要重载handler中的对应的方法即可:class MainHandler(web.RequestHandler): def get(self, *args, **kwargs): """ 客户端发起get请求 :param args: :param kwargs: :return: """ self.write("hello, world") def post(self, *args, **kwargs): """ 客户端发起post请求 :param args: :param kwargs: :return: """ pass def delete(self, *args, **kwargs): """ 客户端发起delete请求 :param args: :param kwargs: :return: """ pass
-
为了充分发挥tornado异步地性能,最好定义异步方法,需要注意的是异步方法中不要有同步阻塞的代码:
class MainHandler(web.RequestHandler): async def get(self, *args, **kwargs): self.write("hello, world")
-
像Django一样将debug设为true修改程序自动重启:
if __name__ == '__main__': app = web.Application([ ("/", MainHandler) ], debug=True) app.listen(8888) ioloop.IOLoop.current().start()
需要注意的是,将debug设为True后,修改完程序,pycharm控制台会显示程序正常退出:
但是tornado服务仍在运行,不过是在后台运行:
如果我们想关闭该程序就只能通过任务管理器关闭后台一个叫做python的进程:
否则再次启动程序时会提示端口已被占用。
也可以在pycharm终端运行该程序文件,当代码变更后就会出现新的命令行,此时程序在后台运行:
此时如果想终止程序只需要按CTRL+C就可以了:
如果想查看控制台信息可以使用pycharm调试模式启动该程序,该模式在退出调试模式的同时程序也会退出:
tornado中为什么不能写同步方法
-
tornado程序是基于单线程的协程模式,所以如果写同步方法会导致同时请求多个url时出现阻塞现象:
import time from tornado import ( web, ioloop ) class MainHandler(web.RequestHandler): async def get(self, *args, **kwargs): # 休眠5秒模仿同步代码,当网页同时出现多个请求时会出现阻塞现象 # 如:第一个请求等待5秒,第二个请求等待10秒 time.sleep(5) self.write("hello, world2") if __name__ == '__main__': app = web.Application([ ("/", MainHandler) ], debug=True) app.listen(8888) ioloop.IOLoop.current().start()
即使同时请求的是不同的url(/和/2)请求也会出现阻塞现象:
class MainHandler(web.RequestHandler): async def get(self, *args, **kwargs): # 休眠5秒模仿同步代码,当网页同时请求多个url时会出现阻塞现象 # 如:第一个请求等待5秒,第二个请求等待10秒 time.sleep(5) self.write("hello, world") class MainHandler2(web.RequestHandler): async def get(self, *args, **kwargs): self.write("hello, world2") if __name__ == '__main__': app = web.Application([ ("/", MainHandler), ("/2", MainHandler) # 即使请求的是不同的url也会阻塞 ], debug=True) app.listen(8888) ioloop.IOLoop.current().start()
所以一定不能再请求方法中写同步代码,否则就不能发挥异步框架的性能
tornado中的url配置
-
可以将url抽离为变量,url可以使用正则匹配参数,并将参数传递给请求方法:
from tornado import web import tornado class MainHandler(web.RequestHandler): async def get(self, *args, **kwargs): self.write("hello, world") class PeopleIdHandler(web.RequestHandler): async def get(self, id, *args, **kwargs): self.write("用户id:{}".format(id)) class PeopleNameHandler(web.RequestHandler): async def get(self, name, *args, **kwargs): self.write("用户姓名:{}".format(name)) # 配置url urls = [ tornado.web.URLSpec("/", MainHandler, name="index"), # (\d+)会自动匹配url中的数字和字母,作为参数传递至请求方法中, # ?P<id>是为了增强代码的可读性,需要注意的是正则<>内的参数必须与方法中的参数名称一样 tornado.web.URLSpec("/people/(?P<id>\d+)/?", PeopleIdHandler, name="people_id"), tornado.web.URLSpec("/people/(?P<name>\w+)/?", PeopleNameHandler, name="people_name"), ] if __name__ == '__main__': app = web.Application(urls, debug=True) app.listen(8000) tornado.ioloop.IOLoop.current().start()
-
如果想使访问时浏览器自动在url末尾加上斜杠/ ,可以在url配置中加上
?
,这样在访问时不管输入http://localhost:8000/people/1还是http://localhost:8000/people/1/
都可以正常访问不会出现404:urls = [ ("/", MainHandler), ("/people/(\d+)/?", PeopleIdHandler), ("/people/(\w+)/?", PeopleNameHandler), ]
-
使用
tornado.web.URLSpec
配置url命名空间:urls = [ tornado.web.URLSpec("/", MainHandler, name="index"), tornado.web.URLSpec("/people/(\d+)/?", PeopleIdHandler, name="people_id"), tornado.web.URLSpec("/people/(\w+)/?", PeopleNameHandler, name="people_name"), ]
使用
self.reverse_url()
来获取命名空间的url:class PeopleIdHandler(web.RequestHandler): async def get(self, id, *args, **kwargs): # 跳转至命名空间为people_name的url,带有参数zzy self.redirect(self.reverse_url("people_name", 'zzy'))
-
通过url配置给handler传入初始值,需要注意传递people_db的参数必须是个字典:
people_db = { "name": "people" } # 配置url urls = [ tornado.web.URLSpec("/", MainHandler, name="index"), tornado.web.URLSpec("/people/(?P<id>\d+)/?", PeopleIdHandler, people_db, name="people_id"), tornado.web.URLSpec("/people/(?P<name>\w+)/?", PeopleNameHandler, name="people_name"), ]
然后在对应的逻辑类中重写initialize方法接受参数:
class PeopleIdHandler(web.RequestHandler): def initialize(self, name): self.name = name async def get(self, id, *args, **kwargs): # 跳转至命名空间为people_name的url,带有参数zzy self.redirect(self.reverse_url("people_name", self.name))
define、options、parse_command_line
options定义启动程序时传递的命令行参数以及类型:
from tornado import web
import tornado
from tornado.options import define, options, parse_command_line
# define, 定义一些可以在命令行中传递的参数以及类型
define('port', default=8008, help="run on the given port", type=int)
define('debug', default=True, help="set tornado debug mode", type=bool)
options.parse_command_line()
class MainHandler(web.RequestHandler):
async def get(self, *args, **kwargs):
self.write("hello, world")
# 配置url
urls = [
tornado.web.URLSpec("/", MainHandler, name="index")
]
if __name__ == '__main__':
app = web.Application(urls, debug=options.debug)
app.listen(options.port)
tornado.ioloop.IOLoop.current().start()
也可以通过配置文件读取参数:
# 通过命令行读取参数
# options.parse_command_line()
# 通过配置文件读取参数
options.parse_config_file("conf.cfg")
RequestHandler常用方法
-
初始化handler类接收参数的方法
initialize
:def initialize(self, db): # 初始化handler类接收参数的过程 self.db = db
-
用于真正调用请求处理之前的初始化方法
prepare
:def prepare(self): # 用于真正调用请求处理之前的初始化方法 # 如:打印日志,打开文件 pass
-
关闭句柄,清理内存
on_finish
:def on_finish(self): # 关闭句柄,清理内存 pass
-
http 请求方法:
def get(self, *args, **kwargs): pass def post(self, *args, **kwargs): pass def delete(self, *args, **kwargs): pass def patch(self, *args, **kwargs): pass
获取参数输入内容的方法:
def get(self, *args, **kwargs): """ get_query_argument 和 get_query_arguments 为获取get请求参数的方法 如果name不存在就会抛出400异常 :param args: :param kwargs: :return: """ # 获取的是字符串,默认取最后一个name的值 self.get_query_argument("name") # 获取的是列表,存放所有的name的值 self.get_query_arguments("name") def post(self, *args, **kwargs): """ get_argument 和 get_arguments 为获取post请求参数的方法 :param args: :param kwargs: :return: """ # 获取的是字符串,取最后一个name的值 data1 = self.get_argument("name") # 获取的是列表,如果url后边跟上name参数会将该name参数的值也放入列表中 data2 = self.get_arguments("name") # 获取所有的参数 data3 = self.request.arguments # 如果请求没有传递headers = { # "Content-type": "application/x-www-form-urlencoded;", # } # 获取json数据, 我们必须先从body中获取参数解码,然后转换为dict对象 # 才能调用get_body_argument 和 get_body_arguments 方法获取json参数 # 如果请求头传递了headers,我们可以直接使用get_body_argument获取参数 param = self.request.body.decode('utf-8') json_data = json.loads(param) data4 = self.get_body_argument("name") data5 = self.get_body_arguments("name")
输出内容的方法:
-
设置异常状态码
set_status
:try: data4 = self.get_body_argument("name") data5 = self.get_body_arguments("name") except Exception as e: self.set_status(500)
-
输出至浏览器显示方法
write
,因为tornado为长连接,所以可以连续写多个write方法,将内容连接起来:def get(self, *args, **kwargs): self.write("hello") self.write("world")
-
显示的内容并没有换行,write是将内容写入缓冲区,然后再将缓冲区中的所有内容显示在浏览器中。
-
如果想关闭长连接,可以调用
finish
方法:# http 方法 def get(self, *args, **kwargs): # 浏览器将会只显示hello不显示world self.write("hello") self.finish() self.write("world")
在调用finish方法的时候可以传递参数,可以传递字符串,字典,byte类型都可以回显:
```
def get(self, *args, **kwargs):
self.write("hello")
self.finish("\r\nover")
self.write("world")
```
-
自定义错误显示方法
write_error
:def write_error(self, status_code, **kwargs): """自定义错误页面显示""" pass
RequestHandler的子类
-
RedirectHandler
: 重定向类和
handler
本身的重定向方法self.redirect("", permanent=True)
作用一样。- permanent为True时是永久重定向状态码为301,为False时是临时重定向状态码为302
RedirectHandler
默认使用永久重定向RedirectHandler
使用是在url中直接配置,重定向更加简单方便,但是为永久重定向
if __name__ == '__main__': app = web.Application([ ("/", MainHandler), # 访问"/2"时重定向至"/" ("/2", RedirectHandler, {"url": "/"}) ], debug=True) app.listen(8888) ioloop.IOLoop.current().start()
-
StaticFileHandler
:处理静态文件类tornado读取静态文件有两种方式:
-
配置静态文件路径等信息,启动时将配置内容以参数的形式传递
from tornado import ( web, ioloop, ) from tornado.web import ( RedirectHandler, StaticFileHandler ) class MainHandler(web.RequestHandler): async def get(self, *args, **kwargs): self.write("hello, world") # 如果配置内容过多,可以将配置内容写入字典中然后在启动应用的时候以解包的方式传递使用 settings = { # 静态文件路径,即使访问static文件夹下子文件里的静态文件也是可以的 "static_path": "C:/Users/ZZY/Desktop/code/chapter02/static", # 访问静态文件输入的url, 不写默认为/static/ "static_url_prefix": "/static2/" } if __name__ == '__main__': app = web.Application([ ("/", MainHandler), # 访问"/2"时重定向至"/" ("/2", RedirectHandler, {"url": "/"}), ], debug=True, **settings) # 解包的方式将settings传递进来 app.listen(8888) ioloop.IOLoop.current().start()
-
也可以通过
StaticFileHandler
类配置url访问静态文件:from tornado import ( web, ioloop, ) from tornado.web import ( RedirectHandler, StaticFileHandler ) class MainHandler(web.RequestHandler): async def get(self, *args, **kwargs): self.write("hello, world") # # 如果配置内容过多,可以将配置内容写入字典中然后在启动应用的时候以解包的方式传递使用 # settings = { # # 静态文件路径,即使访问static文件夹下子文件里的静态文件也是可以的 # "static_path": "C:/Users/ZZY/Desktop/code/chapter02/static", # # 访问静态文件url, 不写默认为/static/ # "static_url_prefix": "/static2/" # } if __name__ == '__main__': app = web.Application([ ("/", MainHandler), # 访问"/2"时重定向至"/" ("/2", RedirectHandler, {"url": "/"}), # 访问静态文件,需要传入path ("/static/(.*)", StaticFileHandler, {"path": "C:/Users/ZZY/Desktop/code/chapter02/static"}), ], debug=True) app.listen(8888) ioloop.IOLoop.current().start()
-
tornado的template
模板使用
-
配置模板文件路径:
# 如果配置内容过多,可以将配置内容写入字典中然后在启动应用的时候以解包的方式传递使用 settings = { # 静态文件路径,即使访问static文件夹下子文件里的静态文件也是可以的 "static_path": "C:/Users/ZZY/Desktop/code/chapter02/static", # 访问静态文件url, 不写默认为/static/ "static_url_prefix": "/static2/", # 模板路径配置文件 "template_path": "templates" }
-
渲染模板:
模板语法
-
后端渲染:
class MainHandler(web.RequestHandler): async def get(self, *args, **kwargs): orders = [ { "name": "小米T恤 忍者米兔双截棍 军绿 XXL", "image": "http://i1.mifile.cn/a1/T11lLgB5YT1RXrhCrK!40x40.jpg", "price": 39, "nums": 3, "detail": "<a href='http://www.baidu.com'>查看详情</a>" }, { "name": "招财猫米兔 白色", "image": "http://i1.mifile.cn/a1/T14BLvBKJT1RXrhCrK!40x40.jpg", "price": 49, "nums": 2, "detail": "<a href='http://www.baidu.com'>查看详情</a>" }, { "name": "小米圆领纯色T恤 男款 红色 XXL", "image": "http://i1.mifile.cn/a1/T1rrDgB4DT1RXrhCrK!40x40.jpg", "price": 59, "nums": 1, "detail": "<a href='http://www.baidu.com'>查看详情</a>" } ] self.render("index.html", orders=orders)
-
遍历取值:
{% for order in orders %} {{ order['image'] }} {% end %}
-
加载静态文件:
<link href="{{ static_url('css/public.css') }}" type="text/css" rel="stylesheet">
使用该语法需要tornado配置如下,访问url必须是static:
# 如果配置内容过多,可以将配置内容写入字典中然后在启动应用的时候以解包的方式传递使用 settings = { # 静态文件路径,即使访问static文件夹下子文件里的静态文件也是可以的 "static_path": "C:/Users/ZZY/Desktop/code/chapter02/static", # 访问静态文件url, 不写默认为/static/ "static_url_prefix": "/static/", # 模板路径配置文件 "template_path": "templates" }
-
数学运算:
{{ order['price']*order['nums'] }}
-
引用方法:
定义一个方法计算总价格,将该方法传递给前端文件:
class MainHandler(web.RequestHandler): def cal_total(self, price, nums): return price*nums async def get(self, *args, **kwargs): orders = [ { "name": "小米T恤 忍者米兔双截棍 军绿 XXL", "image": "http://i1.mifile.cn/a1/T11lLgB5YT1RXrhCrK!40x40.jpg", "price": 39, "nums": 3, "detail": "<a href='http://www.baidu.com'>查看详情</a>" }, { "name": "招财猫米兔 白色", "image": "http://i1.mifile.cn/a1/T14BLvBKJT1RXrhCrK!40x40.jpg", "price": 49, "nums": 2, "detail": "<a href='http://www.baidu.com'>查看详情</a>" }, { "name": "小米圆领纯色T恤 男款 红色 XXL", "image": "http://i1.mifile.cn/a1/T1rrDgB4DT1RXrhCrK!40x40.jpg", "price": 59, "nums": 1, "detail": "<a href='http://www.baidu.com'>查看详情</a>" } ] self.render("index.html", orders=orders, cal_total=self.cal_total)
在index.html中引用该方法:
<div class="col col-4">{{ cal_total(order['price'], order['nums']) }}</div>
也可以在前端文件中直接通过 import 方法不需要通过传递方法名称来使用:
-
在utils.py文件中存在计算总价格的方法:
{% from utils import cal_total %}
需要注意的是前端引入方法的路径是以后端文件相对于引入文件来说的:
-
-
设置变量:
{% set total = 0 %} { total = total + order['price']*order['nums'] }
-
使用Python语法:
在tornado的模板中我们可以使用Python的语法只需要使用
{% %}
将语法括起来就可以了,如:{% if order['price'] > 0 %} <div class="col col-2">{{ order['price'] }}元</div> {% else %} <div>免费</div>
模板继承和重载
-
抽离公共部分至base页面,将非公共部分使用占位符:
index继承base页面:
tornado模板中UIModule
有时候公共模块不容易抽离出来,或者公共模块在不同的继承中需要显示不同的样式。这个时候就需要使用tornado的UIModule
,将小的模块抽离为组件。
-
在templates模板文件夹下建立一个文件夹,将需要抽离出的模块抽离出来:
-
建立一个module类重载
tornado.web.UIModule
,重写其中的render
方法,返回组件:class OrderModule(web.UIModule): def cal_total(self, price, nums): return price * nums def render(self, order, *args, **kwargs) -> str: """ ui模板子组件:订单列表 :param order: 父组件传递参数 :param args: :param kwargs: :return: """ return self.render_string("ui_modules/order_list.html", order=order, cal_total=self.cal_total)
-
配置ui_module组件全局名称对应的方法:
settings = { # 静态文件路径,即使访问static文件夹下子文件里的静态文件也是可以的 "static_path": "C:/Users/ZZY/Desktop/code/chapter02/static", # 访问静态文件url, 不写默认为/static/ "static_url_prefix": "/static/", # 模板路径配置文件 "template_path": "templates", # 配置UIModule组件配置 "ui_modules": { "OrderModule": OrderModule } }
-
使用OrderModule组件,传入参数order:
{% for order in orders %} {% module OrderModule(order) %} {% end %}
公共CSS和JS也可以抽离为公共组件
-
将公共css抽离出来:
-
在组件类中重写
css_file
方法:class OrderModule(web.UIModule): def cal_total(self, price, nums): return price * nums def render(self, order, *args, **kwargs) -> str: """ ui模板子组件:订单列表 :param order: 父组件传递参数 :param args: :param kwargs: :return: """ return self.render_string("ui_modules\order_list.html", order=order, cal_total=self.cal_total) def css_files(self): return ["ui_modules/order_list.css"]
odule组件,传入参数order:
{% for order in orders %}
{% module OrderModule(order) %}
{% end %}
公共CSS和JS也可以抽离为公共组件
-
将公共css抽离出来:
-
在组件类中重写
css_file
方法:class OrderModule(web.UIModule): def cal_total(self, price, nums): return price * nums def render(self, order, *args, **kwargs) -> str: """ ui模板子组件:订单列表 :param order: 父组件传递参数 :param args: :param kwargs: :return: """ return self.render_string("ui_modules\order_list.html", order=order, cal_total=self.cal_total) def css_files(self): return ["ui_modules/order_list.css"]