python-Tornado 框架
阅读原文时间:2020年09月21日阅读:4

概述

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

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

下载安装:

?

1

2

3

4

pip3 install tornado

源码安装

https:``/``/``pypi.python.org``/``packages``/``source``/``t``/``tornado``/``tornado``-``4.3``.tar.gz

框架使用

一、快速上手

+?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

#!/usr/bin/env python

# -*- coding:utf-8 -*-

import tornado.ioloop

import tornado.web

class MainHandler(tornado.web.RequestHandler):

def get(``self``):

self``.write(``"Hello, world"``)

application = tornado.web.Application([

(r``"/index"``, MainHandler),

])

if __name__ =``= "__main__"``:

application.listen(``8888``)

tornado.ioloop.IOLoop.instance().start()

执行过程:

  • 第一步:执行脚本,监听 8888 端口
  • 第二步:浏览器客户端访问 /index  -->  http://127.0.0.1:8888/index
  • 第三步:服务器接受请求,并交由对应的类处理该请求
  • 第四步:类接受到请求之后,根据请求方式(post / get / delete …)的不同调用并执行相应的方法
  • 第五步:方法返回值的字符串内容发送浏览器

#!/usr/bin/env python

-*- coding:utf-8 -*-

#!/usr/bin/env python

-*- coding:utf-8 -*-

import tornado.ioloop
import tornado.web
from tornado import httpclient
from tornado.web import asynchronous
from tornado import gen

import uimodules as md
import uimethods as mt

class MainHandler(tornado.web.RequestHandler):
@asynchronous
@gen.coroutine
def get(self):
print 'start get '
http = httpclient.AsyncHTTPClient()
http.fetch("http://127.0.0.1:8008/post/", self.callback)
self.write('end')

    def callback(self, response):  
        print response.body

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()

异步非阻塞示例

二、路由系统

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

+?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

#!/usr/bin/env python

# -*- coding:utf-8 -*-

import tornado.ioloop

import tornado.web

class MainHandler(tornado.web.RequestHandler):

def get(``self``):

self``.write(``"Hello, world"``)

class StoryHandler(tornado.web.RequestHandler):

def get(``self``, story_id):

self``.write(``"You requested the story " + story_id)

class BuyHandler(tornado.web.RequestHandler):

def get(``self``):

self``.write(``"buy.wupeiqi.com/index"``)

application = tornado.web.Application([

(r``"/index"``, MainHandler),

(r``"/story/([0-9]+)"``, StoryHandler),

])

application.add_handlers(``'buy.wupeiqi.com$'``, [

(r``'/index'``,BuyHandler),

])

if __name__ =``= "__main__"``:

application.listen(``80``)

tornado.ioloop.IOLoop.instance().start()

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

三、模板引擎

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



老男孩

<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>


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、母版



老男孩 {% block CSS %}{% end %}

<div class="pg-header">

</div>

{% block RenderBody %}{% end %}

<script src="{{static\_url("js/jquery-1.8.2.min.js")}}"></script>

{% block JavaScript %}{% end %}  


layout.html

{% extends 'layout.html'%}
{% block CSS %}

{% end %}

{% block RenderBody %}

Index

<ul>  
{%  for item in li %}  
    <li>{{item}}</li>  
{% end %}  
</ul>

{% end %}

{% block JavaScript %}

{% end %}

index.html

3、导入

  • 1024
  • 42区

header.html



老男孩

<div class="pg-header">  
    {% include 'header.html' %}  
</div>

<script src="{{static\_url("js/jquery-1.8.2.min.js")}}"></script>


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>wupeiqi</h1>')  
    #return escape.xhtml\_escape('<h1>wupeiqi</h1>')

uimodules.py

b. 注册

#!/usr/bin/env python

-*- coding:utf-8 -*-

#!/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()

c. 使用




hello

{% module custom(123) %} {{ tab() }}

四、静态文件

对于静态文件,可以配置静态文件的目录和前段使用时的前缀,并且Tornaodo还支持静态文件缓存。

#!/usr/bin/env python

-*- coding:utf-8 -*-

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render('home/index.html')

settings = {
'template_path': 'template',
'static_path': 'static',
'static_url_prefix': '/static/',
}

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

if __name__ == "__main__":
application.listen(80)
tornado.ioloop.IOLoop.instance().start()

app.py


hello

index.html

注:静态文件缓存的实现

def get\_content\_version(cls, abspath):  
    """Returns a version string for the resource at the given path.

    This class method may be overridden by subclasses.  The  
    default implementation is a hash of the file's contents.

    .. versionadded:: 3.1  
    """  
    data = cls.get\_content(abspath)  
    hasher = hashlib.md5()  
    if isinstance(data, bytes):  
        hasher.update(data)  
    else:  
        for chunk in data:  
            hasher.update(chunk)  
    return hasher.hexdigest()

五、cookie

Tornado中可以对cookie进行操作,并且还可以对cookie进行签名以放置伪造。

1、基本操作

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=")

def _create_signature_v1(secret, *parts):
hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
for part in parts:
hash.update(utf8(part))
return utf8(hash.hexdigest())

加密

def _create_signature_v2(secret, s):
hash = hmac.new(utf8(secret), digestmod=hashlib.sha256)
hash.update(utf8(s))
return utf8(hash.hexdigest())

def create_signed_value(secret, name, value, version=None, clock=None,
key_version=None):
if version is None:
version = DEFAULT_SIGNED_VALUE_VERSION
if clock is None:
clock = time.time

timestamp = utf8(str(int(clock())))  
value = base64.b64encode(utf8(value))  
if version == 1:  
    signature = \_create\_signature\_v1(secret, name, value, timestamp)  
    value = b"|".join(\[value, timestamp, signature\])  
    return value  
elif version == 2:  
    # The v2 format consists of a version number and a series of  
    # length-prefixed fields "%d:%s", the last of which is a  
    # signature, all separated by pipes.  All numbers are in  
    # decimal format with no leading zeros.  The signature is an  
    # HMAC-SHA256 of the whole string up to that point, including  
    # the final pipe.  
    #  
    # The fields are:  
    # - format version (i.e. 2; no length prefix)  
    # - key version (integer, default is 0)  
    # - timestamp (integer seconds since epoch)  
    # - name (not encoded; assumed to be ~alphanumeric)  
    # - value (base64-encoded)  
    # - signature (hex-encoded; no length prefix)  
    def format\_field(s):  
        return utf8("%d:" % len(s)) + utf8(s)  
    to\_sign = b"|".join(\[  
        b"2",  
        format\_field(str(key\_version or 0)),  
        format\_field(timestamp),  
        format\_field(name),  
        format\_field(value),  
        b''\])

    if isinstance(secret, dict):  
        assert key\_version is not None, 'Key version must be set when sign key dict is used'  
        assert version >= 2, 'Version must be at least 2 for key version support'  
        secret = secret\[key\_version\]

    signature = \_create\_signature\_v2(secret, to\_sign)  
    return to\_sign + signature  
else:  
    raise ValueError("Unsupported version %d" % version)

解密

def _decode_signed_value_v1(secret, name, value, max_age_days, clock):
parts = utf8(value).split(b"|")
if len(parts) != 3:
return None
signature = _create_signature_v1(secret, name, parts[0], parts[1])
if not _time_independent_equals(parts[2], signature):
gen_log.warning("Invalid cookie signature %r", value)
return None
timestamp = int(parts[1])
if timestamp < clock() - max_age_days * 86400: gen_log.warning("Expired cookie %r", value) return None if timestamp > clock() + 31 * 86400:
# _cookie_signature does not hash a delimiter between the
# parts of the cookie, so an attacker could transfer trailing
# digits from the payload to the timestamp without altering the
# signature. For backwards compatibility, sanity-check timestamp
# here instead of modifying _cookie_signature.
gen_log.warning("Cookie timestamp in future; possible tampering %r",
value)
return None
if parts[1].startswith(b"0"):
gen_log.warning("Tampered cookie %r", value)
return None
try:
return base64.b64decode(parts[0])
except Exception:
return None

def _decode_fields_v2(value):
def _consume_field(s):
length, _, rest = s.partition(b':')
n = int(length)
field_value = rest[:n]
# In python 3, indexing bytes returns small integers; we must
# use a slice to get a byte string as in python 2.
if rest[n:n + 1] != b'|':
raise ValueError("malformed v2 signed value field")
rest = rest[n + 1:]
return field_value, rest

rest = value\[2:\]  # remove version number  
key\_version, rest = \_consume\_field(rest)  
timestamp, rest = \_consume\_field(rest)  
name\_field, rest = \_consume\_field(rest)  
value\_field, passed\_sig = \_consume\_field(rest)  
return int(key\_version), timestamp, name\_field, value\_field, passed\_sig

def _decode_signed_value_v2(secret, name, value, max_age_days, clock):
try:
key_version, timestamp, name_field, value_field, passed_sig = _decode_fields_v2(value)
except ValueError:
return None
signed_string = value[:-len(passed_sig)]

if isinstance(secret, dict):  
    try:  
        secret = secret\[key\_version\]  
    except KeyError:  
        return None

expected\_sig = \_create\_signature\_v2(secret, signed\_string)  
if not \_time\_independent\_equals(passed\_sig, expected\_sig):  
    return None  
if name\_field != utf8(name):  
    return None  
timestamp = int(timestamp)  
if timestamp < clock() - max\_age\_days \* 86400:  
    # The signature has expired.  
    return None  
try:  
    return base64.b64decode(value\_field)  
except Exception:  
    return None

def get_signature_key_version(value):
value = utf8(value)
version = _get_version(value)
if version < 2:
return None
try:
key_version, _, _, _, _ = _decode_fields_v2(value)
except ValueError:
return None

return key\_version

内部算法

签名Cookie的本质是:

写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 == 'wupeiqi' and password == '123':  
        self.set\_secure\_cookie('login\_user', '武沛齐')  
        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

#!/usr/bin/env python

-*- coding:utf-8 -*-

import tornado.ioloop
import tornado.web

class BaseHandler(tornado.web.RequestHandler):

def get\_current\_user(self):  
    return self.get\_secure\_cookie("login\_user")

class MainHandler(BaseHandler):

@tornado.web.authenticated  
def get(self):  
    login\_user = self.current\_user  
    self.write(login\_user)

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 == 'wupeiqi' and password == '123':  
        self.set\_secure\_cookie('login\_user', '武沛齐')  
        self.redirect('/')  
    else:  
        self.render('login.html', \*\*{'status': '用户名或密码错误'})

settings = {
'template_path': 'template',
'static_path': 'static',
'static_url_prefix': '/static/',
'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh',
'login_url': '/login'
}

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。

?

1

2

3

4

5

6

7

8

9

/*

设置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,猛击这里

六、CSRF

Tornado中的夸张请求伪造和Django中的相似,跨站伪造请求(Cross-site request forgery)

settings = {
"xsrf_cookies": True,
}
application = tornado.web.Application([
(r"/", MainHandler),
(r"/login", LoginHandler),
], **settings)

配置

{{ xsrf\_form\_html() }}

使用 - 普通表单

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 + ")"));
}});
};

使用 - AJAX

注:Ajax使用时,本质上就是去获取本地的cookie,携带cookie再来发送请求

七、上传文件

1、Form表单上传


上传文件

HTML

#!/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')

def post(self, \*args, \*\*kwargs):  
    file\_metas = self.request.files\["fff"\]  
    # print(file\_metas)  
    for meta in file\_metas:  
        file\_name = meta\['filename'\]  
        with open(file\_name,'wb') as up:  
            up.write(meta\['body'\])

settings = {
'template_path': 'template',
}

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

if __name__ == "__main__":
application.listen(8000)
tornado.ioloop.IOLoop.instance().start()

Python

2、AJAX上传







扩展:基于iframe实现Ajax上传示例

$('#upload_iframe').load(function(){
var iframeContents = this.contentWindow.document.body.innerText;
iframeContents = JSON.parse(iframeContents);

            })

function bindChangeAvatar1() {
$('#avatarImg').change(function () {
var file_obj = $(this)[0].files[0];
$('#prevViewImg')[0].src = window.URL.createObjectURL(file_obj)
})
}

    function bindChangeAvatar2() {  
        $('#avatarImg').change(function () {  
            var file\_obj = $(this)\[0\].files\[0\];  
            var reader = new FileReader();  
            reader.readAsDataURL(file\_obj);  
            reader.onload = function (e) {  
                $('#previewImg')\[0\].src = this.result;  
            };  
        })  
    }

    function bindChangeAvatar3() {  
        $('#avatarImg').change(function () {  
            var file\_obj = $(this)\[0\].files\[0\];  
            var form = new FormData();  
            form.add('img\_upload', file\_obj);

            $.ajax({  
                url: '',  
                data: form,  
                processData: false,  // tell jQuery not to process the data  
                contentType: false,  // tell jQuery not to set contentType  
                success: function (arg) {

                }  
            })  
        })  
    }

    function bindChangeAvatar4() {  
        $('#avatarImg').change(function () {  
            $(this).parent().submit();

            $('#upload\_iframe').load(function () {  
                var iframeContents = this.contentWindow.document.body.innerText;  
                iframeContents = JSON.parse(iframeContents);  
                if (iframeContents.status) {  
                    $('#previewImg').attr('src', '/' + iframeContents.data);  
                }  
            })

        })  
    }

其他

八、验证码

验证码原理在于后台自动创建一张带有随机内容的图片,然后将内容通过img标签输出到页面。

安装图像处理模块:

?

1

pip3 install pillow

示例截图:

验证码Demo源码下载:猛击这里

九、异步非阻塞

1、基本使用

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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、同步阻塞和异步非阻塞对比

class SyncHandler(tornado.web.RequestHandler):

def get(self):  
    self.doing()  
    self.write('sync')

def doing(self):  
    time.sleep(10)

同步阻塞

class AsyncHandler(tornado.web.RequestHandler):
@gen.coroutine
def get(self):
future = Future()
tornado.ioloop.IOLoop.current().add_timeout(time.time() + 5, self.doing)
yield future

def doing(self, \*args, \*\*kwargs):  
    self.write('async')  
    self.finish()

异步非阻塞

3、httpclient类库

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

#!/usr/bin/env python

# -*- coding:utf-8 -*-

import tornado.web

from tornado import gen

from tornado import httpclient

# 方式一:

class AsyncHandler(tornado.web.RequestHandler):

@gen``.coroutine

def get(``self``, *``args, *``*``kwargs):

print``(``'进入'``)

http = httpclient.AsyncHTTPClient()

data = yield http.fetch(``"http://www.google.com"``)

print``(``'完事'``,data)

self``.finish(``'6666'``)

# 方式二:

# class AsyncHandler(tornado.web.RequestHandler):

#&nbsp;&nbsp;&nbsp;&nbsp; @gen.coroutine

#&nbsp;&nbsp;&nbsp;&nbsp; def get(self):

#&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print('进入')

#&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; http = httpclient.AsyncHTTPClient()

#&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; yield http.fetch("http://www.google.com", self.done)

#

#&nbsp;&nbsp;&nbsp;&nbsp; def done(self, response):

#&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print('完事')

#&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.finish('666')

application = tornado.web.Application([

(r``"/async"``, AsyncHandler),

])

if __name__ =``= "__main__"``:

application.listen(``8888``)

tornado.ioloop.IOLoop.instance().start()

#!/usr/bin/env python

-*- coding:utf-8 -*-

"""
需要先安装支持异步操作Mysql的类库:
Tornado-MySQL: https://github.com/PyMySQL/Tornado-MySQL#installation

pip3 install Tornado-MySQL

"""

import tornado.web
from tornado import gen

import tornado_mysql
from tornado_mysql import pools

POOL = pools.Pool(
dict(host='127.0.0.1', port=3306, user='root', passwd='123', db='cmdb'),
max_idle_connections=1,
max_recycle_sec=3)

@gen.coroutine
def get_user_by_conn_pool(user):
cur = yield POOL.execute("SELECT SLEEP(%s)", (user,))
row = cur.fetchone()
raise gen.Return(row)

@gen.coroutine
def get_user(user):
conn = yield tornado_mysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='cmdb',
charset='utf8')
cur = conn.cursor()
# yield cur.execute("SELECT name,email FROM web_models_userprofile where name=%s", (user,))
yield cur.execute("select sleep(10)")
row = cur.fetchone()
cur.close()
conn.close()
raise gen.Return(row)

class LoginHandler(tornado.web.RequestHandler):
def get(self, *args, **kwargs):
self.render('login.html')

@gen.coroutine  
def post(self, \*args, \*\*kwargs):  
    user = self.get\_argument('user')  
    data = yield gen.Task(get\_user, user)  
    if data:  
        print(data)  
        self.redirect('http://www.oldboyedu.com')  
    else:  
        self.render('login.html')

application = tornado.web.Application([
(r"/login", LoginHandler),
])

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

基于异步非阻塞和Tornado-MySQL实现用户登录示例

自定义Web组件

一、Session

1、面向对象基础

面向对象中通过索引的方式访问对象,需要内部实现 __getitem__ 、__delitem__、__setitem__方法

#!/usr/bin/env python

-*- coding:utf-8 -*-

class Foo(object):

def \_\_getitem\_\_(self, key):  
    print  '\_\_getitem\_\_',key

def \_\_setitem\_\_(self, key, value):  
    print '\_\_setitem\_\_',key,value

def \_\_delitem\_\_(self, key):  
    print '\_\_delitem\_\_',key

obj = Foo()
result = obj['k1']
#obj['k2'] = 'wupeiqi'
#del obj['k1']

2、Tornado扩展

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

class BaseHandler(tornado.web.RequestHandler):

def initialize(self):  
    self.xxoo = "wupeiqi"

class MainHandler(BaseHandler):

def get(self):  
    print(self.xxoo)  
    self.write('index')

class IndexHandler(BaseHandler):

def get(self):  
    print(self.xxoo)  
    self.write('index')

3、session

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

#!/usr/bin/env python

-*- coding:utf-8 -*-

import config
from hashlib import sha1
import os
import time

create_session_id = lambda: sha1(bytes('%s%s' % (os.urandom(16), time.time()), encoding='utf-8')).hexdigest()

class SessionFactory:

@staticmethod  
def get\_session\_obj(handler):  
    obj = None

    if config.SESSION\_TYPE == "cache":  
        obj = CacheSession(handler)  
    elif config.SESSION\_TYPE == "memcached":  
        obj = MemcachedSession(handler)  
    elif config.SESSION\_TYPE == "redis":  
        obj = RedisSession(handler)  
    return obj

class CacheSession:
session_container = {}
session_id = "__sessionId__"

def \_\_init\_\_(self, handler):  
    self.handler = handler  
    client\_random\_str = handler.get\_cookie(CacheSession.session\_id, None)  
    if client\_random\_str and client\_random\_str in CacheSession.session\_container:  
        self.random\_str = client\_random\_str  
    else:  
        self.random\_str = create\_session\_id()  
        CacheSession.session\_container\[self.random\_str\] = {}

    expires\_time = time.time() + config.SESSION\_EXPIRES  
    handler.set\_cookie(CacheSession.session\_id, self.random\_str, expires=expires\_time)

def \_\_getitem\_\_(self, key):  
    ret = CacheSession.session\_container\[self.random\_str\].get(key, None)  
    return ret

def \_\_setitem\_\_(self, key, value):  
    CacheSession.session\_container\[self.random\_str\]\[key\] = value

def \_\_delitem\_\_(self, key):  
    if key in CacheSession.session\_container\[self.random\_str\]:  
        del CacheSession.session\_container\[self.random\_str\]\[key\]

class RedisSession:
def __init__(self, handler):
pass

class MemcachedSession:
def __init__(self, handler):
pass

自定义Session

4、分布式Session

#!/usr/bin/env python
#coding:utf-8

import sys
import math
from bisect import bisect

if sys.version_info >= (2, 5):
import hashlib
md5_constructor = hashlib.md5
else:
import md5
md5_constructor = md5.new

class HashRing(object):
"""一致性哈希"""

def \_\_init\_\_(self,nodes):  
    '''初始化  
    nodes : 初始化的节点,其中包含节点已经节点对应的权重  
            默认每一个节点有32个虚拟节点  
            对于权重,通过多创建虚拟节点来实现  
            如:nodes = \[  
                    {'host':'127.0.0.1:8000','weight':1},  
                    {'host':'127.0.0.1:8001','weight':2},  
                    {'host':'127.0.0.1:8002','weight':1},  
                \]  
    '''

    self.ring = dict()  
    self.\_sorted\_keys = \[\]

    self.total\_weight = 0

    self.\_\_generate\_circle(nodes)

def \_\_generate\_circle(self,nodes):  
    for node\_info in nodes:  
        self.total\_weight += node\_info.get('weight',1)

    for node\_info in nodes:  
        weight = node\_info.get('weight',1)  
        node = node\_info.get('host',None)

        virtual\_node\_count = math.floor((32\*len(nodes)\*weight) / self.total\_weight)  
        for i in xrange(0,int(virtual\_node\_count)):  
            key = self.gen\_key\_thirty\_two( '%s-%s' % (node, i) )  
            if self.\_sorted\_keys.\_\_contains\_\_(key):  
                raise Exception('该节点已经存在.')  
            self.ring\[key\] = node  
            self.\_sorted\_keys.append(key)

def add\_node(self,node):  
    ''' 新建节点  
    node : 要添加的节点,格式为:{'host':'127.0.0.1:8002','weight':1},其中第一个元素表示节点,第二个元素表示该节点的权重。  
    '''  
    node = node.get('host',None)  
    if not node:  
            raise Exception('节点的地址不能为空.')

    weight = node.get('weight',1)

    self.total\_weight += weight  
    nodes\_count = len(self.\_sorted\_keys) + 1

    virtual\_node\_count = math.floor((32 \* nodes\_count \* weight) / self.total\_weight)  
    for i in xrange(0,int(virtual\_node\_count)):  
        key = self.gen\_key\_thirty\_two( '%s-%s' % (node, i) )  
        if self.\_sorted\_keys.\_\_contains\_\_(key):  
            raise Exception('该节点已经存在.')  
        self.ring\[key\] = node  
        self.\_sorted\_keys.append(key)

def remove\_node(self,node):  
    ''' 移除节点  
    node : 要移除的节点 '127.0.0.1:8000'  
    '''  
    for key,value in self.ring.items():  
        if value == node:  
            del self.ring\[key\]  
            self.\_sorted\_keys.remove(key)

def get\_node(self,string\_key):  
    '''获取 string\_key 所在的节点'''  
    pos = self.get\_node\_pos(string\_key)  
    if pos is None:  
        return None  
    return self.ring\[ self.\_sorted\_keys\[pos\]\].split(':')

def get\_node\_pos(self,string\_key):  
    '''获取 string\_key 所在的节点的索引'''  
    if not self.ring:  
        return None

    key = self.gen\_key\_thirty\_two(string\_key)  
    nodes = self.\_sorted\_keys  
    pos = bisect(nodes, key)  
    return pos

def gen\_key\_thirty\_two(self, key):

    m = md5\_constructor()  
    m.update(key)  
    return long(m.hexdigest(), 16)

def gen\_key\_sixteen(self,key):

    b\_key = self.\_\_hash\_digest(key)  
    return self.\_\_hash\_val(b\_key, lambda x: x)

def \_\_hash\_val(self, b\_key, entry\_fn):  
    return (( b\_key\[entry\_fn(3)\] << 24)|(b\_key\[entry\_fn(2)\] << 16)|(b\_key\[entry\_fn(1)\] << 8)| b\_key\[entry\_fn(0)\] )

def \_\_hash\_digest(self, key):  
    m = md5\_constructor()  
    m.update(key)  
    return map(ord, m.digest())

"""
nodes = [
{'host':'127.0.0.1:8000','weight':1},
{'host':'127.0.0.1:8001','weight':2},
{'host':'127.0.0.1:8002','weight':1},
]

ring = HashRing(nodes)
result = ring.get_node('98708798709870987098709879087')
print result

"""

一致性哈西

from hashlib import sha1
import os, time

create_session_id = lambda: sha1('%s%s' % (os.urandom(16), time.time())).hexdigest()

class Session(object):

session\_id = "\_\_sessionId\_\_"

def \_\_init\_\_(self, request):  
    session\_value = request.get\_cookie(Session.session\_id)  
    if not session\_value:  
        self.\_id = create\_session\_id()  
    else:  
        self.\_id = session\_value  
    request.set\_cookie(Session.session\_id, self.\_id)

def \_\_getitem\_\_(self, key):  
    # 根据 self.\_id ,在一致性哈西中找到其对应的服务器IP  
    # 找到相对应的redis服务器,如: r = redis.StrictRedis(host='localhost', port=6379, db=0)  
    # 使用python redis api 链接  
    # 获取数据,即:  
    # return self.\_redis.hget(self.\_id, name)

def \_\_setitem\_\_(self, key, value):  
    # 根据 self.\_id ,在一致性哈西中找到其对应的服务器IP  
    # 使用python redis api 链接  
    # 设置session  
    # self.\_redis.hset(self.\_id, name, value)

def \_\_delitem\_\_(self, key):  
    # 根据 self.\_id 找到相对应的redis服务器  
    # 使用python redis api 链接  
    # 删除,即:  
    return self.\_redis.hdel(self.\_id, name)

session

二、表单验证

在Web程序中往往包含大量的表单验证的工作,如:判断输入是否为空,是否符合规则。





hello


    <p>hostname: <input type="text" name="host" /> </p>  
    <p>ip: <input type="text" name="ip" /> </p>  
    <p>port: <input type="text" name="port" /> </p>  
    <p>phone: <input type="text" name="phone" /> </p>  
    <input type="submit" />  
</form>  


HTML

#!/usr/bin/env python

-*- coding:utf-8 -*-

import tornado.ioloop
import tornado.web
from hashlib import sha1
import os, time
import re

class MainForm(object):
def __init__(self):
self.host = "(.*)"
self.ip = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$"
self.port = '(\d+)'
self.phone = '^1[3|4|5|8][0-9]\d{8}$'

def check\_valid(self, request):  
    form\_dict = self.\_\_dict\_\_  
    for key, regular in form\_dict.items():  
        post\_value = request.get\_argument(key)  
        # 让提交的数据 和 定义的正则表达式进行匹配  
        ret = re.match(regular, post\_value)  
        print key,ret,post\_value

class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')
def post(self, *args, **kwargs):
obj = MainForm()
result = obj.check_valid(self)
self.write('ok')

settings = {
'template_path': 'template',
'static_path': 'static',
'static_url_prefix': '/static/',
'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh',
'login_url': '/login'
}

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

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

Python

由于验证规则可以代码重用,所以可以如此定义:

#!/usr/bin/env python

-*- coding:utf-8 -*-

import tornado.ioloop
import tornado.web
import re

class Field(object):

def \_\_init\_\_(self, error\_msg\_dict, required):  
    self.id\_valid = False  
    self.value = None  
    self.error = None  
    self.name = None  
    self.error\_msg = error\_msg\_dict  
    self.required = required

def match(self, name, value):  
    self.name = name

    if not self.required:  
        self.id\_valid = True  
        self.value = value  
    else:  
        if not value:  
            if self.error\_msg.get('required', None):  
                self.error = self.error\_msg\['required'\]  
            else:  
                self.error = "%s is required" % name  
        else:  
            ret = re.match(self.REGULAR, value)  
            if ret:  
                self.id\_valid = True  
                self.value = ret.group()  
            else:  
                if self.error\_msg.get('valid', None):  
                    self.error = self.error\_msg\['valid'\]  
                else:  
                    self.error = "%s is invalid" % name

class IPField(Field):
REGULAR = "^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$"

def \_\_init\_\_(self, error\_msg\_dict=None, required=True):

    error\_msg = {}  # {'required': 'IP不能为空', 'valid': 'IP格式错误'}  
    if error\_msg\_dict:  
        error\_msg.update(error\_msg\_dict)

    super(IPField, self).\_\_init\_\_(error\_msg\_dict=error\_msg, required=required)

class IntegerField(Field):
REGULAR = "^\d+$"

def \_\_init\_\_(self, error\_msg\_dict=None, required=True):  
    error\_msg = {'required': '数字不能为空', 'valid': '数字格式错误'}  
    if error\_msg\_dict:  
        error\_msg.update(error\_msg\_dict)

    super(IntegerField, self).\_\_init\_\_(error\_msg\_dict=error\_msg, required=required)

class CheckBoxField(Field):

def \_\_init\_\_(self, error\_msg\_dict=None, required=True):  
    error\_msg = {}  # {'required': 'IP不能为空', 'valid': 'IP格式错误'}  
    if error\_msg\_dict:  
        error\_msg.update(error\_msg\_dict)

    super(CheckBoxField, self).\_\_init\_\_(error\_msg\_dict=error\_msg, required=required)

def match(self, name, value):  
    self.name = name

    if not self.required:  
        self.id\_valid = True  
        self.value = value  
    else:  
        if not value:  
            if self.error\_msg.get('required', None):  
                self.error = self.error\_msg\['required'\]  
            else:  
                self.error = "%s is required" % name  
        else:  
            if isinstance(name, list):  
                self.id\_valid = True  
                self.value = value  
            else:  
                if self.error\_msg.get('valid', None):  
                    self.error = self.error\_msg\['valid'\]  
                else:  
                    self.error = "%s is invalid" % name

class FileField(Field):
REGULAR = "^(\w+\.pdf)|(\w+\.mp3)|(\w+\.py)$"

def \_\_init\_\_(self, error\_msg\_dict=None, required=True):  
    error\_msg = {}  # {'required': '数字不能为空', 'valid': '数字格式错误'}  
    if error\_msg\_dict:  
        error\_msg.update(error\_msg\_dict)

    super(FileField, self).\_\_init\_\_(error\_msg\_dict=error\_msg, required=required)

def match(self, name, value):  
    self.name = name  
    self.value = \[\]  
    if not self.required:  
        self.id\_valid = True  
        self.value = value  
    else:  
        if not value:  
            if self.error\_msg.get('required', None):  
                self.error = self.error\_msg\['required'\]  
            else:  
                self.error = "%s is required" % name  
        else:  
            m = re.compile(self.REGULAR)  
            if isinstance(value, list):  
                for file\_name in value:  
                    r = m.match(file\_name)  
                    if r:  
                        self.value.append(r.group())  
                        self.id\_valid = True  
                    else:  
                        self.id\_valid = False  
                        if self.error\_msg.get('valid', None):  
                            self.error = self.error\_msg\['valid'\]  
                        else:  
                            self.error = "%s is invalid" % name  
                        break  
            else:  
                if self.error\_msg.get('valid', None):  
                    self.error = self.error\_msg\['valid'\]  
                else:  
                    self.error = "%s is invalid" % name

def save(self, request, upload\_path=""):

    file\_metas = request.files\[self.name\]  
    for meta in file\_metas:  
        file\_name = meta\['filename'\]  
        with open(file\_name,'wb') as up:  
            up.write(meta\['body'\])

class Form(object):

def \_\_init\_\_(self):  
    self.value\_dict = {}  
    self.error\_dict = {}  
    self.valid\_status = True

def validate(self, request, depth=10, pre\_key=""):

    self.initialize()  
    self.\_\_valid(self, request, depth, pre\_key)

def initialize(self):  
    pass

def \_\_valid(self, form\_obj, request, depth, pre\_key):  
    """  
    验证用户表单请求的数据  
    :param form\_obj: Form对象(Form派生类的对象)  
    :param request: Http请求上下文(用于从请求中获取用户提交的值)  
    :param depth: 对Form内容的深度的支持  
    :param pre\_key: Html中name属性值的前缀(多层Form时,内部递归时设置,无需理会)  
    :return: 是否验证通过,True:验证成功;False:验证失败  
    """

    depth -= 1  
    if depth < 0:  
        return None  
    form\_field\_dict = form\_obj.\_\_dict\_\_  
    for key, field\_obj in form\_field\_dict.items():  
        print key,field\_obj  
        if isinstance(field\_obj, Form) or isinstance(field\_obj, Field):  
            if isinstance(field\_obj, Form):  
                # 获取以key开头的所有的值,以参数的形式传至  
                self.\_\_valid(field\_obj, request, depth, key)  
                continue  
            if pre\_key:  
                key = "%s.%s" % (pre\_key, key)

            if isinstance(field\_obj, CheckBoxField):  
                post\_value = request.get\_arguments(key, None)  
            elif isinstance(field\_obj, FileField):  
                post\_value = \[\]  
                file\_list = request.request.files.get(key, None)  
                for file\_item in file\_list:  
                    post\_value.append(file\_item\['filename'\])  
            else:  
                post\_value = request.get\_argument(key, None)

            print post\_value  
            # 让提交的数据 和 定义的正则表达式进行匹配  
            field\_obj.match(key, post\_value)  
            if field\_obj.id\_valid:  
                self.value\_dict\[key\] = field\_obj.value  
            else:  
                self.error\_dict\[key\] = field\_obj.error  
                self.valid\_status = False

class ListForm(object):
def __init__(self, form_type):
self.form_type = form_type
self.valid_status = True
self.value_dict = {}
self.error_dict = {}

def validate(self, request):  
    name\_list = request.request.arguments.keys() + request.request.files.keys()  
    index = 0  
    flag = False  
    while True:  
        pre\_key = "\[%d\]" % index  
        for name in name\_list:  
            if name.startswith(pre\_key):  
                flag = True  
                break  
        if flag:  
            form\_obj = self.form\_type()  
            form\_obj.validate(request, depth=10, pre\_key="\[%d\]" % index)  
            if form\_obj.valid\_status:  
                self.value\_dict\[index\] = form\_obj.value\_dict  
            else:  
                self.error\_dict\[index\] = form\_obj.error\_dict  
                self.valid\_status = False  
        else:  
            break

        index += 1  
        flag = False

class MainForm(Form):

def \_\_init\_\_(self):  
    # self.ip = IPField(required=True)  
    # self.port = IntegerField(required=True)  
    # self.new\_ip = IPField(required=True)  
    # self.second = SecondForm()  
    self.fff = FileField(required=True)  
    super(MainForm, self).\_\_init\_\_()

class SecondForm(Form):

def __init__(self):

self.ip = IPField(required=True)

self.new_ip = IPField(required=True)

super(SecondForm, self).__init__()

class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')
def post(self, *args, **kwargs):
# for i in dir(self.request):
# print i
# print self.request.arguments
# print self.request.files
# print self.request.query
# name_list = self.request.arguments.keys() + self.request.files.keys()
# print name_list

    # list\_form = ListForm(MainForm)  
    # list\_form.validate(self)  
    #  
    # print list\_form.valid\_status  
    # print list\_form.value\_dict  
    # print list\_form.error\_dict

    # obj = MainForm()  
    # obj.validate(self)  
    #  
    # print "验证结果:", obj.valid\_status  
    # print "符合验证结果:", obj.value\_dict  
    # print "错误信息:"  
    # for key, item in obj.error\_dict.items():  
    #     print key,item  
    # print self.get\_arguments('favor'),type(self.get\_arguments('favor'))  
    # print self.get\_argument('favor'),type(self.get\_argument('favor'))  
    # print type(self.get\_argument('fff')),self.get\_argument('fff')  
    # print self.request.files  
    # obj = MainForm()  
    # obj.validate(self)  
    # print obj.valid\_status  
    # print obj.value\_dict  
    # print obj.error\_dict  
    # print self.request,type(self.request)  
    # obj.fff.save(self.request)  
    # from tornado.httputil import HTTPServerRequest  
    # name\_list = self.request.arguments.keys() + self.request.files.keys()  
    # print name\_list  
    # print self.request.files,type(self.request.files)  
    # print len(self.request.files.get('fff'))

    # obj = MainForm()  
    # obj.validate(self)  
    # print obj.valid\_status  
    # print obj.value\_dict  
    # print obj.error\_dict  
    # obj.fff.save(self.request)  
    self.write('ok')

settings = {
'template_path': 'template',
'static_path': 'static',
'static_url_prefix': '/static/',
'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh',
'login_url': '/login'
}

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

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