tornado分布式框架
tornado文档
简介
tornado web server : web 框架
优势:
1. 轻量级的 web 框架, 和Django比
2. 性能卓越, 使用 异步非阻塞
3. 处理 C10K 的场景, C10K : concurrent(并发) 10000 , 能够同一时间处理10000个并发的连接
和Django对比:
django tornado
路由 有 有
模板 有 有
视图 有 有
ORM 有 无
session 有 无
cookie 有 有
form,admin, 中间件等 有 无
通过上述对比, django 是一个大而全的框架, 而tornado是一个小而精的框架, 着眼于服务高性能
Django 有点像国产车, torando 像保时捷, 内饰之类的东西需要自己定制
安装
pip3 install tornado (linux yum)
或者源码:
python3 setup.py install (linux 源码下载 .tar.gz 文件, make && make install)
推荐使用 linux 平台或者 BSD 平台
原因:
因为tornado的高性能, 不是依赖于开启多个进程或者线程, 即便在单进程下保持高效, 是依赖于linux的epoll和BSD的Kqueue特别的高效
tornado简单上手(单进程下开发,推荐使用)
import tornado.web # tornado框架web界面模块
import tornado.ioloop # tornado框架强大引擎,IO高效核心模块
# 视图层
class IndexHandler(tornado.web.RequestHandler):
# 在执行get/post请求之前, 都会先执行initialize()方法
def initialize(self, height):
self.height = height
print('this is initialize')
# 接受get请求
def get(self):
print('get。。。。')
self.write('hello world。。') # 相当于Django框架中的HttpResponse
# 接受post请求
def post(self):
pass
# 配置路由
app = tornado.web.Application([
(r'/', IndexHandler), # 路由匹配试图函数
])
# 绑定端口
'''
IOLoop.current() : 返回一个 IoLoop的实例
IOLoop.current().start() : 开始监听这个8001端口
'''
app.listen(8001) # 绑定端口
tornado.ioloop.IOLoop.current().start() # 开始监听端口
tornado执行流程
1. 绑定一个端口
2. 启动服务并开始监听
3. 客户端来了一个请求后 : 127.0.0.1:8001/
4. 将请求转发给路由, 进行匹配
5. 然后寻找相对应的视图进行处理, 处理完成之后, 返回数据
tornado框架-单进程下开发(基本不用)
import tornado.httpserver # 开启tornado服务端
# 配置路由
app = tornado.web.Application([
(r'/', IndexHandler), # 路由匹配试图函数
])
# 开单进程
httpServer = tornado.httpserver.HTTPServer(app) # 实例化httperver, 并绑定端口
httpServer.listen(8001) # 绑定端口8001
# 相当于: app.listen(8001)
tornado框架-多进程开发(基本不用)
import tornado.httpserver # 开启tornado服务端
# 配置路由
app = tornado.web.Application([
(r'/', IndexHandler), # 路由匹配试图函数
])
httpServer = tornado.httpserver.HTTPServer(app) # 实例化httperver, 并绑定端口
httpServer.bind(8001) # 绑定端口8001
# 开启多进程
httpServer.start(num) :
取值:
num > 0 : num=5: 开启5个进程
num < 0 : 取值根据电脑的cpu核数进行取值
num : 默认是1
# 注意:
# 但是不建议大家使用上述的httpServer方式:
# 1. 如果想改其中一个进程相关的代码, 需要将所有的进程全部停止掉
# 2. 监控不方便 (8000 -- 8005)
# 基于上面的原因, 推荐大家使用 app.listen(8000)
# 如果想要起多个端口进行监控,起多个客户端, 然后运行服务端的相关代码
tornado框架-options: (配置信息,基本不用)
options:在define中定义的所有变量, options都可以通过.来获得
以config.txt文本的形式配置配置信息(基本不用)
- 一个变量单值
from tornado.options import define, options
# 常见的方法和属性:
define("port", default=8000, type=int, help='端口号') # 配置一个值
# 参数详解:
# name : 定义的变量名
# default : 变量对应的默认值 , 如果不同过命令行或者配置文件传入这个变量对应的值的话, 就使用默认值
# type : 类型 变量的类型
# mutiple : 默认是False, 表示可以给变量传入多个值
if __name__ == '__main__':
tornado.options.parse_command_line('config') # 注意命令行传参,必须加上解析单个值命令
# 路由层
app = tornado.web.Application([
(r'/', IndexHandler),
])
app.listen(options.port) # 绑定一个端口,端口从config.txt的自动帮忙取, 并没有开始监听
tornado.ioloop.IOLoop.current().start() # 开始监听端口
实例:
# define("port", default=8000, type=int, help='端口号') # 一个变量单个值
# define('names', default=[], type=str, multiple=True) # 一个变量多值
# 需要加上 : tornado.options.parse_command_line()
- 一个变量多个值
define('names', default=[], type=str, multiple=True) # 配置多个值
tornado.options.parse_config_file(path) # 会将config文件中的配置读入到options中
上述传值存在问题:
- 不支持 Python 的语法格式
- 不支持字典格式
以普通python文件配置配置信息config.py: (推荐使用)
# 将文件当成一个包 使用
options = {
"port" : 8003,
"host" : '127.0.0.1',
}
import config # 直接导模块(配置文件)使用
port = config.options.port
tornado自定以目录结构(仿Django)
MTV与MVC的区别
MVC :
M: model 操作数据库
V: views 操作静态html页面
C: controller 业务逻辑
MTV:
M: model 操作数据库
V: views 业务逻辑
T: 操作静态html页面
基础目录结构
static : 存放静态文件 (js, css, img等)
templates : 存放静态页面 (html文件)
models : 存放操作数据库相关的model文件
views : 存放的是处理业务逻辑的文件
server.py : 启动服务文件
application.py : 路由配置文件
config.py : 配置文件
tornado自定义框架
static文件夹
models文件夹
templates文件夹
- index.py
import tornado.web
# 视图层
class IndexHandler(tornado.web.RequestHandler):
# 接受get请求
def get(self):
print('get。。。。')
# self.write('hello world。。') # 相当于Django框架中的HttpResponse
self.render('index.html')
# 接受post请求
def post(self):
pass
application.py 路由层
from tornado.web import Application
from views.index import IndexHandler
from config import settings
# 配置路由
class App(Application):
def __init__(self):
url = [
(r'/', IndexHandler), # 路由匹配试图函数
]
super(App, self).__init__(url, **settings)
config.py 配置信息
import os
BASE_DIR = os.path.dirname(__file__)
# 变量的配置
options = {
"port": 8002,
}
settings = {
"static_path": os.path.join(BASE_DIR, 'static'),
'template_path': os.path.join(BASE_DIR, 'template'),
'debug': True,
}
server.py 框架启动文件
import tornado.ioloop # tornado框架强大引擎,IO高效核心模块
from application import App
import config
if __name__ == '__main__':
app = App()
app.listen(config.options["port"]) # 绑定端口
print('端口:[ %s ] 服务已经启动...' % (config.options["port"]))
tornado.ioloop.IOLoop.current().start() # 开始监听端口
tornado框架解析-application路由层
- 路由传参1
application路由层
from config import settings
class App(Application):
def __init__(self):
url = [
(r'/', IndexHandler,{"height":180}), # 路由匹配试图函数
]
super(App, self).__init__(url, **settings)
views 试图
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def initialize(self,height): # 只能initialize函数能接收这种参数
print(height)
self.height = height
- 路由无名传参
application路由层
from tornado.web import Application
from config import settings
from views import index
# 配置路由
class App(Application):
def __init__(self):
url = [
(r'/detail/([0-9]+)/', index.DetailHandler), # 路由无名分组
]
super(App, self).__init__(url, **settings)
views视图层
import tornado.web
class DetailHandler(RequestHandler):
def get(self,id,*args, **kwargs):
print(id) # 接受无名传参
self.write(id) # 响应回去
- 路由有名传参
application路由层
from tornado.web import Application
from config import settings
from views import index
# 配置路由
class App(Application):
def __init__(self):
url = [
(r'/detail/(?P<id>[0-9]+)/', index.DetailHandler), # 路由有名分组
]
super(App, self).__init__(url, **settings)
views视图层
import tornado.web
class DetailHandler(RequestHandler):
def get(self,id,*args, **kwargs):
print(id) # 接受有名传参
self.write(id) # 响应回去
- 路由反向解析
application路由层
from tornado.web import Application
from config import settings
from views import index
# 配置路由
class App(Application):
def __init__(self):
url = [
tornado.web.url(r'/index/', IndexHandler, name='index'), # 路由反向解析
]
super(App, self).__init__(url, **settings)
views视图层
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self,*args,**kwargs):
url = self.reverse_url("index") # 反向解析路由
self.write('ok')
数据交互(获取get请求值)
- template模板
<form action="/index/" method="get">
username : <input type="text" name="name">
<input type="submit" value="提交">
</form>
- views视图
import tornado.web
class Index(RequestHandler):
def get(self,*args, **kwargs):
get_query_argument(name, strip)
# 返回单个值 name:地址栏传过来的参数 strip:是否取出空格
# get_query_arguments(name) # 前台复选框传过来多个值,用这个接收
self.write("ok") # 响应回去
数据交互(获取post请求值)
- template模板
<form action="/dictinfo/" method="post">
# 单选框
username : <input type="text" name="name">
pwd : <input type="text" name="pwd">
# 复选框
hobby :
足球:<input type="checkbox" name="hobby" value="football">
篮球:<input type="checkbox" name="hobby" value="basketball">
钱:<input type="checkbox" name="hobby" value="money">
# 提交按钮
<input type="submit" value="提交">
</form>
- views视图
import tornado.web
class Index(RequestHandler):
def get(self,*args, **kwargs):
get_body_argument(name, strip)
# 返回单个值 name:地址栏传过来的参数 strip:是否去除空格
# get_body_arguments(name, strip) # 前台复选框传过来多个值,用这个接收
self.write("ok") # 响应回去
数据交互(get与post请求值都能获取) *********
- template模板
<form action="/dictinfo/" method="post">
# 单选框
username : <input type="text" name="name">
pwd : <input type="text" name="pwd">
# 复选框
hobby :
足球:<input type="checkbox" name="hobby" value="football">
篮球:<input type="checkbox" name="hobby" value="basketball">
钱:<input type="checkbox" name="hobby" value="money">
# 提交按钮
<input type="submit" value="提交">
</form>
- views视图
import tornado.web
class Index(RequestHandler):
def get(self,*args, **kwargs):
self.get_argument(name, strip) # 获取单个值
# self.get_arguments(name, strip) # 获取复选框传过来的值
self.write('ok')
request属性详解
- views视图层
import tornado.web
class Index(RequestHandler):
def get(self,*args, **kwargs):
print(self.request) # request属性详解
self.write('ok')
request属性详解:
protocol='http' : 协议
host='127.0.0.1:9992' : 主机地址 (******************)
method='GET' : 请求方式
uri='/fangshi/' : 请求具体的路由文件
version='HTTP/1.1' :协议的版本
remote_ip='127.0.0.1' : 客户端ip
tornado框架-响应信息详解
响应信息-返回字符串str
方式1
import tornado.web
class Index(RequestHandler):
def get(self,*args, **kwargs):
self.write('ok') # 相当于django的HttpResponse响应
方式2
import tornado.web
import json
class Index(RequestHandler):
def get(self,*args, **kwargs):
info = {
"name" : 'lxxx',
"age" : 18
}
self.write(info) # 返回字符串
响应信息-返回json数据
import tornado.web
import json
class Index(RequestHandler):
def get(self,*args, **kwargs):
info = {
"name" : 'lxxx',
"age" : 18
}
infoStr = json.dumps(info) # 序列化
self.write(infoStr) # 返回json数据
响应的所有信息
import tornado.web
class Index(RequestHandler):
def get(self,*args, **kwargs):
self.finish()
# 作用和write一样, 也是将数据刷新到客户端浏览器中. 关闭当前的连接通道不能再finish
# 因为连接已经关闭了,所以下面不能在继续进行write数据了
self.set_header(name, value) # 设置响应头header头信息 (*****)
# 验证场景:设置token的时候用到
self.set_status(code, reason) # 设置响应的状态码以及状态的原因
self.redirect(url) # 重定向到指定的url (*****)
self.write('ok') # 返回字符串或json数据
self.render('test.html', li=li, d = d, name=name) # 渲染模板信息
tornado框架-上传下载文件-设置响应状态码
- template模板层
<form action="/wenjian/" method="post" enctype="multipart/form-data">
<input type="file" name="myfile">
<input type="file" name="myfile">
<input type="file" name="myimg">
<input type="submit" value="上传">
</form>
- views视图层
class WenjianHandler(RequestHandler):
def get(self):
self.render('uploadfile.html') # 返回上传页面
# self.set_status(999) # 设置响应状态码
def post(self, *args, **kwargs):
filesDict = self.request.files # 接收文件数据
print(filesDict) # 打印接收到文件数据
# 下载文件
for key, val in filesDict.items():
for filesinfo in val:
filename = filesinfo['filename']
uppath = os.path.join(BASE_DIR, 'upfile', filename)
with open(uppath, 'wb') as fp:
fp.write(filesinfo['body'])
传过来的多个文件信息数据:
{
# 传过来的文本信息数据
'myfile': [
{
'filename': 'a.txt', 'body': b'aaaaaaaaaaaaa', 'content_type': 'text/plain'
},
{
'filename': 'b.txt', 'body': b'dsadsadsadsadsadsadsa',
'content_type': 'text/plain'
}
],
# 传过来的图片信息数据
'myimg': [
{
'filename': 'c.png', 'body': b'\x89PNG\r\n\x1a\n\',
'content_type': 'image/png'
}
]
}
tornado框架-模板层(跟Django类似)
- views视图层
import tornado.web
class Index(RequestHandler):
def get(self,*args, **kwargs):
self.render('test.html', li=li, d = d, name=name) # 渲染模板信息
模板语法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>index</h1>
<p>{{name}}</p>
<!--显示传入参数值:-->
{{name}}
<!--显示传入的列表值-->
{% for item in li %}
{{item}}
{% end %}
<!--显示传入的字典值:-->
{% for key, val in d.items() %}
{{key}} --- {{val}}
{% end %}
<!--收尾跟Django有点不一样-->
</body>
</html>
- 模板判断语法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% if condition %}
...
{% elif condition %}
...
{% else %}
...
{% end %}
</body>
</html>
静态文件引入(css/js引入等)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Title</title>
<!--静态文件引入,推荐使用这种-->
<link rel="stylesheet" href="{{static_url('css/index.css')}}">
<!--静态文件引入,不推荐使用-->
<link rel="stylesheet" href="/static/index.css">
</head>
<body>
...
</body>
</html>
母版继承 与 引入html片段
- 母版
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Title</title>
<!--留下模块等待子版填充-->
{% block css %}
{% end %}
</head>
<body>
<div class="pg-header">
<!--引入html片段-->
{% include 'comm.html' %}
</div>
</body>
</html>
- 子版 (子版继承母版)
<!--引入html片段-->
{% extends 'layout.html' %}
{% block css %}
<link href="{{static_url('css/my.css')}}" rel="stylesheet" />
{% end %}
include导入html片段
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Title</title>
</head>
<body>
<div class="pg-header">
<!--引入html片段-->
{% include 'comm.html' %}
</div>
</body>
</html>
tornado框架-models(自定义ORM)
参考地址:Django ORM https://gitee.com/lupython/CaoZuomysqlShuJuKuDeLei/tree/master
tornado框架-安全策略
防范csrf攻击
# csrf攻击:CSRF攻击者在用户已经登录目标网站之后,诱使用户访问一个攻击页面,利用目标网站对用户的信任,以用户身份在攻击页面对目标网站发起伪造用户操作的请求,达到攻击目的。
# 防范方法:
1. 尽量使用POST,限制GET
2. 浏览器Cookie策略
3. 加验证码
4. Referer Check
5. Anti CSRF Token (携带随机字符串)
# template模板层携带随机Token
<form action="/new_message" method="post">
{{ xsrf_form_html() }} # 显示随机Token
{% module xsrf_form_html() %} # 隐藏随机Token
<input type="text" name="message"/>
<input type="submit" value="Post"/>
</form>
# 在配置信息开启csrf验证
### 默认配置
settings = {
"static_path" : os.path.join(BASE_DIR, 'static'), # 静态路径配置
"template_path" : os.path.join(BASE_DIR, 'template'), # 模板路径配置
"debug" : True, # 开启开发测试环境
"xsrf_cookies" : True # 开启csrf验证
}
防范XSS攻击
# XSS: 跨站脚本攻击,指恶意攻击者利用网站,没有对用户提交数据进行转义处理或者过滤不足的缺点,进而添加一些代码,嵌入到web页面中去。使别的用户访问都会执行相应的嵌入代码。
# 防范方法:不信任任何客户端提交的数据,只要是客户端提交的数据就应该先进行相应的过滤处理然后方可进行下一步的操作。
# 前台渲染用:safe 模板语言能解析标签
{{ article_obj.content|safe }} # 模板语法解析标签
# 后台处理客户端提交的数据
soup = BeautifulSoup(content,"html.parser") # 防xss攻击,解析标签
all_tag = soup.find_all()
for i in all_tag:
if i.name == "script":
i.decompose() # 删除script标签
sql注入问题
# 第一种采用预编译语句集,它内置了处理SQL注入的能力,只要使用它的setString方法传值即可。
# 第二种是采用正则表达式将包含有 单引号('),分号(;) 和 注释符号(--)的语句给替换掉来防止SQL注入。
sql = "select {filed} from {table}".format(table='db01', filed='name')
cursor.execute(sql) # 采用内置处理sql注入的能力
tornado框架-异步处理请求
异步处理请求方式1 -回调方式获取异步处理结果
from tornado.httpclient import AsyncHTTPClient
class StudentHandler(RequestHandler):
# 回调函数
def on_response(self, response):
# print(response)
if response.error: # 执行错误情况下
self.send_error(500)
else: # 执行正确的情况下
res = json.loads(response.body)
self.write(res)
self.finish() # 执行完关闭连接
# 请求函数
@tornado.web.asynchronous # 保持本次连接通信不关闭
def get(self, *args, **kwargs):
url = "http://xxx.com/login.html"
client = AsyncHTTPClient() # 异步客户端
client.fetch(url, self.on_response) # 发起异步请求,执行回调函数
异步处理请求方式1 -协程方式获取异步处理结果
class ClassesHandler(RequestHandler):
@tornado.gen.coroutine
def get(self, *args, **kwargs):
url = "http://xxx.com/login.html"
client = AsyncHTTPClient() # 异步客户端
res = yield client.fetch(url) # 发起异步请求,执行回调函数
if res.error: # 执行错误情况下
self.send_error(500)
else: # 执行正确的情况下
ret = json.loads(res.body)
self.write(ret)
self.finish() # 执行完关闭连接
tornado框架-websocket应用
- template模板层
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Title</title>
</head>
<body>
<div id="content" style="width: 500px; height: 400px;overflow: auto "></div>
<input type="text" id="msg">
<input type="button" onclick="sendMsg()" value="发送">
<script
src="http://code.jquery.com/jquery-1.12.4.min.js"
integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ="
crossorigin="anonymous">
</script>
<script>
var ws = new WebSocket('ws://192.168.13.54:8086/chat/');
// ws.onopen : 连接建立成功后,调用
// ws.onopen = function (e) {...};
// ws.onmessage: 获取服务端发送过来的数据
ws.onmessage = function (e) {
$('#content').append("<p>"+e.data+"</p>");
}
// ws.send : 向服务端发送数据
function sendMsg(){
var msg = $('#msg').val();
ws.send(msg);
$('#msg').val("");
}
</script>
</body>
</html>
- views层
class ChatHandler(WebSocketHandler):
users = []
def open(self, *args, **kwargs): # 每次新建立一个连接触发open函数的执行
self.users.append(self)
for user in self.users:
print(user)
user.write_message('%s登陆了'%(self.request.remote_ip)) # 获取连接者的ip
def on_close(self): # 关闭客户端连接
self.users.remove(self)
for user in self.users:
user.write_message('%s退出了'%(self.request.remote_ip))
def on_message(self, message): # 客户端向服务端发消息
for user in self.users:
user.write_message('%s说了: %s' % (self.request.remote_ip, message))
def close(self, code=None, reason=None): # 关闭socket连接
pass