• Web框架之Tornado


    前言

    Tornado是使用Python编写的一个强大的、可扩展的Web服务器。它在处理严峻的网络流量时表现得足够强健,但却在创建和编写时有着足够的轻量级,并能够被用在大量的应用和工具中。

    Tornado是基于Bret Taylor和其他人员为FriendFeed所开发的网络服务框架,当FriendFeed被Facebook收购后得以开源。不同于那些最多只能达到10,000个并发连接的传统网络服务器,Tornado在设计之初就考虑到了性能因素,旨在解决C10K问题,这样的设计使得其成为一个拥有非常高性能的框架。此外,它还拥有处理安全性、用户验证、社交网络以及与外部服务(如数据库和网站API)进行异步交互的工具。

    Tornado所做的是能够快速简单地编写高速的Web应用。如果编写一个可扩展的社交应用、实时分析引擎,或RESTful API,那么简单而强大的Python,以及Tornado正是为你准备的!

    总之,Tornado也很强大!!!

    下载和安装

    # pip安装
    pip3 install tornado
     
    # 源码安装
    tar xvzf tornado-4.4.1.tar.gz
    cd tornado-4.4.1
    python setup.py build
    sudo python setup.py install

    源码下载:tornado-1.2.1.tar.gz、 tornado-4.4.1.tar.gz

    Tornado各模块

    # 主要模块
        web        - FriendFeed 使用的基础 Web 框架,包含了 Tornado 的大多数重要的功能
        escape     - XHTML, JSON, URL 的编码/解码方法
        database   - 对 MySQLdb 的简单封装,使其更容易使用
        template   - 基于 Python 的 web 模板系统
        httpclient - 非阻塞式 HTTP 客户端,它被设计用来和 web 及 httpserver 协同工作
        auth       - 第三方认证的实现(包括 Google、Facebook、Yahoo BBAuth、FriendFeed...)
        locale     - 针对本地化和翻译的支持
        options    - 命令行和配置文件解析工具,针对服务器环境做了优化
    
    # 底层模块
        httpserver - 服务于 web 模块的一个非常简单的 HTTP 服务器的实现
        iostream   - 对非阻塞式的 socket 的简单封装,以方便常用读写操作
        ioloop     - 核心的 I/O 循环

    Hello,world

    # 经典HelloWorld示例
    
    import tornado.ioloop
    import tornado.web
    
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.write("Hello, world")
    
    application = tornado.web.Application([
        (r"/", MainHandler),
    ])
    
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()

    路由系统

    路由系统其实就是 url 和 类 的对应关系,这里不同于其他框架,其他很多框架均是 url 对应 函数,Tornado中每个url对应的是一个类。

    Tornado中原生支持二级域名的路由,如:

    方法

    1、处理程序和参数

      请求来时,程序会用正则匹配相应路由地址,并交付于 tornado.web.RequestHandler 的子类处理;子类会根据请求方式(post / get / delete ...)的不同调用并执行相应的方法,方法返回字符串内容并发送到浏览器。

    self.write("<h1>Hello, World</h1>")    # html代码直接写在浏览器客户端
    self.render("index.html")              # 返回html文件,调用render_string(),内部其实是打开并读取文件,返回内容
    self.redirect("http://www.baidu.com",permanent=False) # 跳转重定向,参数代表是否永久重定向
    
    name = self.get_argument("name")       # 获取客户端传入的参数值
    name = self.get_arguments("name")      # 获取多个值,类别形式
    file = self.request.files["filename"]  # 获取客户端上传的文件
    
    raise tornado.web.HTTPError(403)       # 返回错误信息给客户端

    2、重写 RequestHandler 的方法函数

    对于一个请求的处理过程代码调用次序如下:

    1. 程序为每一个请求创建一个 RequestHandler 对象;
    2. 程序调用 initialize() 函数,这个函数的参数是 Application 配置中的关键字参数定义。(initialize 方法是 Tornado 1.1 中新添加的,旧版本中你需要重写 __init__ 以达到同样的目的) initialize 方法一般只是把传入的参数存到成员变量中,而不会产生一些输出或者调用像 send_error 之类的方法。
    3. 程序调用 prepare()。无论使用了哪种 HTTP 方法,prepare 都会被调用到,因此这个方法通常会被定义在一个基类中,然后在子类中重用。prepare可以产生输出信息。如果它调用了finish(或send_error` 等函数),那么整个处理流程就此结束。
    4. 程序调用某个 HTTP 方法:例如 get()post()put() 等。如果 URL 的正则表达式模式中有分组匹配,那么相关匹配会作为参数传入方法。

     重写 initialize() 函数(会在创建RequestHandler对象后调用):

    class ProfileHandler(tornado.web.RequestHandler):
    
        def initialize(self,database):
            self.database = database
    
        def get(self):
            self.write("result:" + self.database)
    
    application = tornado.web.Application([
        (r"/init", ProfileHandler, dict(database="database"))
    ])

    模板引擎

    Tornao中的模板语言和django中类似,模板引擎将模板文件载入内存,然后将数据嵌入其中,最终获取到一个完整的字符串,再将字符串返回给请求者。

    Tornado 的模板支持“控制语句”和“表达语句”,控制语句是使用 {% 和 %} 包起来的 例如 {% if len(items) > 2 %}。表达语句是使用 {{ 和 }} 包起来的,例如 {{ items[0] }}

    控制语句和对应的 Python 语句的格式基本完全相同。我们支持 ifforwhile 和 try,这些语句逻辑结束的位置需要用 {% end %} 做标记。还通过 extends 和 block 语句实现了模板继承。这些在 template 模块 的代码文档中有着详细的描述。

    注:在使用模板前需要在setting中设置模板路径:"template_path" : "tpl"

    1、基本使用

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
      
    import tornado.ioloop
    import tornado.web
      
      
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.render("index.html", list_info = [11,22,33])
      
    application = tornado.web.Application([
        (r"/index", MainHandler),
    ])
      
      
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    app.py
    <!DOCTYPE html>
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <title>老男孩</title>
        <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
    </head>
    <body>
    
        <div>
            <ul>
                {% for item in list_info %}
                    <li>{{item}}</li>
                {% end %}
            </ul>
        </div>
        
        <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>
        
    </body>
    </html>
    index.html
    在模板中默认提供了一些函数、字段、类以供模板使用:
    
    escape:         tornado.escape.xhtml_escape 的別名
    xhtml_escape:   tornado.escape.xhtml_escape 的別名
    url_escape:     tornado.escape.url_escape 的別名
    json_encode:    tornado.escape.json_encode 的別名
    squeeze:        tornado.escape.squeeze 的別名
    linkify:        tornado.escape.linkify 的別名
    datetime:       Python 的 datetime 模组
    handler:        当前的 RequestHandler 对象
    request:        handler.request 的別名
    current_user:   handler.current_user 的別名
    locale:         handler.locale 的別名
    _:              handler.locale.translate 的別名
    static_url:     for handler.static_url 的別名
    xsrf_form_html: handler.xsrf_form_html 的別名
    其它方法

    2、母版

    <!DOCTYPE html>
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <title>老男孩</title>
        <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
        {% block CSS %}{% end %}
    </head>
    <body>
    
        <div class="pg-header">
    
        </div>
        
        {% block RenderBody %}{% end %}
       
        <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>
        
        {% block JavaScript %}{% end %}
    </body>
    </html>
    layout.html
    {% extends 'layout.html'%}
    {% block CSS %}
        <link href="{{static_url("css/index.css")}}" rel="stylesheet" />
    {% end %}
    
    {% block RenderBody %}
        <h1>Index</h1>
    
        <ul>
        {%  for item in li %}
            <li>{{item}}</li>
        {% end %}
        </ul>
    
    {% end %}
    
    {% block JavaScript %}
        
    {% end %}
    index.html

    3、导入

    <div>
        <ul>
            <li>1024</li>
            <li>42区</li>
        </ul>
    </div>
    header.html
    <!DOCTYPE html>
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <title>老男孩</title>
        <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
    </head>
    <body>
    
        <div class="pg-header">
            {% include 'header.html' %}
        </div>
        
        <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>
        
    </body>
    </html>
    index.html

    4、自定义UIMethod以UIModule

    a,定义

    # uimethods.py
     
    def tab(self):
        return 'UIMethod'
    uimethods.py
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    from tornado.web import UIModule
    from tornado import escape
    
    class custom(UIModule):
    
        def render(self, *args, **kwargs):
            return escape.xhtml_escape('<h1>nick</h1>')
            #return escape.xhtml_escape('<h1>suoning</h1>')
    uimodule.py

    b,注册

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    import tornado.ioloop
    import tornado.web
    from tornado.escape import linkify
    import uimodules as md
    import uimethods as mt
    
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.render('index.html')
    
    settings = {
        'template_path': 'template',
        'static_path': 'static',
        'static_url_prefix': '/static/',
        'ui_methods': mt,
        'ui_modules': md,
    }
    
    application = tornado.web.Application([
        (r"/index", MainHandler),
    ], **settings)
    
    
    if __name__ == "__main__":
        application.listen(8009)
        tornado.ioloop.IOLoop.instance().start()
    View Code

    c,使用

    <!DOCTYPE html>
    <html>
    <head lang="en">
        <meta charset="UTF-8">
        <title>Nick</title>
        <link href="{{static_url("commons.css")}}" rel="stylesheet" />
    </head>
    <body>
        <h1>hello</h1>
        {% module custom(123) %}
        {{ tab() }}
    </body>
    DEMO.html

    静态文件

    在应用配置 settings 中指定 static_path 选项来提供静态文件服务;

    在应用配置 settings 中指定 static_url_prefix 选项来提供静态文件前缀服务;

    在导入静态文件时用 {{static_url('XX.css')}} 方式实现主动缓存静态文件;

    settings = {
        'template_path': 'views',
        'static_path': 'static',
        'static_url_prefix': '/static/',
    }
    # html使用
    
    <head lang="en">
        <title>Nick</title>
        <link href="{{static_url("commons.css")}}" rel="stylesheet" />
    </head>

    Cookie

    1、基本Cookie

    set_cookie 方法在用户的浏览中设置 cookie;

    get_cookie 方法在用户的浏览中获取 cookie。

    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            if not self.get_cookie("mycookie"):
                self.set_cookie("mycookie", "myvalue")
                self.write("Your cookie was not set yet!")
            else:
                self.write("Your cookie was set!")

    2、加密Cookie(签名)

    Cookie 很容易被恶意的客户端伪造。加入你想在 cookie 中保存当前登陆用户的 id 之类的信息,你需要对 cookie 作签名以防止伪造。Tornado 通过 set_secure_cookie 和 get_secure_cookie 方法直接支持了这种功能。 要使用这些方法,你需要在创建应用时提供一个密钥,名字为 cookie_secret。 你可以把它作为一个关键词参数传入应用的设置中:

    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            if not self.get_secure_cookie("mycookie"):
                self.set_secure_cookie("mycookie", "myvalue")
                self.write("Your cookie was not set yet!")
            else:
                self.write("Your cookie was set!")
                 
    application = tornado.web.Application([
        (r"/", MainHandler),
    ], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")

    加密Cookice的本质:

    写cookie过程:

    • 将值进行base64加密
    • 对除值以外的内容进行签名,哈希算法(无法逆向解析)
    • 拼接 签名 + 加密值

    读cookie过程:

    • 读取 签名 + 加密值
    • 对签名进行验证
    • base64解密,获取值内容

    注:许多API验证机制和安全cookie的实现机制相同。

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
     
    import tornado.ioloop
    import tornado.web
     
     
    class MainHandler(tornado.web.RequestHandler):
     
        def get(self):
            login_user = self.get_secure_cookie("login_user", None)
            if login_user:
                self.write(login_user)
            else:
                self.redirect('/login')
     
     
    class LoginHandler(tornado.web.RequestHandler):
        def get(self):
            self.current_user()
     
            self.render('login.html', **{'status': ''})
     
        def post(self, *args, **kwargs):
     
            username = self.get_argument('name')
            password = self.get_argument('pwd')
            if username == 'nick' and password == 'nicknick':
                self.set_secure_cookie('login_user', 'nick')
                self.redirect('/')
            else:
                self.render('login.html', **{'status': '用户名或密码错误'})
     
    settings = {
        'template_path': 'template',
        'static_path': 'static',
        'static_url_prefix': '/static/',
        'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh'
    }
     
    application = tornado.web.Application([
        (r"/index", MainHandler),
        (r"/login", LoginHandler),
    ], **settings)
     
     
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    基于cookie实现用户验证-DEMO

    3、JavaScript操作Cookie

    由于Cookie保存在浏览器端,所以在浏览器端也可以使用JavaScript来操作Cookie

    /*
    设置cookie,指定秒数过期
     */
    function setCookie(name,value,expires){
        var temp = [];
        var current_date = new Date();
        current_date.setSeconds(current_date.getSeconds() + 5);
        document.cookie = name + "= "+ value +";expires=" + current_date.toUTCString();
    }

    对于参数:

    • domain   指定域名下的cookie
    • path       域名下指定url中的cookie
    • secure    https使用

    注:jQuery中也有指定的插件 jQuery Cookie 专门用于操作cookie,猛击这里

    异步非阻塞

    1、基本使用

    装饰器 + Future 从而实现Tornado的异步非阻塞

    class AsyncHandler(tornado.web.RequestHandler):
     
        @gen.coroutine
        def get(self):
            future = Future()
            future.add_done_callback(self.doing)
            yield future
            #
            # tornado.ioloop.IOLoop.current().add_future(future,self.doing)
            # yield future
     
        def doing(self,*args, **kwargs):
            self.write('async')
            self.finish()

    当发送GET请求时,由于方法被@gen.coroutine装饰且yield 一个 Future对象,那么Tornado会等待,等待用户向future对象中放置数据或者发送信号,如果获取到数据或信号之后,就开始执行doing方法。

    异步非阻塞体现在当在Tornaod等待用户向future对象中放置数据时,还可以处理其他请求。

    注意:在等待用户向future对象中放置数据或信号时,此连接是不断开的。

    2,httpclient类库

    Tornado提供了httpclient类库用于发送Http请求,其配合Tornado的异步非阻塞使用。

    class AsyncHandler(tornado.web.RequestHandler):
        @gen.coroutine
        def get(self):
            from tornado import httpclient
     
            http = httpclient.AsyncHTTPClient()
            yield http.fetch("http://www.google.com", self.endding)
     
     
        def endding(self, response):
            print(len(response.body))
            self.write('ok')
            self.finish()

    3,自定义web非阻塞框架

    import socket
    import select
    import time
    
    
    class HttpRequest(object):
        """
        用户封装用户请求信息
        """
    
        def __init__(self, content):
            """
            :param content:用户发送的请求数据:请求头和请求体
            """
            self.content = content
    
            self.header_bytes = bytes()
            self.body_bytes = bytes()
    
            self.header_dict = {}
    
            self.method = ""
            self.url = ""
            self.protocol = ""
    
            self.initialize()
            self.initialize_headers()
    
        def initialize(self):
    
            temp = self.content.split(b'
    
    ', 1)
            if len(temp) == 1:
                self.header_bytes += temp
            else:
                h, b = temp
                self.header_bytes += h
                self.body_bytes += b
    
        @property
        def header_str(self):
            return str(self.header_bytes, encoding='utf-8')
    
        def initialize_headers(self):
            headers = self.header_str.split('
    ')
            first_line = headers[0].split(' ')
            if len(first_line) == 3:
                self.method, self.url, self.protocol = headers[0].split(' ')
                for line in headers:
                    kv = line.split(':')
                    if len(kv) == 2:
                        k, v = kv
                        self.header_dict[k] = v
    
    
    class Future(object):
        def __init__(self, timeout=0):
            self.result = None
            self.timeout = timeout
            self.start = time.time()
    
    
    def main(request):
        f = Future(5)
        return f
    
    
    def index(request):
        return "indexasdfasdfasdf"
    
    
    routers = [
        ('/main/', main),
        ('/index/', index),
    ]
    
    
    def run():
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind(("127.0.0.1", 9999,))
        sock.setblocking(False)
        sock.listen(128)
    
        inputs = [sock, ]
        # inputs.append(sock)
    
        async_request_dict = {
            # 'socket': futrue
        }
    
        while True:
            rlist, wlist, elist = select.select(inputs, [], [], 0.05)
            for r in rlist:
                if r == sock:
                    """新请求到来"""
                    conn, addr = sock.accept()
                    conn.setblocking(False)
                    inputs.append(conn)
                else:
                    """客户端发来数据"""
                    data = b""
                    while True:
                        try:
                            chunk = r.recv(1024)
                            data = data + chunk
                        except Exception as e:
                            chunk = None
                        if not chunk:
                            break
                    # data进行处理:请求头和请求体
                    request = HttpRequest(data)
                    # 1. 请求头中获取url
                    # 2. 去路由中匹配,获取指定的函数
                    # 3. 执行函数,获取返回值
                    # 4. 将返回值 r.sendall(b'alskdjalksdjf;asfd')
                    import re
                    flag = False
                    func = None
                    for route in routers:
                        if re.match(route[0], request.url):
                            flag = True
                            func = route[1]
                            break
                    if flag:
                        result = func(request)
                        if isinstance(result, Future):
                            async_request_dict[r] = result
                        else:
                            r.sendall(bytes(result, encoding='utf-8'))
                            inputs.remove(r)
                            r.close()
                    else:
                        r.sendall(b"404")
                        inputs.remove(r)
                        r.close()
    
            for conn in async_request_dict.keys():
                future = async_request_dict[conn]
                start = future.start
                timeout = future.timeout
                ctime = time.time()
                if (start + timeout) <= ctime:
                    future.result = b"timeout"
                if future.result:
                    conn.sendall(future.result)
                    conn.close()
                    del async_request_dict[conn]
                    inputs.remove(conn)
    
    
    if __name__ == '__main__':
        run()
    自定制web非阻塞框架-DEMO

    自定制Web组件

    一,session

    Tornado框架中,默认执行Handler的get/post等方法之前默认会执行 initialize方法,所以可以通过自定义的方式使得所有请求在处理前执行操作...

    session其实就是定义在服务器端用于保存用户回话的容器,其必须依赖cookie才能实现。

    import tornado.ioloop
    import tornado.web
    from controllers.account import LoginHandler
    from controllers.home import HomeHandler
    
    import time
    import hashlib
    
    class Cache(object):
        """
        将session保存在内存
        """
        def __init__(self):
            self.container = {}
    
        def __contains__(self, item):
            return item in self.container
    
        def initial(self,random_str):
            self.container[random_str] = {}
    
        def get(self,random_str,key):
            return self.container[random_str].get(key)
    
        def set(self,random_str,key,value):
            self.container[random_str][key] = value
    
        def delete(self,random_str,key):
            del self.container[random_str][key]
    
        def open(self):
            pass
    
        def close(self):
            pass
    
        def clear(self,random_str):
            del self.container[random_str]
    
    class Memcache(object):
        def __init__(self):
            pass
    
        def get(self,key):
            pass
    
        def set(self,key,value):
            pass
    
        def delete(self,key):
            pass
    
        def open(self):
            pass
    
        def close(self):
            pass
    
    P = Cache
    
    class Session(object):
        def __init__(self,handler):
            self.handler = handler
            self.random_str = None
            self.ppp = P()
            self.ppp.open()
            # 去用户请求信息中获取session_id,如果没有,新用户
            client_random_str = self.handler.get_cookie('session_id')
            if not client_random_str:
                "新用户"
                self.random_str = self.create_random_str()
                container[self.random_str] = {}
            else:
                if client_random_str in self.ppp:
                    "老用户"
                    self.random_str = client_random_str
                else:
                    "非法用户"
                    self.random_str = self.create_random_str()
                    self.ppp.initial(self.random_str)
            ctime = time.time()
            self.handler.set_cookie('session_id',self.random_str,expires=ctime+1800)
            self.ppp.close()
    
        def create_random_str(self):
            v = str(time.time())
            m = hashlib.md5()
            m.update(bytes(v,encoding='utf-8'))
            return m.hexdigest()
    
        def __setitem__(self, key, value):
            self.ppp.open()
            self.ppp.set(self.random_str,key,value)
            self.ppp.close()
        def __getitem__(self, key):
            self.ppp.open()
            v = self.ppp.get(self.random_str,key)
            self.ppp.close()
            return v
        def __delitem__(self, key):
            self.ppp.open()
            self.ppp.delete(self.random_str,key)
            self.ppp.close()
    
        def clear(self):
            self.ppp.open()
            self.ppp.clear(self.random_str)
            self.ppp.close()
    
    class Foo(object):
        def initialize(self):
            # self是MainHandler对象
            self.session = Session(self)
            super(Foo,self).initialize()
    
    class HomeHandler(Foo,tornado.web.RequestHandler):
    
        def get(self):
            user = self.session['uuuuu']
            if not  user:
                self.redirect("http://www.oldboyedu.com")
            else:
                self.write(user)
    
    class LoginHandler(Foo,tornado.web.RequestHandler):
    
        def get(self):
            self.session['uuuuu'] = 'root'
            self.redirect('/home')
    
    class TestHandler(tornado.web.RequestHandler):
        def get(self):
            self.set_cookie('k1', 'vvv', expires=time.time()+20)
    
    class ShowHandler(tornado.web.RequestHandler):
        def get(self):
            self.write(self.get_cookie('k1'))
    
    
    
    application = tornado.web.Application([
        (r"/login", LoginHandler),
        (r"/home", HomeHandler),
        (r"/test", TestHandler),
        (r"/show", ShowHandler),
    ])
    
    if __name__ == "__main__":
        application.listen(9999)
        tornado.ioloop.IOLoop.instance().start()
    自定义session
  • 相关阅读:
    boost::asio中的implementation_type介绍
    boost::asio::io_service::run学习笔记
    vim使用笔记
    进程、线程运行状态查看包括线程在cpu上运行情况
    c++自旋锁——学习笔记
    grep搜索过滤指定目录
    /usr/bin/ld: cannot find -lstdc++ -lz问题
    linux下条件变量使用笔记
    map使用笔记
    关于友元函数在类内定义的问题--笔记
  • 原文地址:https://www.cnblogs.com/zhangliang91/p/10547676.html
Copyright © 2020-2023  润新知