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对象,所有定义的变量都会变成其属性
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语法配置值即可
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解释:

  1. debug,设置为True,调试模式,自动重启服务器/取消缓存模板/静态文件hash/提供追踪信息,但会因代码错误而停止,一个debug=True相当于autoreload=True, compiled_template_cache=False, static_hash_cache=False, serve_traceback=True;
  2. static_path,设置静态文件目录;
  3. template_path,设置模板文件目录;
  4. autoescape=None,关闭项目文档自动转义;
  5. 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。

模板

  1. 配置模板路径,config.py中配置settings的template_path,在Application方法中调用

  2. 渲染并返回给客户端,render(template_name, args=args)

  3. 模板语法,字典不支持点语法,基本语法

  4. 函数,{{ static_url('css/style.css') }},静态文件目录拼接,利用static_path与提供的地址进行拼接,并创建基于文件内容的hash值,保证每次加载的都是最新文件

  5. 转义,关闭自动转义{% raw string %},{% autoescape None %},配置中"autoescape":None,escape()函数开启转义

  6. 继承,{% block main %}{% end %},{% enxtends "base.html" %}{% block main %}{% end %}

  7. 静态文件,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)方法,验证逻辑写在该方法内

同步与异步

同步就是按顺序执行程序,前一个阻塞了,就不会执行到后一个;异步则是遇到阻塞将程序交给内核处理,直接执行下去,如果阻塞操作结束有返回结果,再获取其结果

回调函数实现异步

回调函数其实就是在任务最后执行的函数,因为函数可以作为参数传入,因此可以将阻塞操作封装到函数里作为回调函数传给任务,而这个任务可以调用子线程执行,这样就不会造成同步阻塞。

协程实现异步

  1. 使用yield生成器,yield阻塞操作函数,阻塞函数中向生成器send数据,主函数中调用next()运行生成器即可,注意捕捉StopIteration错误,会用到全局变量用于在阻塞操作中调用

  2. 1中要当作生成器处理,所以不可避免代码较多不能像普通函数调用,因此改进版用装饰器封装

  3. 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)的更多相关文章

  1. 五、WEB框架基础(1)

    框架与架构 Python语言有很多web框架,主要是四个,企业级框架Django,高并发处理框架Tornado,快速开发框架Flask,自定义协议框架Twisted. 全栈网络框架封装了网络通信/线程 ...

  2. Python-S9-Day115——Flask Web框架基础

    01 今日内容概要 02 内容回顾 03 Flask框架:配置文件导入原理 04 Flask框架:配置文件使用 05 Flask框架:路由系统 06 Flask框架:请求和响应相关 07 示例:学生管 ...

  3. python 学习笔记十五 web框架

    python Web程序 众所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. Python的WEB框架分为两类: 自己写socket,自 ...

  4. 五. web开发基础

    一.HTML 二.CSS 三.JavaScript 四.web框架 1.web框架本质 众所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端 ...

  5. WEB框架概述(译)

    在学习WEB框架之前,我个人觉得需要搞清楚一件事:什么是WEB框架?在网上找了很多资料,觉得什么是WEB框架这篇文章讲的比较全面而清晰,本文作者Jeff Knupp. 全文如下: Web 应用框架,或 ...

  6. 【译】什么是 web 框架?

    Web 应用框架,或者简单的说是“Web 框架”,其实是建立 web 应用的一种方式.从简单的博客系统到复杂的富 AJAX 应用,web 上每个页面都是通过写代码来生成的.我发现很多人都热衷于学习 w ...

  7. WEB框架-Django框架学习-预备知识

    今日份整理,终于开始整个阶段学习的后期了,今日开始学习Django的框架,加油,你是最胖的! 1.web基础知识 1.1 web应用 Web应用程序是一种可以通过Web访问的应用程序,程序的最大好处是 ...

  8. Python开发【第十五篇】:Web框架之Tornado

    概述 Tornado 是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本.这个 Web 框架看起来有些像web.py 或者 Google 的 webapp,不过为了 ...

  9. Python开发【第十八篇】Web框架之Django【基础篇】

    一.简介 Python下有许多款不同的 Web 框架,Django 是重量级选手中最有代表性的一位,许多成功的网站和APP都基于 Django. Django 是一个开放源代码的Web应用框架,由 P ...

随机推荐

  1. bzoj 1758 [Wc2010]重建计划 分数规划+树分治单调队列check

    [Wc2010]重建计划 Time Limit: 40 Sec  Memory Limit: 162 MBSubmit: 4345  Solved: 1054[Submit][Status][Disc ...

  2. Java防止SQL注入的途径介绍

    为了防止SQL注入,最简洁的办法是杜绝SQL拼接,SQL注入攻击能得逞是因为在原有SQL语句中加入了新的逻辑,如果使用PreparedStatement来代替Statement来执行SQL语句,其后只 ...

  3. HDU1071 The area

    Ignatius bought a land last week, but he didn't know the area of the land because the land is enclos ...

  4. webconfig连接串的使用与用代码写连接串的使用

    原文发布时间为:2008-07-25 -- 来源于本人的百度文章 [由搬家工具导入] 1、使用web.config中设置连接串 在web.config中<configuration>... ...

  5. How to build and run ARM Linux on QEMU from scratch

    This blog shows how to run ARM Linux on QEMU! This can be used as a base for later projects using th ...

  6. 第5章-unix网络编程 TCP/服务端程序示例

    这一章主要是完成一个完整的tcp客户/服务器程序.通过一很简单的例子.弄清客户和服务器如何启动,如何终止,发生了某些错误会发生什么.这些事很重要的  客户端代码 #include "unp. ...

  7. Day 20 迭代器、生成器

    一. 迭代器 1.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退) 2.可迭代对象:实 ...

  8. JS快速上手-基础Javascript

    1.1背景 1.1.1 ECMAScript与javascript ECMAScript是javascript的官方命名.因为java已经是一个商标.如今,一些早前收到过授权的公司,如Moailla, ...

  9. python笔记4:高级特性

    4 高级特性 4.1  切片 [:] *注:-- list和tuple,字符串都支持切片 4.2 迭代 Iteration for ... in 及 for ... in if 两个变量迭代时, 例1 ...

  10. Anaconda环境搭建

    最近要使用Anaconda做一些机器视觉相关的开发,在此记录下Anaconda的搭建 首先去官网下载 这里我选择windows平台 由于浏览器自己下载的过慢,我这里选择用迅雷下载 没python就把那 ...