五、Web框架基础(2)
Tornado
异步协程编程。(其实是异步IO而非真正的异步,从内核拷贝到用户空间的过程还是同步的)
适合用户量大、高并发,如抢票、网页游戏、在线聊天等场景;或大量HTTP持久连接,通过单TCP持久连接,HTTP1.1默认持久连接。
用于解决高性能需求,C10K问题,注重性能,走少而精的方向。
特点是:HTTP服务器、异步编程、WebSockets。
安装测试
import tornado.web # web模块
import tornado.ioloop # io循环模块
class MainHandler(tornado.web.RequestHandler): # 路由类
def get(self, *args, **kwargs): # 处理get请求
self.write('Hello, world!')
def main():
app = tornado.web.Application([ # web框架的核心应用类,与服务器对应的接口,是服务器的实例
('/', MainHandler),
])
app.listen(8000) # 监听端口
tornado.ioloop.IOLoop.instance().start() # 启动服务器,instance()获取IOLoop实例
if __name__ == '__main__':
main()
高性能原理
IOLoop与epoll交互,epoll管理socket请求,当有socket建立时,epoll开始监听这个socket,当有数据发送过来时,epoll通知IOLoop将数据发送到Application的路由表,路由转发到指定Handler处理并返回到对应socket。
HTTP服务器
import tornado.httpserver
httpServer = tornado.httpserver.HTTPServer(app) # 创建服务器对象实例
httpServer.listen(8000)
多进程
tornado默认是单进程。
httpServer.bind(8000) # 将服务器绑定到指定端口而不是监听
httpServer.start([unit]) # 多进程启动,默认是1,小于等于0时开启CPU核心数个进程
区别
app.listen()只能在单进程中使用,虽然提供了多进程,仍存在问题不推荐使用上述方法,应该手动启动多进程。
问题1:子进程会复制IOLoop实例,修改IOLoop会影响所有子进程
问题2:所有进程都是由一个命令启动,无法在不停止服务的情况下修改代码
问题3:所有进程共享一个端口,无法单独监控不同进程
开发过程还是用单线程。
OPTIONS
把端口当参数传入,使用tornado.options模块,全局参数的定义、存储、转换。
- 方法、属性:
- define(name, default=None, type=None, help=None, metavar=None, multiple=False, group=None, callback=None)
- name:变量名,必须唯一
- default:默认值
- type:变量类型,从命令行或文件导入参数时tornado自动转换,没有type会根据default类型转换
- help:用作提示信息
- multiple:接收多个值,默认False
- options
- 全局options对象,所有定义的变量都会变成其属性
- define(name, default=None, type=None, help=None, metavar=None, multiple=False, group=None, callback=None)
from tornado.options import define, options
define('port', default=8000, type=int)
app.listen(options.port)
- 获取参数:
- tornado.options.parse_command_line()
- 接收命令行参数,保存到options对象中
- tornado.options.parse_config_file(path)
- 从文件导入参数
- 任意文件,用python语法配置值即可
- tornado.options.parse_command_line()
from tornado.options import parse_command_line()
python3 test.py --port=8000 # 多个值用逗号分隔
from tornado.options import parse_config_file(path)
用python模块文件,可以直接导入对应参数,免去define和parse。
使用parse方法时,默认打开logging功能,向屏幕输出信息,第一行options.logging=None可以关闭日志或命令行--logging=none
配置参数
对创建app的路由参数进行解耦,移植到application.py文件中,通过继承Application类来重写handlers并配置参数。
from views.index import IndexHandler, HomeHandler
import tornado.web
import config
class Application(tornado.web.Application):
def __init__(self):
handlers =[
('/', IndexHandler),
('/home', HomeHandler),
]
super(Application, self).__init__(handlers, **config.settings)
settings可设置的参数
常见的settings解释:
- debug,设置为True,调试模式,自动重启服务器/取消缓存模板/静态文件hash/提供追踪信息,但会因代码错误而停止,一个debug=True相当于autoreload=True, compiled_template_cache=False, static_hash_cache=False, serve_traceback=True;
- static_path,设置静态文件目录;
- template_path,设置模板文件目录;
- autoescape=None,关闭项目文档自动转义;
- cookie_secret,cookie安全密钥;
为Handler传参用字典,调用使用initialize(),并且该方法总在HTTP方法前执行
('/home', HomeHandler, {'args1':'hello', 'args2':'world')
class HomeHandler(tornado.web.RequestHandler):
def initialize(self, **kwargs): # 接收路由的参数,在执行请求方法之前调用
self.word1 = kwargs.get('args1')
self.word2 = kwargs.get('args2')
数据
RequestHandler的write方法会自动将字典类型数据转为json字串,并设置Content-type为Application/json,如果手动转换,还需要通过self.set_header(name, value)设置Content-type头,否则为text/html。
对响应头的修改可以通过重载set_default_headers()方法来设置,使其在HTTP方法之前执行。
对响应设置状态码通过self.set_status(code, reason=None)设置。
重定向self.redirect(url)。
抛出错误self.send_error(code, **kwargs)
处理错误write_error(self, code, **kwargs)并返回对应界面,在HTTP方法抛出错误之后执行。
反向解析self.reverse_url(name),name由tornado.web.url(path, handler, **kwargs, name)中提供。
tornado.web.Application([
('/', MainHandler),
tornado.web.url('/test', TestHandler, name='testing'),
])
class IndexHandler(RequestHandler):
def get(self):
url = self.reverse_url('testing')
return self.write('<a href='{}'>test</a>'.format(url))
class TestHandler(RequestHandler):
def get(self):
return self.write('test')
RequestHandler
提取url特定部分,GET,POST,在头部增加自定义字段
提取特定部分,在路由路径中/test/(?P<h1>\w+)/(?P<h2>\w+)/(?P<h3>\w+)
- 获取get/post参数
self.get_argument(name, default=ARG_DEFAULT, strip=True)/self.get_arguments(name, strip=True)
- name是字段名,default设置默认值,strip去空格,前者返回单字段,后者返回列表,同名获取最后一个值
渲染页面self.render(template_name)
增加自定义字段,self.add_header(name, value)
self.request对象
- 属性
- method:HTTP请求方法
- host:被请求的主机名
- uri:请求的完整资源地址,包括路径和参数
- version:使用的HTTP版本
- headers:请求头部分,字典
- body:请求体数据
- remote_ip:客户端IP
- files:用户上传的文件,字典
tornado.httputil.HTTPFile对象
上传文件的时候使用,是接收到的文件对象,就是self.request.files内的列表。
- 属性
- filename,文件名称
- body,文件的数据实体
- content_type,文件类型
响应输出
self.write(body),数据写到缓冲区
self.finish(),刷新缓冲区,关闭当次请求通道,之后的write无效
RequestHandler可重载方法
initialize(),参数初始化,一般用于接收路由参数
prepare(),请求处理前调用
http,请求
set_default_headers(),配置默认headers
write_error(),配合self.send_error()方法自定义错误页面
on_finish() ,请求处理结束后调用,常用于内存资源释放或日志记录
执行顺序,无错误:headers/initialize/prepare/http/on_finish,错误:headers/initialize/prepare/http/headers/errors/on_finish。
模板
配置模板路径,config.py中配置settings的template_path,在Application方法中调用
渲染并返回给客户端,render(template_name, args=args)
模板语法,字典不支持点语法,基本语法
函数,{{ static_url('css/style.css') }},静态文件目录拼接,利用static_path与提供的地址进行拼接,并创建基于文件内容的hash值,保证每次加载的都是最新文件
转义,关闭自动转义{% raw string %},{% autoescape None %},配置中"autoescape":None,escape()函数开启转义
继承,{% block main %}{% end %},{% enxtends "base.html" %}{% block main %}{% end %}
静态文件,StaticFileHandler映射静态文件,该类由tornado.web提供,在路由中直接使用,传入path参数和default_filename参数,(r'/', StaticFileHandler,
数据库
Tornado没有自带的ORM,数据库需要自行选择框架和驱动去适配。
常见的peewee和sqlalchemy都可以,但sqla的学习曲线比较长。
安全
- set_cookie(name, value, domain=None, expires=None, path='/', expires_days=None, **kwargs)
- name:cookie名
- value:值
- domain:提交cookie的匹配的域名
- path:提交匹配的路径
- expires:cookie有效期,可以是时间戳证书、时间元组、datetime类型,是UTC时间
- expires_days:同样有效期,天数,优先级低于expires
- get_cookie(name, default=None)
- name:获取的cookie名
- default:如果指定name的cookie不存在,返回默认值
- clear_cookie(name, path='/', domain=None)
- name:指定要删除的cookie名
- path:指定匹配的路径
- domain:指定匹配的域名
clear_all_cookies(path=‘/’, domain=None)
- set_secure_cookie(name, value, expires_days=30, version=None, **kwargs)
- Application配置密钥cookie_secret=‘xxxxx'
- get_secure_cookie(name, value=None, max_age_days=31, min_version=None)
- value:验证不通过的返回值
- max_age_days:过滤安全cookie的时间戳
- xsrf保护,同源策略,针对POST请求,也不是绝对安全的
- xsrf_cookies=True
- 模板中应用
- {% module xsrf_form_html() %}
- 为浏览器设置_xsrf的安全cookie,浏览器关闭后失效
- 为模板表单添加了隐藏域
- 非模板应用
- self.xsrf_token:手动设置_xsrf的cookie,浏览器可以获取对应token;手动添加隐藏域:为表单生成隐藏域,值通过js脚本从cookie中获取
- Ajax请求,$.ajax({url:xxx, method:xxx, data:xxx, callback:function(data) {}, headers:{'X-XSRFToken':getCookie('_xsrf')}})
- 一般在进入主页时提供xsrf的cookie即可,后续只需验证
用户验证
- tornado.web.authenticated装饰器
- 用法,包装http方法
- 验证,RequestHandler重载get_current_user(self)方法,验证逻辑写在该方法内
同步与异步
同步就是按顺序执行程序,前一个阻塞了,就不会执行到后一个;异步则是遇到阻塞将程序交给内核处理,直接执行下去,如果阻塞操作结束有返回结果,再获取其结果
回调函数实现异步
回调函数其实就是在任务最后执行的函数,因为函数可以作为参数传入,因此可以将阻塞操作封装到函数里作为回调函数传给任务,而这个任务可以调用子线程执行,这样就不会造成同步阻塞。
协程实现异步
使用yield生成器,yield阻塞操作函数,阻塞函数中向生成器send数据,主函数中调用next()运行生成器即可,注意捕捉StopIteration错误,会用到全局变量用于在阻塞操作中调用
1中要当作生成器处理,所以不可避免代码较多不能像普通函数调用,因此改进版用装饰器封装
2中依然存在全局变量,对任务函数和阻塞操作函数都作为生成器处理,多线程参数为阻塞操作函数生成器,next()该生成器,并将结果send到任务函数生成器中,注意捕捉错误
# 例1
gen = None
def blocking_task(time):
def run():
time.sleep(time)
try:
global gen
gen.send('Worked down')
except StopIteration as e:
pass
threading.Thread(target=run).start()
def worker1(time):
print('Start work1')
res = yield blocking_task(time)
print(res)
print('End work1')
def worker2(time):
print('Start work2')
time.sleep(time)
print('End work2')
def main():
global gen
gen = worker1(5)
next(gen)
worker2(2)
# 例2
def blocking_task(time):
def run():
time.sleep(time)
try:
global gen
gen.send('Worked down')
except StopIteration as e:
pass
threading.Thread(target=run).start()
def gen_decorator(f):
def wrap(*args, **kwargs):
global gen
f(*args, **kwargs)
next(f)
return wrap
@gen_decorator
def worker1(time):
print('Start work1')
res = yield blocking_task(time)
print(res)
print('End work1')
def worker2(time):
print('Start work2')
time.sleep(time)
print('End work2')
def main():
worker1(5)
worker2(2)
# 例3
def blocking_task(time):
time.sleep(time)
yield 'Worked Down'
def gen_decorator(f):
def wrap(*args, **kwargs):
gen1 = worker1(*args, **kwargs)
gen2 = next(gen1)
def run(g):
res = next(g)
try:
gen1.send(res)
except StopIteration as e:
pass
threading.Thread(target=run, args=(gen2,)).start()
return wrap
@gen_decorator
def worker1(time):
print('Start work1')
res = yield blocking_task(time)
print(res)
print('End work1')
def worker2(time):
print('Start work2')
time.sleep(time)
print('End work2')
def main():
worker1(5)
worker2(2)
Tornado的异步
epoll用于解决网络IO并发问题,Tornado的异步基于epoll。
- tornado.httpclient.AsyncHTTPClient,这是Tornado提供的异步Web请求客户端,用来运行异步Web请求
- fetch(request, callback=None),用于执行一个Web请求,并异步响应返回一个tornado.httpclient.HTTPResponse,request可以是一个url,或一个tornado.httpclient.HTTPRequest对象,url自动转换为对象
- HTTPResponse,响应类
- code,状态码
- reason,状态描述
- body,响应数据
- error,异常
- HTTPRequest,请求类,构造函数可以接收参数
- url,字串类型,目的网址
- method,字串类型,请求方法
- headers,字典类型或HTTPHeaders类型,请求头
- body,HTTP请求体
tornado.web.asynchronous,异步回调装饰器,不关闭通信通道
tornado.gen.coroutine,异步协程装饰器
# 回调异步
import json
class TestHandler(RequestHandler):
def on_response(self, response):
if response.error:
self.write_error(500)
else:
self.finish(json.loads(response.body))
@tornado.web.asynchronous
def get(self, *args, **kwargs):
url = 'xxxx'
client = AsyncHTTPClient()
client.fetch(url, self.on_response)
# 协程异步
class TestHandler(RequestHandler):
@tornado.gen.coroutine
def get(self, *args, **kwargs):
url = 'xxxx'
client = AsyncHTTPClient()
res = client.fetch(url)
if res.error:
self.write_error(500)
else:
self.write(json.loads(res.body))
# 协程异步改进,异步客户端独立出来
class TestHandler(RequestHandler):
@tornado.gen.coroutine
def get(self, *args, **kwargs):
res = yield get_data()
self.write(res)
@tornado.gen.coroutine
def get_data(self):
url = 'xxx'
client = AsyncHTTPClient()
res = yield client.fetch(url)
if res.error:
data = {'res':0}
else:
data = json.loads(res.body)
raise tornado.gen.Return(data) # 相当于yield中的send方法
Websockets
WebSocket是HTML5规范中提出的新的B/S通信协议,该协议使用新的协议头ws://url
。
WebSocket是独立的创建在TCP协议之上的协议,和HTTP唯一的关系是使用了HTTP协议的101状态码。
WebSocket使客户端与服务端之间的数据交互变得更加简单,允许服务器直接向客户端推送数据。
大多数浏览器都已经支持WebSocket。
Tornado的WebSocket模块
- tornado.websocket.WebSocketHandler用于处理通信
- open() 有新连接时被调用,一般用于记录用户身份并初始化信息
- on_message(msg) 收到消息时调用
- on_close() 断开连接时调用,一般用于清理内存
- write_message(msg, binary=False) 用于主动向客户端发送消息,可以是字串或字典(自动json字串),如果binary为False,msg以utf-8编码,如果为True,发送字节码
- close() 服务端主动断开连接
- check_origin(origin) 判断源origin,对于符合条件的请求源允许连接
<div>
<input type='text' id='message' />
<button onclick='sendMessage()'>Send</button>
<button onclick='exsitsPage()'>Exit</button>
</div>
<script>
// 建立WebSocket连接
var ws = new WebSocket("ws://ip:port/chat")
// 接收服务器发来的数据
ws.onmessage = function(e) {
var obj = querySelector('div')
data = "<p> %s </p>" % e.data
document.insertAdjacentHTML('beforeBegin', data)
}
// 向服务器发送数据
function sendMessage() {
var message = document.querySelector('#message').data
ws.send(message)
document.querySelector('#message').data = ''
}
// 主动关闭连接,关闭浏览器会自动断开连接
function exsitsPage(ws) {
ws.close()
}
</script>
import tornado.websocket
class ChatHandler(tornado.websocket.WebSocketHandler):
users = list() # 用于存储连接的用户信息
def open(self):
self.users.append(self)
for user in self.users:
user.write_message(u"[{}]进入聊天室".format(self.request.remote_ip))
def on_message(self, msg):
for user in self.users:
user.write_message(u"[{}]说:{}".format(self.request.remote_ip, msg))
def on_close(self):
pass
def check_origin(self, origin):
return True
五、Web框架基础(2)的更多相关文章
- 五、WEB框架基础(1)
框架与架构 Python语言有很多web框架,主要是四个,企业级框架Django,高并发处理框架Tornado,快速开发框架Flask,自定义协议框架Twisted. 全栈网络框架封装了网络通信/线程 ...
- Python-S9-Day115——Flask Web框架基础
01 今日内容概要 02 内容回顾 03 Flask框架:配置文件导入原理 04 Flask框架:配置文件使用 05 Flask框架:路由系统 06 Flask框架:请求和响应相关 07 示例:学生管 ...
- python 学习笔记十五 web框架
python Web程序 众所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. Python的WEB框架分为两类: 自己写socket,自 ...
- 五. web开发基础
一.HTML 二.CSS 三.JavaScript 四.web框架 1.web框架本质 众所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端 ...
- WEB框架概述(译)
在学习WEB框架之前,我个人觉得需要搞清楚一件事:什么是WEB框架?在网上找了很多资料,觉得什么是WEB框架这篇文章讲的比较全面而清晰,本文作者Jeff Knupp. 全文如下: Web 应用框架,或 ...
- 【译】什么是 web 框架?
Web 应用框架,或者简单的说是“Web 框架”,其实是建立 web 应用的一种方式.从简单的博客系统到复杂的富 AJAX 应用,web 上每个页面都是通过写代码来生成的.我发现很多人都热衷于学习 w ...
- WEB框架-Django框架学习-预备知识
今日份整理,终于开始整个阶段学习的后期了,今日开始学习Django的框架,加油,你是最胖的! 1.web基础知识 1.1 web应用 Web应用程序是一种可以通过Web访问的应用程序,程序的最大好处是 ...
- Python开发【第十五篇】:Web框架之Tornado
概述 Tornado 是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本.这个 Web 框架看起来有些像web.py 或者 Google 的 webapp,不过为了 ...
- Python开发【第十八篇】Web框架之Django【基础篇】
一.简介 Python下有许多款不同的 Web 框架,Django 是重量级选手中最有代表性的一位,许多成功的网站和APP都基于 Django. Django 是一个开放源代码的Web应用框架,由 P ...
随机推荐
- 切糕(bzoj 3144)
Description Input 第一行是三个正整数P,Q,R,表示切糕的长P. 宽Q.高R.第二行有一个非负整数D,表示光滑性要求.接下来是R个P行Q列的矩阵,第z个 矩阵的第x行第y列是v(x, ...
- 如何在requirejs下引用bootstrap
原本以为只要require过来就能用 require(['jquery','underscore','bootstrap','cache'],function($,U,B,C){ 但发现会报错,类似未 ...
- hdu 4183(网络流)
Pahom on Water Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)To ...
- Java后端WebSocket的Tomcat实现 html5 WebSocket 实时聊天
WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功能,使服务端也能主动向客户端发送数据.Tomcat7.0.47上才能运行. 需要添加Tomcat里lib目 ...
- Image与Base64String的互转换
public Image Base64ToImage(string base64String) { // Convert Base64 String to byte[] byte[] imageByt ...
- Android之观察者/被观察者模式Observer/Observable
Android 本身也是有观察者模式的.虽然项目中很多需要通知数据改变的地方,用了EventBus,但是不得不说这个观察者模式还是很好用的.最近在开发新版本的时候引用了腾讯的IM,之前写直播的时候就用 ...
- tomcat7.0.55配置单向和双向HTTPS连接(二)
上一篇文章:tomcat7.0.55配置单向和双向HTTPS连接 只是简要的配置了一下HTTPS,还有许多问题没有解决,本篇来解决这些文件 首先按照这篇文章:Widows下利用OpenSSL生成证书来 ...
- Java Web工程连接MySQL数据库及Tomcat服务器页面中文乱码
Java Web工程连接MySQL数据库 一. 准备工作 1.下载连接MySQL数据库的JDBC (可以去官网下,也可以去百度云找) 2.将下载的jar文件复制到Tomcat的lib目录下 3.新建一 ...
- Ant -----ant标签和自定义任务
随便记一下 Ant的用法吧.ant ,maven, gradle ,三个打包工具到齐了,Ant 常见标签解析,ant 自定义task . <?xml version="1.0" ...
- 邁向IT專家成功之路的三十則鐵律 鐵律二十一:IT人用才之道-穿透
在以道德為基礎的企業主管之人,其最根本的能力除了須要有洞悉事物的敏捷思維之外,眼光還必要有像水柱般一樣的穿山引石之能,如此不僅能夠為企業找到適才之人,更能為企業的永續經營奠定有如泰山般的基石.只可惜大 ...