tornado基础
目录
Hello World Demo
from tornado.web import Application,RequestHandler # tornado 的基础web框架模块
from tornado.ioloop import IOLoop # tornado 的核心io循环模块,封装了Linux的epoll和BSD的kqueue
class IndexHandler(RequestHandler):
"""视图类"""
def get(self,*args,**kwargs):
self.write('hello world')
if __name__ == '__main__':
# 路由映射
app = Application([
(r'/',IndexHandler)
])
# 绑定监听端口
# 注意:此时服务器并没有开启监听
app.listen(8000) # 监听端口
# tornado.ioloop.IOLoop.current() 返回当前线程的IOLoop实例
# start 开启循环
IOLoop.current().start()
流程概述
服务端启动socket服务,托管给linux的epoll管理socket
浏览器通过tcp连接(三次握手),将本次socket托管在epoll
当用户发送请求携带数据时,本次socket产生一个信号
tornado的IOLoop实例会时刻与epoll询问当前是否有可执行的任务,当客户端socket产生信号,即携带数据,解析数据后通tronado的web中的路由映射执行其对应的函数
httpserver
from tornado.web import Application,RequestHandler # tornado 的基础web框架模块
from tornado.ioloop import IOLoop # tornado 的核心io循环模块,封装了Linux的epoll和BSD的kqueue
from tornado.httpserver import HTTPServer
class IndexHandler(RequestHandler):
"""视图类"""
def get(self,*args,**kwargs):
self.write('hello world')
if __name__ == '__main__':
app = Application([
(r'/',IndexHandler)
])
# 实例化http服务器对象并绑定端口
httpServer = HTTPServer(app)
httpServer.listen(8000)
# tornado.ioloop.IOLoop.current() 返回当前线程的IOLoop实例
# start 开启循环
IOLoop.current().start()
单进程与多进程
tornado默认启动的是单进程
开启多进程
import tornado.web
import tornado.ioloop
import tornado.httpserver
class IndexHandler(RequestHandler):
def get(self,*args,**kwargs):
self.write('hello world')
if __name__ == '__main__':
app = tornado.web.Application([
(r'/',IndexHandler)
])
httpServer = tornado.httpserver.HTTPServer(app)
httpServer.bind(8000)
httpServer.start(5) # 默认开启1个进程,指定开启5个,None或者小于等于0默认开启cpu核心数的个数
tornado.ioloop.IOLoop.current().start()
注意:
虽然tornado为我们提供了一次性启动多个进程的方式,但是由于一些问题,不建议以上方式启动多进程,而是使用命令行手动启动多进程,这样还能绑定不同的端口
为什么不通过代码启动多进程?
1.每个子进程都会从父进程中复制一份IOLoop的实例,如果在创建子进程前修改了IOLoop会影响所有的子进程
2.所有的进程都是由一个命令启动的,无法做到在不停止服务的情况下修改其中一个进程的代码
3.所有进程共享一个端口,想要分别监控很困难
options
tornado提供了一个tornado.options模块用以全局参数的定义存储,定义,修改
参数介绍
from tornado.options import define,options
# 方法原型
define(
name, # 属性名,必须保证其唯一性,否则报Option %r already defined in %s"错误
default=None, # 设置选项的默认值
type=None, # 设置选项变量的类型,从命令行或配置文件导入参数时tornado会根据类型转换值,如果没有设置type 会根据default值进行转换,如果没设置default不会转换
help=None, # 帮助文本
metavar=None,
multiple=False, # 允许多个值 如 list tupple multiple=True
group=None,
callback=None
)
# 定义变量
define('port',default=8000,type=int)
define('list',default=[],type=int,multiple=True)
# 调用变量
options.prot # 全局的options对象,所有定义号的选项变量都会作为该对象的属性
options.list
# 获取参数的方法
options.parse_command_line() # 将命令行的参数传到define中定义,
命令行启动实例
import tornado.web
import tornado.ioloop
import tornado.httpserver
from tornado.options import define,options
# 定义变量
define('port',default=8000,type=int)
define('list',default=[],type=int,multiple=True)
class IndexHandler(tornado.web.RequestHandler):
"""视图类"""
def get(self,*args,**kwargs):
self.write('hello world')
if __name__ == '__main__':
# 转换命令行参数,并保存到options对象中,需要通过命令行启动文件
options.parse_command_line()
print(options.list)
app = tornado.web.Application([
(r'/',IndexHandler)
])
httpServer = tornado.httpserver.HTTPServer(app)
httpServer.listen(options.port)
tornado.ioloop.IOLoop.current().start()
shell启动入口文件
python xxx.py --port=8080 --list=1,2,3,4
# 此时启动的就是在8080端口了
配置文件启动实例
config #####没有后缀
options.parse_config_file(path) # 从配置文件导入参数,path是配置文件的路径
shell启动入口文件
python xxx.py
此时我们的配置文件仍需要严格按照python的语法书写,不支持字典类型
配置文件
刚开始使用的是options方式,后来改成py包的方式配置的
config.py
options = {
'prot':8080,
'list':[1,2,3]
}
入口文件
from tornado.web import RequestHandler
import tornado.ioloop
import tornado.httpserver
import config
class IndexHandler(RequestHandler):
"""视图类"""
def get(self,*args,**kwargs):
self.write('hello world')
if __name__ == '__main__':
print(config.options.list)
app = tornado.web.Application([
(r'/',IndexHandler)
])
httpServer = tornado.httpserver.HTTPServer(app)
httpServer.listen(config.options.port)
tornado.ioloop.IOLoop.current().start()
配置文件加载到Application对象中
application.py
import config # 导入模块
import views
from tornado.web import Application
class Application(Application):
def __init__(self):
handler = [
(r'/',views.index.IndexHandle)
]
super().__init__(handler,**config.settings)
基本配置参数
settings.py
BASE_DIRS = os.path.dirname(__file__)
# 配置
settings = {
# 静态文件目录
'static_path' : os.path.join(BASE_DIRS,'static'),
# 模板目录
'template_path' : os.path.join(BASE_DIRS,'template'),
'debug':True # 默认为false,即生产模式,项目上线需要改成false
'autoreload':True # 只有自动重启的功能,需要注释debug配置
# 秘钥字符串配置
'cookie_secret':'DL93JQb8Qv++5iia76G5a4kxJ4RnHUDjk7iRZJC9GP8='
# xsrf保护
'xsrf_cookies':True
# 用户认证失败映射的路由
'login_url':'/login'
}
# 生成随机字符串
import base64
import uuid
base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
### b'DL93JQb8Qv++5iia76G5a4kxJ4RnHUDjk7iRZJC9GP8='
参数描述
debug:True的特性
1.tornado应用会监控源代码文件中如果有保存改动时,会重启服务器,可以减少手动重启次数,从而提高开发效率
2.当服务器改动后,访问发生错误时,不会重启服务,需手动重启
3.取消缓存编译的模板 'compiled_template_cache':True # debug为True时,此参数为fasle 不缓存,可单独配置
4.取消缓存静态文件的hash值 'static_hash_cache':True # debug为True时,此参数为false不缓存,可单独配置
5.提供追踪信息 'serve_traceback':True # debug为True时,此参数为True,提供追踪信息,可单独配置
logging日志
当使用parse_command_line 或者parse_config_file 时 tornado会默认开启logging模块功能,向终端输出一些打印信息
关闭日志
入口文件代码控制日志关闭
from tornado.options import options
if __name__ == '__main__':
# 第一行就关闭
options.logging=None
命令行关闭
python xx.py --logging=none
关闭cmd后服务会关闭,当项目上线时,启动入口文件都是启动的守护进程,即后台运行,关闭终端也不会断开服务
shell
nohup python xxx.py &
路由系统
固定传参
urls.py
import views
from tornado.web import Application
class Application(Application):
def __init__(self):
handler = [
(r'/',views.index.IndexHandle,{'world1':1,'world2':2})
]
super().__init__(handler,**config.settings)
view(传参)
from tornado.web import RequestHandler
class IndexHandler(RequestHandler):
"""视图类"""
def initialize(self,world1,world2):
self.world1 = world1
self.world2 = world2
def get(self,*args,**kwargs):
print(self.world1,self,world2)
self.write('hello world')
反向解析
urls.py
from tornado.web import Application
from tornado.web import url
from views import index
class Application(Application):
def __init__(self):
handler = [
url(r'/',index.IndexHandler,name='index')
]
super().__init__(handler,**config.settings)
views
from tornado.web import RequestHandler
class IndexHandler(RequestHandler):
"""视图类"""
def initialize(self):
# 获取路由匹配时{}中传递的参数
pass
def get(self,*args,**kwargs):
# 根据name获取url路径
self.reverse_url('index')
self.write('hello world')
RequestHandler
url携带参数
无名分组
url(r'/article/(\w+)/(\w+)',index.ArticleHandler,name='index')
获取参数views
from tornado.web import RequestHandler
class ArticleHandler(RequestHandler):
"""视图类"""
def get(self,h1,h2,h3,*args,**kwargs):
"""通过get方法的位置传参"""
print(h1,h2,h3)
self.write('hello world')
命名分组
url(r'/article/(?P<page>\w+)/(\w+)',index.ArticleHandler,name='index')
from tornado.web import RequestHandler
class ArticleHandler(RequestHandler):
"""视图类"""
def get(self,page,h2,h3,*args,**kwargs):
"""通过get方法的位置传参"""
print(page,h2,h3)
self.write('hello world')
get方法传递参数
http://www.xxx.py/article?a=1&b=2
http://www.xxx.py/article?a=1&a=2
路由匹配
url(r'/article',index.ArticleHandler,name='index')
view
from tornado.web import RequestHandler
class ArticleHandler(RequestHandler):
"""视图类"""
def get(self,*args,**kwargs):
"""通过get方法的位置传参"""
# 多个同名参数只取最后一个,如果参数不存在,返回default定义的值,如果没定义default,会抛出 tornado.MissingArgumentError,strip 是否过滤两遍的空格
self.get_query_argument(self, name, default=_ARG_DEFAULT, strip=True)
a = self.get_query_argument('a')
# 多个同名参数
self.get_query_arguments(self, name, strip=True)
art_list = self.get_query_arguments(a)
print(art_list[1])
self.write('hello world')
post方法传递参数
from tornado.web import RequestHandler
class ArticleHandler(RequestHandler):
"""视图类"""
def get(self,*args,**kwargs):
# 配置好template_path后,直接返回html文件
self.render('articl.html')
def post(self,*args,**kwargs):
username = self.get_body_argument('username')
password = self.get_body_argument('password')
hobby_list = self.get_body_arguments('hobby')
self.write('hello world')
既可以获取get方法请求参数,又可以获取post方法请求参数
from tornado.web import RequestHandler
class ArticleHandler(RequestHandler):
"""视图类"""
def get(self,*args,**kwargs):
# 配置好template_path后,直接返回html文件
username = self.get_argument('username')
password = self.get_argument('password')
hobby_list = self.get_arguments('hobby')
self.render('articl.html')
def post(self,*args,**kwargs):
username = self.get_argument('username')
password = self.get_argument('password')
hobby_list = self.get_arguments('hobby')
self.write('hello world')
request对象
# 参数:
method # 请求方法
host # 被请求的主机ip:端口
uri # 请求的完整资源地址,包括路径和get查询参数部分
path # 请求路径
query # 请求参数
version # 使用的http版本
headers # 请求头的协议头,字典类型
body # 请求体的数据
remote_ip # 客户端的ip地址
files # 用户上传的文件 字典类型
remote_ip # 当前用户ip
实例
from tornado.web import RequestHandler
class ArticleHandler(RequestHandler):
"""视图类"""
def get(self,*args,**kwargs):
self.request.method
self.render('articl.html')
HTTPFile对象
tornado.httputil.HTTPFile
# 上传一个文件就是HTTPFile对象,对象属性
filename
body
content-type # 文件类型
Response
# 返回数据
self.write()
程序先将数据存放到write_buffer缓冲区,再由缓冲区写入服务器
刷新缓冲的情况:
1.程序结束,请求结束
2.手动刷新
3.缓冲区慢了
4.遇到\n
# 返回模板
self.render('home.html',{'a':1})
# 刷新缓冲区
self.finish() # 手动刷新缓冲区,关闭当次请求通道,再write就没有意义了
# 利用write()写json数据
利用json模块dumps的json字符串的content-type类型为text/html
而使用write自动序列化的方式 content-type为application/json
# 设置响应头
self.set_header(name,value) #手动设置一个名为name 值为value的响应头
self.set_header('Content-Type','application/json;charset=UTF-8')
class IndexHandler(RequestHandler):
def set_default_headers(self):
'''在get前调用,可以预先设置默认的headers,如果get中重新set_header后会被覆盖'''
self.set_header('Content-Type','application/json;charset=UTF-8')
def get(self):
pass
def post(self):
pass
# 返回状态码
'''status_code:int类型状态码,reason 描述信息:str类型,如果reason=None,namestatus_code必须是规定好的正常状态码,导致服务器错误'''
self.set_status(status_code,reason=None)
# 重定向
self.redirect(url)
# 返回错误信息,tornado会调用self.write_error()
self.send_error(status_code=500,**kwargs)
# 用来处理send_error()抛出的错误信息,并返回给浏览器
def write_error(status_code,**kwargs)
class IndexHandler(RequestHandler):
def write_error(self,status_code,**kwargs):
'''在get前调用,可以预先设置默认的headers,如果get中重新set_header后会被覆盖'''
if status_code==500 :
self.set_status(500)
self.render('error500.html')
def get(self):
# 调用write_error()
self.send_error(status=500)
def post(self):
pass
预留接口执行顺序
set_default_headers() #设置响应头
initialize() # 可接收路由匹配传的字典参数
prepare() # 做预处理,在执行对应请求方法前调用,比如反爬策略,限流,认证权限之类的
write_error()
on_finish() # 请求处理结束后调用,资源释放,日志处理,尽量不要做响应逻辑
在正常流程执行未抛出错误时的顺序
set_default_headers -- initialize -- prepare -- HTTP请求方法逻辑 -- on_finish
抛出错误时
set_default_headers -- initialize -- prepare -- HTTP请求方法逻辑 -- set_default_headers -- write_error -- on_finish
模板
static_url创建了一个机遇文件内容的hash值,并将其以?拼接的方式当做查询参数添加到URL末尾,以便快速查询到该文件
路径配置
{'template_path' : os.path.join(BASE_DIRS,'template'),}
模板语法
变量
{{ num }}
{{num + 10}}
{{dict['num']}} # 不能dict.num
流程控制
{% if num == 1 %}
{% elif %}
{% else %}
{% end %}
# for循环
{% for num in num_list %}
{% end %}
# while循环
{% while 条件 %}
{% end %}
反向解析
static_url()
# 示例:
<link href="{{static_url('css/home.css')}}"></link>
自定义函数
# 自定义函数,后端传函数名
{% function(100,10) %}
自动转义
# 转义
# tornado自动开启自动转义功能,防止了网站受到恶意攻击,如果遇到标签,script代码等
# 关闭自动转义
{% raw 变量名 %} # 单行
{% autoescape None %} # 当前文档全部关闭
{'autoescape':None} # 配置中配置,所有请求相关资源都会关闭,禁用
{% escape(变量名) %} # 在当前整个关闭自动转义后,可以对某个特定变量开启转义
block块
{% block con %}
{% end %}
模板继承
{% extends 'base.html' %}
静态文件
static_path
# static_path是静态文件的目录路径
# 静态文件配置
settings = {
'static_path' : os.path.join(BASE_DIRS,'static'),
}
StaticFileHandler
静态页面
不通过路由匹配,直接请求静态资源
比如:http://www.baidu.com
# 通过/路径进行路由匹配出的资源
http://www.baidu.com/index.html
# 直接请求项目根路径下的index.html这个文档
tornado.web.StaticFileHandler
可以通过这个类来映射静态页面,不用渲染数据的页面
# 路由匹配,写在最下面,否则会出现其他的路由匹配不到了
(r'/(.*)$',tornado.web.StaticFileHandler,{'path':os.path.join(setting.BASE_DIRS,'static/html'),'default_filename':'index.html'})
"""
http://www.best.com --- 访问default_filename的默认资源
http://www.best.com/index.html --- 访问index.html
"""
数据库数据交互
初始化数据库连接对象
import config # 导入模块
import views
from tornado.web import Application
class Application(Application):
def __init__(self):
handler = [
(r'/',views.index.IndexHandle)
]
super().__init__(handler,**config.settings)
self.db = 数据库连接对象(参数....)
视图使用
class Artile(RequestHandler):
def get(self,*args,**kwargs)
self.application.db # 数据库对象
cookie
普通cookie
# 设置cookie
self.set_cookie(name, value, domain=None, expires=None, path="/",expires_days=None, **kwargs)
# 获取cookie
self.get_cookie(self, name, default=None)
self.get_cookie(id, default='未登录')
# 清除cookie
# 并不是立即删除浏览器的cookie 而是给cookie值设置为空,并改变有效期,真正删除cookie是浏览器发现为空时,自动清除
self.clear_cookie(self, name, path="/", domain=None)
# 清除所有cookie
self.clear_all_cookies(self, path="/", domain=None)
参数说明
name # cookie名
value # cookie值
domain # 提交cookie时匹配的ip
expires # cookie的有效期,时间戳,时间元祖,datatime UTC时间
path # 提交cookie时匹配的路径
expires_days # 设置有效期时间,优先级低于expires
实际上是通过设置响应头实现的
# 同理
self.set_header('Set-Cookie',cookie值)
安全cookie
由于cookie是存储在客户端的,很容易被篡改,tornado提供了对cookie进行混淆加密的方法,单不是完全安全,cookie中不要存储敏感性cookie
# cookie_secret 随机字符串,同理django的SECRET_KEY
# 生成随机字符串
import base64
import uuid
base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
### b'DL93JQb8Qv++5iia76G5a4kxJ4RnHUDjk7iRZJC9GP8='
# 配置文件配置
{'cookie_secret':'DL93JQb8Qv++5iia76G5a4kxJ4RnHUDjk7iRZJC9GP8='}
原型
# 设置安全cookie
self.set_secure_cookie(self, name, value,expires_days=30, version=None,**kwargs)
# 获取安全cookie
self.get_secure_cookie(self, name, value=None, max_age_days=31,min_version=None)
# max_age_days --- 超过31天的过滤掉,根据expires_days+1计算
# return bytes类型
参数说明
# cookie
2|1:0|10:1624724193|3:ali|8:aGVsbG8=|b5cc0126960cd76c3d6583cb0ef42edfef1d7cc5354c1c077dd26dda1bfbc39e
# 安全cookie版本
2 --- # DEFAULT_SIGNED_VALUE_VERSION
1: --- # 后面数据的长度
0 --- # key_version
10: ---# 后面数据的长度
1624724193 --- # 时间戳
ali --- # cookie名
aGVsbG8= --- # b64加密后的cookie值
签名 --- sha256加密后,通过秘钥字符串加盐后的字符串
cookie计数
访问频率限制,但会存在xsrf攻击
用户携带cookie访问频率
# 第一种方式
def prepare(self,*args,**kwargs):
"""
执行视图逻辑之前
"""
count = self.get_cookie('count',None)
if not count:
count = 1
else:
int(count) += 1
self.set_cookie('count',count)
# 判断在一定时间内用户的访问次数,如果频率太快就send_error(),需要根据用户进行隔离
#第二种方式:
对于相对安全的操作,一般不会将cookie计数放在prepare或者get方法中,一般是post方法,配合csrf_token 实现保护机制
xsrf保护机制
# xsrf -- 跨站请求伪造
# 同源机制,原理是可以发送请求,服务器也能接受到,如果是get,但是浏览器会根据同源机制不会渲染本次请求数据,如果是post则会返回403,首先得开启xsrf保护
方式一:配置文件
// 开启xsrf_cookies 保护
{'xsrf_cookies':True}
# 配合模板中form表单应用
<form>
{% module_xsrf_form_html() %}
</form>
"""django中是在前端模板中生成隐藏标签,值为token字符串,用户发送请求时,携带这个字符串,后端进行校验实现,而tornado中是通过设置cookie的方式实现_xsrf为cookie名,在关闭浏览器时失效,并且添加隐藏标签名为_xsrf"""
第二种方式:手动设置
# 或者通过js实现,form表单提交
# 1.将xsrf_token字符串,写入到前端的cookie中
self.xsrf_token
# css
<form>
<input id='xsrf' type='hidden' name='_xsrf' value=''></input>
</form>
# js
<script>
function getCookie(name){
var cookie = document.cookie.match("\\b"+name+"=([^;]*)\\b")
return cookie ? cookie[1] : undefined
}
document.getElementByID('xsrf').value = getCookie('_xsrf')
</script>
方式三:ajax提交
# 1.首先引入jquery,并用反向解析
<script>
function login(){
$.ajax({
url: "test.html",
method:'POST',
data:{},
context: document.body,
success: function(){
$(this).addClass("done");
// 设置请求头
headers:{
'X-XSRFToken':getCookie('_xsrf')
}
}
);
}
</script>
访问主页时就设置xsrf
urls.py
from tornado.web import Application
from tornado.web import url
from views import index
class Application(Application):
def __init__(self):
handler = [
url(r'/',index.IndexHandler,name='index')
]
super().__init__(handler,**config.settings)
views.py
from tornado.web import StaticFileHandler
class IndexHandler(StaticFileHandler):
def __init__(*args,**kwargs):
super().__init__(*args,**kwargs)
# 将xsrf字符串设置到浏览器的cookie中
self.xsrf_token
# 通过ajax发送请求的时候还要携带_xsrf,上面代码ajax提交会实现此代码
登录认证
authenticated装饰器
from tornado.web import RequestHandler,authenticated
class LoginHandler(RequestHandler):
def get(self,*args,**kwargs):
next_url = self.get_argument('next','/')
url = '/login?next=%s'%next_url
self.render('login.html',{"next_url":next_url})
def post(self,*args,**kwargs):
next_url = self.get_argument('next','/')
if username == 1 and pwd == 2:
# 获取next请求参数
# 重定向到之前访问的页面,并携带已登录的标识
self.redirect(next_url+'?flag=logined')
else:
# 可能会取不到next,因为前段form表单的请求路径写死了,需要动态变化,请求login页面时就将next传给前端的form请求路径中
self.redirect('/login?next=%s'%next_url)
# 访问home,验证是否登录,如果登录执行get,没登录重定向到login
class HomeHandler(RequestHandler):
def get_current_user(self):
"""
return True: 直接执行请求方法
return False : 如果是get/head方法,重定向到配置文件中配置的login_url,并携带一个next请求参数表示是从哪个页面跳转过来的,然后再执行loginview的视图逻辑
"""
# 获取标识,如果有则返回的是logined字符串,为True,如果没有则是返回None,则为false
flag = self.get_argument('flag',None)
return flag
@authenticated
def get(self,*args,**kwargs):
self.render('home.html')
同步与异步
# 同步
先执行任务1,任务1,执行完毕后执行任务2,如果遇到io操作一直等待到io完毕后
# 异步
先执行任务1,遇到io,切换cpu执行下一个任务遇到io,切换回任务1.
# 异步应用场景
ajax
tornado 基于异步,才会高效
回调函数实现异步
# 实现思路
对于耗时的操作,交给另一个线程做,主线程继续往下执行,当耗时操作结束后将结果反馈给我们
import time
import threading
def Iojob(callback):
def run(callback):
time.sleep(3)
print('doing job')
# 执行回调函数
callback('返回的数据')
threading.Thread(target=run,args=(callback,)).start()
def callback(data):
"""回调函数返回data数据
"""
print(data)
def workA():
print('worka')
Iojob(callback)
def workB():
print('workb')
Iojob(callback)
def run():
workA()
workB()
if __name__ == '__main__':
run()
协程实现异步
# 实现思路
根据协程保持状态的特性,当遇到yield关键字时遇到io 程序会被挂起, send 的时候会唤醒当前协程
import time
import threading
gen = None
def Iojob():
def run():
time.sleep(3)
print('doing job')
try:
global gen
gen.send('status')
except StopIteration:
pass
threading.Thread(target=run).start()
def gen(func):
def wrapper(*args,**kwargs):
global gen
gen = func(*args,**kwargs)
next(gen)
return wrapper
@gen
def workA():
print('worka')
res = yield Iojob()
print('处理完的结果:',res)
def workB():
print('workb')
Iojob()
def run():
workA()
workB()
if __name__ == '__main__':
run()
消除全局变量最终版
import time
import threading
def Iojob():
time.sleep(3)
print('doing job')
yield 'status'
def gen(func):
def warpper(*args,**kwargs):
gen1 = func(*args,**kwargs) # workA的生成器
gen2 = next(gen1) # Iojob的生成器
def run(gen2):
res = next(gen2)
try:
gen1.send(res) # 返回给workA数据
except StopIteration:
pass
threading.Thread(target=run,args=(gen2,)).start()
return warpper
@gen
def workA():
print('worka')
res = yield Iojob()
print('处理完的结果:', res)
def workB():
print('workb')
Iojob()
def run():
workA()
workB()
if __name__ == '__main__':
run()
tornado中的异步
因为epoll 主要是用来解决网络io的并发问题,所以tornado的异步也是最主要体现在网络的IO异步上,即异步web请求
AsyncHTTPClient
tornado.httpclient.AsyncHTTPClient # tornado提供的异步web请求客户端,用来进行异步web请求
fetch(request,callback=None) --- # 用于执行一个web请求,并异步响应,返回一个tornado.httpclient.HttpResponse对象
# request可以需要发送请求的url,也可以是一个tornado.httpclient.HTTPRequest对象,如果传入的是url,会自动编程一个HTTPReqeust对象
HTTPRequest
# http请求类
构造函数的参数
url
method
headers
body # http请求体
HTTPResponse
# http响应类
code -- 状态码
reason --状态码的描述
body -- 响应的数据
error -- 异常
示例
# 对第三方服务发起异步请求
class GetMessage(ReqeustHandler)
def on_response(self,response):
if response.error:
self.send_error(500)
else:
data = response.body
self.wirte(data)
self.finish()
@tornado.web.asynchronous # 不关闭http通道
def get(self,*args,**kwargs):
# 创建客户端
client = AsyncHTTPClient()
# 发送请求,如果成功执行self.on_response回调函数
client.fetch(url,self.on_response)
# self.write('ok') 因为响应时异步的,如果现在返回给浏览器的话就会关闭通道,异步请求到的结果就没办法接收到数据了
协程异步
class GetMessage(ReqeustHandler)
@tornado.gen.coroutine # 协程异步装饰器,get请求整个挂起
def get(self,*args,**kwargs):
# 创建客户端
client = AsyncHTTPClient()
# 基于协程实现的io操作
res = yield client.fetch(url)
if res.error:
self.send_error(500)
else:
self.write(data)
# self.write('ok') 因为响应时异步的,如果现在返回给浏览器的话就会关闭通道,异步请求到的结果就没办法接收到数据了
将IO操作单独封装
class GetMessage(ReqeustHandler)
@tornado.gen.coroutine # 协程异步装饰器,get请求整个挂起
def get(self,*args,**kwargs):
# 基于协程实现的io操作
res = yield self.get_data()
self.write(res)
@tornado.gen.coroutine
def get_data(self):
# 创建客户端
client = AsyncHTTPClient()
res = yield client.fetch(url)
if res.error:
ret = {'ret':0}
else:
ret = res.body
raise tornado.gen.Return(ret)
websocket
tornado.websocket 提供了WebSocketHandler基类进行通信
open() # 当建立websocket连接后服务端触发这个方法
on_message() #客户端发送message给服务器时服务端触发这个方法
on_close() # 当websocket连接关闭之后服务端触发这个方法
write_message(message,birnary=False) #向客户端发送消息,一般为字符串类型,如果是字典类型则,内部转成json串,如果birnary的值为False 则message以utf-8编码发送,反之则以二进制模式发送,可以发送任意字节码
close() # 服务器关闭websocke连接
check_origin(origin) # 判断源origin,对于符合条件的请求允许连接,否则返回403,可以重写该方法来解决websocket的跨域连接问题
即时通讯后端代码示例
from tornado.websocket import WebSocketHandler
class ChatHandler(WebSocketHandler):
users = []
def open(self,*args,**kwargs):
# 1.建立socket连接后执行这个函数,表示该用户已经登录了
# 2.登录后通知好友列表中的所有人
self.users.append(self)
for user in users:
user.write_message('%s已上线'%username)
def on_message(self,message):
# 将消息,广播给每一个用户
for user in self.users:
user.write('%s 说:%s'%(user,message))
def on_close(self):
self.users.remove(self)
for user in users:
user.write_message('%s已上线'%username)
def check_origin(origin):
# 可以跨域
return True
<script>
<!--建立websocket链接 -->
var ws = new WebSocket('ws://10.0.145.117:8000/chat')
# 后端发送数据即执行onmessage方法
ws.onmessage = function(data){
data.data
}
function sendMessage(){
var msg = document.getElementById('message').value
# 通过websocket发送数据
ws.send(msg)
}
</script>
django和tornado的区别?
# django中是在前端模板中生成隐藏标签,值为token字符串,用户发送请求时,携带这个字符串,后端进行校验实现,而tornado中是通过设置cookie的方式实现
python防脱发技巧

浙公网安备 33010602011771号