Tornado web 框架

摘要:
World“)settings={'template_path':#html文件'static_path':#static文件前缀'cookie_secret':#cookie自定义字符串salt adding#'xsrf_cookies':”)#html代码直接写入浏览器客户端self.render(“index.html”)#html文件被返回,
Tornado web 框架 其实很简单、深度应用

一、简介

 

  Tornado 是 FriendFeed 使用的可扩展的非阻塞式 web 服务器及其相关工具的开源版本。这个 Web 框架看起来有些像web.py 或者 Google 的 webapp,不过为了能有效利用非阻塞式服务器环境,这个 Web 框架还包含了一些相关有用工具及优化。

 

  Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其非阻塞的方式和对 epoll 的运用,Tornado 每秒可以处理数以千计的连接,这就意味着对于实时的 Web 服务来说,Tornado 是一个理想的 Web 框架。开发这个 Web 服务器的主要目的就是为了处理 FriendFeed 的实时功能 ——在 FriendFeed 的应用里每一个活动用户都会保持着一个服务器连接。(关于如何扩容服务器,以处理数以千计的客户端的连接的问题,请参阅 C10K problem。)

 

  请参见 Tornado 文档 或 Tornado 原文文档(镜像)以详细了解该 Web 框架。

 

下载和安装

 
复制代码
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 框架第3张 Tornado 各模块
   

二、Hello, world

 

 "Hello, world" 及 Application settings 基本配置:

 
复制代码
import tornado.ioloop
import tornado.web
# import uimodules as md
# import uimethods as mt

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

settings = {
    'template_path': 'views',        # html文件
    'static_path': 'statics',        # 静态文件(css,js,img)
    'static_url_prefix': '/statics/',# 静态文件前缀
    'cookie_secret': 'suoning',      # cookie自定义字符串加盐
    # 'xsrf_cookies': True,          # 防止跨站伪造
    # 'ui_methods': mt,              # 自定义UIMethod函数
    # 'ui_modules': md,              # 自定义UIModule类
}

application = tornado.web.Application([
    (r"/", MainHandler),
], **settings)

if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
复制代码
   

三、方法

 

 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.cnblogs.com/suoning",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" : "views"

 

 1、基本使用

 
Tornado web 框架第10张 app.py
 
Tornado web 框架第11张 index.html
 
Tornado web 框架第12张 其他方法
 

2、母版

 
Tornado web 框架第13张 layout.html
 
Tornado web 框架第14张 index.html
 

3、导入

 
Tornado web 框架第15张 header.html
 
Tornado web 框架第16张 index.html
 

4、自定义UIMethod以UIModule

 

a.定义

 
Tornado web 框架第17张 uimethods.py
 
Tornado web 框架第18张 uimodules.py
 

b.注册

 
Tornado web 框架第19张 View Code
 

c.使用

 
Tornado web 框架第20张 View Code
   

 五、静态文件和主动式文件缓存

 

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

 

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

 

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

 
settings = {
    'template_path': 'views',
    'static_path': 'static',
    'static_url_prefix': '/static/',
}
 
<head lang="en">
    <title>Nick</title>
    <link href="http://t.zoukankan.com/{{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=")
复制代码
 
Tornado web 框架第25张 内部算法
 

加密Cookice的本质:

 

写cookie过程:

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

读cookie过程:

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

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

 
Tornado web 框架第26张 基于Cookie实现用户验证-Demo
 
Tornado web 框架第27张 基于签名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,猛击这里

   

七、用户认证

 

当前已经认证的用户信息都被保存在每一个请求处理器的 self.current_user 当中, 同时在模板的 current_user 中也是。默认情况下,current_user 为 None

 

要在应用程序实现用户认证的功能,你需要重写请求处理中 get_current_user() 这 个方法,在其中判定当前用户的状态,比如通过 cookie。下面的例子让用户简单地使用一个 nickname 登陆应用,该登陆信息将被保存到 cookie 中:

 
复制代码
class BaseHandler(tornado.web.RequestHandler):
    def get_current_user(self):
        return self.get_secure_cookie("user")

class MainHandler(BaseHandler):
    def get(self):
        if not self.current_user:
            self.redirect("/login")
            return
        name = tornado.escape.xhtml_escape(self.current_user)
        self.write("Hello, " + name)

class LoginHandler(BaseHandler):
    def get(self):
        self.write('<html><body><form action="/login" method="post">'
                   'Name: <input type="text" name="name">'
                   '<input type="submit" value="Sign in">'
                   '</form></body></html>')

    def post(self):
        self.set_secure_cookie("user", self.get_argument("name"))
        self.redirect("/")

application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/login", LoginHandler),
], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
复制代码
 

对于那些必须要求用户登陆的操作,可以使用装饰器 tornado.web.authenticated。 如果一个方法套上了这个装饰器,但是当前用户并没有登陆的话,页面会被重定向到 login_url(应用配置中的一个选项),上面的例子可以被改写成:

 
复制代码
class MainHandler(BaseHandler):
    @tornado.web.authenticated
    def get(self):
        name = tornado.escape.xhtml_escape(self.current_user)
        self.write("Hello, " + name)

settings = {
    "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
    "login_url": "/login",
}
application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/login", LoginHandler),
], **settings)
复制代码
 

如果你使用 authenticated 装饰器来装饰 post() 方法,那么在用户没有登陆的状态下, 服务器会返回 403 错误。

 

Tornado 内部集成了对第三方认证形式的支持,比如 Google 的 OAuth 。参阅 auth 模块 的代码文档以了解更多信息。 for more details. Checkauth 模块以了解更多的细节。在 Tornado 的源码中有一个 Blog 的例子,你也可以从那里看到 用户认证的方法(以及如何在 MySQL 数据库中保存用户数据)。

   

八、CSRF 跨站伪造请求的防范

 

跨站伪造请求(Cross-site request forgery), 简称为 XSRF,是个性化 Web 应用中常见的一个安全问题。前面的链接也详细讲述了 XSRF 攻击的实现方式。

 

当前防范 XSRF 的一种通用的方法,是对每一个用户都记录一个无法预知的 cookie 数据,然后要求所有提交的请求中都必须带有这个 cookie 数据。如果此数据不匹配 ,那么这个请求就可能是被伪造的。

 

Tornado 有内建的 XSRF 的防范机制,要使用此机制,你需要在应用配置中加上 xsrf_cookies 设定:

 
复制代码
settings = {
    "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
    "login_url": "/login",
    "xsrf_cookies": True,
}
application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/login", LoginHandler),
], **settings)
复制代码
 

如果设置了 xsrf_cookies,那么 Tornado 的 Web 应用将对所有用户设置一个 _xsrf 的 cookie 值,如果 POST PUT DELET 请求中没有这 个 cookie 值,那么这个请求会被直接拒绝。如果你开启了这个机制,那么在所有 被提交的表单中,你都需要加上一个域来提供这个值。你可以通过在模板中使用 专门的函数 xsrf_form_html() 来做到这一点:

 
<form action="/new_message" method="post">
  {{ xsrf_form_html() }}
  <input type="text" name="message"/>
  <input type="submit" value="Post"/>
</form>
 

如果你提交的是 AJAX 的 POST 请求,你还是需要在每一个请求中通过脚本添加上 _xsrf 这个值。下面是在 FriendFeed 中的 AJAX 的 POST 请求,使用了 jQuery 函数来为所有请求组东添加 _xsrf 值:

 
复制代码
function getCookie(name) {
    var r = document.cookie.match("\b" + name + "=([^;]*)\b");
    return r ? r[1] : undefined;
}

jQuery.postJSON = function(url, args, callback) {
    args._xsrf = getCookie("_xsrf");
    $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
        success: function(response) {
        callback(eval("(" + response + ")"));
    }});
};
复制代码
 

对于 PUT 和 DELETE 请求(以及不使用将 form 内容作为参数的 POST 请求) 来说,你也可以在 HTTP 头中以 X-XSRFToken这个参数传递 XSRF token。

 

如果你需要针对每一个请求处理器定制 XSRF 行为,你可以重写 RequestHandler.check_xsrf_cookie()。例如你需要使用一个不支持 cookie 的 API, 你可以通过将 check_xsrf_cookie() 函数设空来禁用 XSRF 保护机制。然而如果 你需要同时支持 cookie 和非 cookie 认证方式,那么只要当前请求是通过 cookie 进行认证的,你就应该对其使用 XSRF 保护机制,这一点至关重要。

   

九、文件上传

 

1、Form表单上传

 
Tornado web 框架第38张 HTML
 
Tornado web 框架第39张 Python
 

2、AJAX上传

 
Tornado web 框架第40张 HTML - XMLHttpRequest
 
Tornado web 框架第41张 HTML - jQuery
 
Tornado web 框架第42张 HTML - iframe
 
Tornado web 框架第43张 Python
 
Tornado web 框架第44张 扩展:基于iframe实现Ajax上传示例
   

十、验证码

 

安装图像处理模块:pip3 install pillow

 

 实例截图:

 

Tornado web 框架第45张

 

验证码Demo源码下载:猛击这里,注意导入模块的路径

 
Tornado web 框架第46张 验证码实例 python文件
 
Tornado web 框架第47张 验证码实例 HTML文件
   

十一、非阻塞式异步请求

 

当一个处理请求的行为被执行之后,这个请求会自动地结束。因为 Tornado 当中使用了 一种非阻塞式的 I/O 模型,所以你可以改变这种默认的处理行为——让一个请求一直保持 连接状态,而不是马上返回,直到一个主处理行为返回。要实现这种处理方式,只需要使用 tornado.web.asynchronous 装饰器就可以了。

 

使用了这个装饰器之后,你必须调用 self.finish() 已完成 HTTTP 请求,否则 用户的浏览器会一直处于等待服务器响应的状态:

 
class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        self.write("Hello, world")
        self.finish()
 

下面是一个使用 Tornado 内置的异步请求 HTTP 客户端去调用 FriendFeed 的 API 的例 子:

 
复制代码
class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        http = tornado.httpclient.AsyncHTTPClient()
        http.fetch("http://friendfeed-api.com/v2/feed/bret",
                   callback=self.on_response)

    def on_response(self, response):
        if response.error: raise tornado.web.HTTPError(500)
        json = tornado.escape.json_decode(response.body)
        self.write("Fetched " + str(len(json["entries"])) + " entries "
                   "from the FriendFeed API")
        self.finish()
复制代码
 

例子中,当 get() 方法返回时,请求处理还没有完成。在 HTTP 客户端执行它的回 调函数 on_response() 时,从浏览器过来的请求仍然是存在的,只有在显式调用了 self.finish() 之后,才会把响应返回到浏览器。

 

关于更多异步请求的高级例子,可以参阅 demo 中的 chat 这个例子。它是一个使用 long polling 方式 的 AJAX 聊天室。如果你使用到了 long polling,你可能需要复写on_connection_close(), 这样你可以在客户连接关闭以后做相关的清理动作。

   

十二、自定义Session

 

何为 Session?

 

是一种服务器端的机制,用于存储特定的用户会话所需的信息;必须依赖 cookice 。

 

Tornado 默认没有提供 Session 机制,我们可以自定义如下:

 
复制代码
#!/usr/bin/env python
#-*- coding:utf-8 -*-
"""blog:http://www.cnblogs.com/suoning"""
__author__ = 'Nick Suo'

import tornado.web
import tornado.ioloop

import time
import hashlib

container = {}   # 用于存放 Session,可放在数据库,缓存等地

class Session:

    def __init__(self,handler):
        """
        :param handler: 在 initialize 方法函数里初始化,传入当前对象self
        :param handler: 客户端是否有指定 __ss__ flag
        """
        self.handler = handler
        self.random_str = None

    def __genarte_randmo_str(self):
        """
        以当前时间戳生成随机字符串
        :return: 返回随机字符串
        """
        obj = hashlib.md5()
        obj.update(bytes(str(time.time()), encoding='utf-8'))
        random_str = obj.hexdigest()
        return random_str

    def __setitem__(self,key,value):
        """
        设置 session 方法函数
        :param key: 设置的 key
        :param value: 设置的 value
        """
        if not self.random_str:
            random_str = self.handler.get_secure_cookie("__ss__")
            if not random_str:
                random_str = self.__genarte_randmo_str()
                container[random_str] = {}
            else:
                if random_str not in container.keys():
                    random_str = self.__genarte_randmo_str()
                    container[random_str] = {}
            self.random_str = random_str

        container[self.random_str][key] = value
        self.handler.set_secure_cookie("__ss__",self.random_str)    # expires_days=None

    def __getitem__(self,key):
        """
        获取指定 session 方法函数
        :param key: 要获取的 key
        :return: 返回获取到的值
        """
        randmon_str = self.handler.get_secure_cookie("__ss__")
        randmon_str = str(randmon_str,encoding='utf-8')
        if not randmon_str:
            return None
        user_info_dic = container.get(randmon_str, None)
        if not user_info_dic:
            return None
        value = user_info_dic.get(key, None)
        return value


class MyHandler(tornado.web.RequestHandler):

    def initialize(self):
        self.session = Session(self)


class IndexHandler(MyHandler):

    def get(self, *args, **kwargs):
        ......
        result = self.session["name"]    # 获取 Session 方法函数
        self.session['name'] = "Nick"    # 设置 Session 方法函数



settings = {
    'template_path':'views',
    'static_path':'statics',
    'cookie_secret':'suoning..................',
}

application = tornado.web.Application([
    (r'/index',IndexHandler),
], **settings)


if __name__ == '__main__':
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
复制代码
 

免责声明:文章转载自《Tornado web 框架》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇wordpress数据库优化wp_posts表 OPTIMIZEC. Painting Fence 分治下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Android Studio 自定义签名,代码段快捷键

此篇文章主要介绍如何在Android Studio中函数如何添加注释,使其和eclipse一样方便的添加注释 Android Studio默认函数注释为 /** * */ 下面方法将要改为如下格式 1 2 3 4 5 /**  *  * @author zony  * @time 15-11-25 下午2:41  */ 步...

使用swagger遇到的问题

1.定义全局的请求参数时, defaultValue不能是中文,不然一直是请求中 ParameterBuilder userName = new ParameterBuilder(); ParameterBuilder tokenPar1 = new ParameterBuilder(); List<Parameter> pars = new...

.net core 学习小结之 配置介绍(config)以及热更新

命令行的配置 var settings = new Dictionary<string, string>{ { "name","cyao"}, {"age","18"} }; var builder = new Configura...

JPA实体类监听器@EntityListeners注解使用实例

被@Prepersist注解的方法 ,完成save之前的操作。被@Preupdate注解的方法 ,完成update之前的操作。被@PreRemove注解的方法 ,完成remove之前的操作。被@Postpersist注解的方法 ,完成save之后的操作。被@Postupdate注解的方法 ,完成update之后的操作。被@PostRemovet注解的方法...

linux权限问题学习总结

寒假里看的权限问题,现在来总结一下。 文件权限除了r、w、x外还有s、t、i、a权限: 1、s:文件属主和组设置SUID和GUID,文件在被设置了s权限后将以root身份执行。在设置s权限时文件属主、属组必须先设置相应的x权限,否则s权限并不能正真生效(chmod命令不进行必要的完整性检查,即使不设置x权限就设置s权限,chmod也不会报错,当我们ls -...

跨站脚本攻击(XSS)

XSS——通过“HTML 注入” 篡改了网页,插入了恶意的脚本,从而在用户浏览网页时,控制用户浏览器。 XSS 分类: 1. 反射性 XSS:把用户输入的数据 “反射” 给浏览器,往往需要诱使用户 “点击” 一个恶意链接,也叫非持久型 XSS。 2. 存储型 XSS:把用户输入的数据 “存储” 在服务器端,当其他用户打开包含有该数据的页面时执行其中可能包含...