Python之路【第二十篇】Tornado框架


Tornado

Tornado是使用Python編寫的一個強大的、可擴展的Web服務器。它在處理嚴峻的網絡流量時表現得足夠強健,但卻在創建和編寫時有着足夠的輕量級,並能夠被用在大量的應用和工具中。

我們現在所知道的Tornado是基於Bret Taylor和其他人員為FriendFeed所開發的網絡服務框架,當FriendFeed被Facebook收購后得以開源。不同於那些最多只能達到10,000個並發連接的傳統網絡服務器,Tornado在設計之初就考慮到了性能因素,旨在解決C10K問題,這樣的設計使得其成為一個擁有非常高性能的框架。此外,它還擁有處理安全性、用戶驗證、社交網絡以及與外部服務(如數據庫和網站API)進行異步交互的工具。

C10K問題

基於線程的服務器,如Apache,為了傳入的連接,維護了一個操作系統的線程池。Apache會為每個HTTP連接分配線程池中的一個線程,如果所有的線程都處於被占用的狀態並且尚有內存可用時,則生成一個新的線程。盡管不同的操作系統會有不同的設置,
大多數Linux發布版中都是默認線程堆大小為8MB。Apache的架構在大負載下變得不可預測,為每個打開的連接維護一個大的線程池等待數據極易迅速耗光服務器的內存資源。 大多數社交網絡應用都會展示實時更新來提醒新消息、狀態變化以及用戶通知,這就要求客戶端需要保持一個打開的連接來等待服務器端的任何響應。這些長連接或推送請求使得Apache的最大線程池迅速飽和。一旦線程池的資源耗盡,服務器將不能再響應新的請求。 異步服務器在這一場景中的應用相對較新,但他們正是被設計用來減輕基於線程的服務器的限制的。當負載增加時,諸如Node.js,lighttpd和Tornodo這樣的服務器使用協作的多任務的方式進行優雅的擴展。
也就是說,如果當前請求正在等待來自其他資源的數據(比如數據庫查詢或HTTP請求)時,一個異步服務器可以明確地控制以掛起請求。異步服務器用來恢復暫停的操作的一個常見模式是當合適的數據准備好時調用回調函數。

快速入手

1、安裝Tornado

pip install tornado
源碼安裝:https://pypi.python.org/packages/source/t/tornado/tornado-4.3.tar.gz

2、第一個Tornado程序

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luotianshuai'

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

基本上所有的WEB框架都有以下的流程(以Tornado為例):

准備階段

    加載配置文件

    加載路由映射   application = tornado.web.Application([(r"/index", MainHandler),])

    創建socket   sk = socket

循環階段

    類似socket Server不斷的循環監聽文件句柄,當有請求過來的時候,根據用戶的請求方法來來判斷是什么請求,在通過反射來執行相應的函數或類

運行流程:

第一步:執行腳本,監聽 8888 端口
第二步:瀏覽器客戶端訪問 /index  -->  http://127.0.0.1:8888/index
第三步:服務器接受請求,並交由對應的類處理該請求
第四步:類接受到請求之后,根據請求方式(post / get / delete ...)的不同調用並執行相應的方法
第五步:方法返回值的字符串內容發送瀏覽器

 3、application

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

內部在執行的時候執行了兩個方法__init__方法和self.add_handlers(".*$", handlers)方法{源碼后期解析Tornado時補充}

這個add_handlers默認傳輸的".*$" 就是www,他內部生成的路由映射的時候相當於(二級域名的方式)下圖:

我們可以通過application.add_handlers,添加一個“shuaige.com”,他會生成一個類似下面的對應關系

shuaige

.*

如果匹配的是shuaige他會去"shuaige"里去找對應關系,如果沒有匹配默認就去.*,他這個就類似Django中的URL分類!~~

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

application.add_handlers("shuaige.com",([
    (r"/index", MainHandler),
])
)

路由系統其實就是 url 和 類 的對應關系,這里不同於其他框架,其他很多框架均是 url 對應 函數,Tornado中每個url對應的是一個類。

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luotianshuai'

import tornado.ioloop
import tornado.web

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

class Shuaige(tornado.web.RedirectHandler):
    def get(self):
        self.write("This is shuaige web site,hello!")

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

application.add_handlers("shuaige.com",([
    (r"/index", Shuaige),
])
)


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

模板

Tornao中的模板語言和django中類似,模板引擎將模板文件載入內存,然后將數據嵌入其中,最終獲取到一個完整的字符串,再將字符串返回給請求者。

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

控制語句和對應的 Python 語句的格式基本完全相同。我們支持 ifforwhile 和 try,這些語句邏輯結束的位置需要用 {% end %} 做標記。還通過 extends 和 block 語句實現了模板繼承。這些在 template 模塊 的代碼文檔中有着詳細的描述。

目錄結構:

Tornado主文件:

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

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

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


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

主模板html(layout.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>shuaige</title>
    {% block css%}

    {% end %}
</head>
<body>

    <div><h1>TEST</h1></div>
    {% block htmlbody %}{% end %}

    <script src="{{static_url('js/jquery-1.8.2.min.js')}}"></script>

    {% block JavaScript %}{% end %}

</body>
</html>

子版(index.html)

{% extends 'layout.html' %}

{% block css %}
    <link href="{{static_url('css/index.css')}}" rel="stylesheet" />
{% end %}

{% block htmlbody %}
    <h1 id="shuaige" class="tim">沒有取值,就先這樣吧,循環就先不寫了.</h1>
{% end %}

{% block JavaScript %}

{% end %}

for循環使用如下:

{% 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 %}

在模板中默認提供了一些函數、字段、類以供模板使用:

  • escapetornado.escape.xhtml_escape 的別名
  • xhtml_escapetornado.escape.xhtml_escape 的別名
  • url_escapetornado.escape.url_escape 的別名
  • json_encodetornado.escape.json_encode 的別名
  • squeezetornado.escape.squeeze 的別名
  • linkifytornado.escape.linkify 的別名
  • datetime: Python 的 datetime 模組
  • handler: 當前的 RequestHandler 對象
  • requesthandler.request 的別名
  • current_userhandler.current_user 的別名
  • localehandler.locale 的別名
  • _handler.locale.translate 的別名
  • static_url: for handler.static_url 的別名
  • xsrf_form_htmlhandler.xsrf_form_html 的別名

2、自定義模板

Tornado默認提供的這些功能其實本質上就是 UIMethod 和 UIModule,我們也可以自定義從而實現類似於Django的simple_tag的功能:

1、定義

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

2、注冊

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

main.py

實用功能

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

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

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


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

在html中引用的時候

    <link href="{{static_url('css/index.css')}}" rel="stylesheet" />

目錄結構:

靜態文件緩存:

<link href="{{static_url('css/index.css')}}" rel="stylesheet" />
#這里static_url  為什么不寫路徑呢?
'''
在Django中,我們寫成變量形式的主要是為了以后擴展方便。但是在Tornado中不僅有這個功能還有一個緩存的功能
'''

原理:

拿一個靜態文件來說:/static/commons.js如果用Tornado封裝后,類似於給他加了一個版本號/static/commons.js?v=sldkfjsldf123

當客戶端訪問過來的時候會攜帶這個值,如果發現沒變客戶端緩存的靜態文件直接渲染就行了,不必再在服務器上下載一下靜態文件了。

Tornado靜態文件實現緩存源代碼:

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

2、CSRF

Tornado中的誇張請求偽造和Django中的相似,跨站偽造請求(Cross-site request forgery)

首先在index.py中啟用

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

在form表單提交的時候加上

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

Ajax在使用的時候如下

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使用時,本質上就是去獲取本地的cookie,攜帶cookie再來發送請求

自定義Session

對Session來說Tornado是沒有的,Cookie和Session的關系可以參考我之前寫的博客:http://www.cnblogs.com/luotianshuai/p/5278175.html

簡單回顧下:

1、自動生成一段字符串

2、將字符串發送到客戶端的瀏覽器,同時把字符串當做key放在session里。(可以理解為session就是一個字典)

3、在用戶的session對應的value里設置任意值

操作session

  • 獲取session:request.session[key]
  • 設置session:reqeust.session[key] = value
  • 刪除session:del request[key]
request.session.set_expiry(value)
* 如果value是個整數,session會在些秒數后失效。
* 如果value是個datatime或timedelta,session就會在這個時間后失效。
* 如果value是0,用戶關閉瀏覽器session就會失效。
* 如果value是None,session會依賴全局session失效策略。

2、上面是Django的原理是一樣的,那么我們來自己寫一個Session

2.1、儲備知識點

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

2.2、代碼

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

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

session_container = {}

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


class Session(object):

    session_id = "__sessionId__"

    def __init__(self, request):
        #當你請求過來的時候,我先去get_cookie看看有沒有cookie!目的是看看有沒有Cookie如果有的話就不生成了,沒有就生成!
        session_value = request.get_cookie(Session.session_id)
        #如果沒有Cookie生成Cookie[創建隨機字符串]
        if not session_value:
            self._id = create_session_id()
        else:
            #如果有直接將客戶端的隨機字符串設置給_id這個字段,隨機字符串封裝到self._id里了
            self._id = session_value
        #在給它設置一下
        request.set_cookie(Session.session_id, self._id)

    def __getitem__(self, key):
        ret = None
        try:
            ret =  session_container[self._id][key]
        except Exception,e:
            pass
        return ret


    def __setitem__(self, key, value):
        #判斷是否有這個隨機字符串
        if session_container.has_key(self._id):
            session_container[self._id][key] = value
        else:
            #如果沒有就生成一個字典
            '''
            類似:隨機字符串:{'IS_LOGIN':'True'}
            '''
            session_container[self._id] = {key: value}

    def __delitem__(self, key):
        del session_container[self._id][key]


class BaseHandler(tornado.web.RequestHandler):

    def initialize(self):
        '''
        這里initialize的self是誰?
        obj = LoginHandler()
        obj.initialize() ==>這里LoginHandler這個類里沒有initialize這個方法,在他父類里有
        所以initialize得self就是LoginHandler的對象
        '''
        self.my_session = Session(self) #執行Session的構造方法並且把LoginHandler的對象傳過去
        '''
        這個self.my_session = Session()
        看這個例子:
        self.xxx = '123'  在這里創建一個對象,在LoginHandler中是否可以通過self.xxx去訪問123這個值?
        可以,因為self.xxx 是在get之前執行的,他們倆的對象都是LoginHandler對象
        '''

class MainHandler(BaseHandler):

    def get(self):
        ret = self.my_session['is_login']
        if ret:
            self.write('index')
        else:
            self.redirect("/login")

class LoginHandler(BaseHandler):
    def get(self):
        '''
        當用戶訪登錄的時候我們就得給他寫cookie了,但是這里沒有寫在哪里寫了呢?
        在哪里呢?之前寫的Handler都是繼承的RequestHandler,這次繼承的是BaseHandler是自己寫的Handler
        繼承自己的類,在類了加擴展initialize! 在這里我們可以在這里做獲取用戶cookie或者寫cookie都可以在這里做
        '''
        '''
        我們知道LoginHandler對象就是self,我們可不可以self.set_cookie()可不可以self.get_cookie()
        '''
        # self.set_cookie()
        # self.get_cookie()

        self.render('login.html', **{'status': ''})

    def post(self, *args, **kwargs):
        #獲取用戶提交的用戶名和密碼
        username = self.get_argument('username')
        password = self.get_argument('pwd')
        if username == 'wupeiqi' and password == '123':
            #如果認證通過之后就可以訪問這個self.my_session對象了!然后我就就可以吧Cookie寫入到字典中了,NICE
            self.my_session['is_login'] = 'true'

            '''
            這里用到知識點是類里的:
            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'] #自動觸發執行  __getitem__
            obj['k2'] = 'wupeiqi' #自動觸發執行 __setitem__
            del obj['k1'] #自動觸發執行  __delitme__

            '''

            self.redirect('/index')
        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([
    #創建兩個URL 分別對應  MainHandler  LoginHandler
    (r"/index", MainHandler),
    (r"/login", LoginHandler),
], **settings)


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

分布式Session框架

上面通過Tornado預留擴展和Class中使用的知識實現了session框架,那么來看下如何實現分布式Session框架!

首先來看下,上面面的這個session,默認是保存在:session_container = {} 這個可不可使是Redis,memcache,mysql?如果們把session都存儲到這台機器上不管以后去增加、刪除、查詢的時候都得去這台機器上去拿

一旦這台數據掛掉,或者數據量大的時候這是這台機器就滿足不了了!

這是我們就需要考慮分布式了!比如有3台機器,然后一部分放在A機器上、一部分放在B機器上、一部分放在C機器上:如下圖:

當用戶訪問過來的時候通過權重和hash計算把session加入到hash環中的服務上,實現分不到不同的主機上存儲,當第二次user攜帶Cookie過來的時候通過計算找到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

"""

自定義模型綁定(form框架)

在python中只有Django中提供了form框架,最強的是微軟的vs.net mvc。那咱們自己寫一個form框架!更深入的理解form的實現!

用戶在輸入表單的時候首先要對用戶的輸入進行檢測。

已Django為例,咱們創建一個Form類。看下面Django的流程

用戶 ==》reques.post ==>obj = Form(數據) ==> obj.is_valid()

問題:request在獲取用戶輸入的內容的時候他知道有多少個嗎?當然不知道,Form表單內有可能有一個有可能有多個內容。所以我們通過reques這個方式不太方便

那咱們看下下面的方式:

class Foo(object):
    def __init__(self):
        self.username = 'shuaige'#這個值可以通過request取出來
        self.password = 'shuaige666'#這個值可以通過request取出來

obj = Foo()

for k,v in obj.__dict__.items():
    request.POST[key]

咱們循環咱們自己定義的取值,這樣是已咱們自己為主,你前端傳多少值我不管了就!NICE

 簡單例子代碼:

index.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <link href="{{static_url("commons.css")}}" rel="stylesheet" />
</head>
<body>
    <h1>hello</h1>
    <form action="/index" method="post">

        <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>
</body>
</html>

index.py

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

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

#創建一個form類
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():
            '''
            通過request.get_argument()來獲取用戶穿過來的值
            在循環的時候不需要關心用戶傳過來多少值,我們循環我們類中的字段,以我們的為准
            '''
            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()

2、自定義Form升級版

回顧下Django在我們定義Form表單的時候是不是一個Form表單,對於一個Form類?那is_valid()這個參數有必每次都寫嗎?如果不寫怎么實現?

基類(父類)  == 其派生類(子類繼承父類),並且還可以在父類中預留擴展方法!!!!!!!!

還有就是在不同form中的有沒有可能他們使用的正則是相同的呢?比如Django中的forms.GenericIPAddressField()我們可以參考他來做。

我們寫一個

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}$"

這樣在每個Form里直接引用就行了不用每個Form里都寫這個正則了

class MainForm(BaseForm):
    def __init__(self):
        self.host = "().*"
        self.ip = IPField()

還有一個需要注意的是is_valid()在Django中我們是不是可以自定如果錯誤的提示,然后把錯誤返回給用戶?類似Django中的返回的error

 

3、ListForm

咱們寫Form表單的時候,是寫一個類。這個類里有一個或者多個字段。

class LoginForm(forms.Form):
    username = forms.EmailField(
        error_messages={'required':u'郵箱賬戶不能為空'},
        widget=forms.widgets.EmailInput(attrs={'class': "form-control", "placeholder":"郵箱"})
    )
    password = forms.CharField(error_messages={'required':u'密碼不能為空'},
        widget=forms.widgets.PasswordInput(attrs={'class': "form-control", "placeholder":"密碼"})
    )

那我們在后台調用的時候他是一個類的對象,我們給他綁定到前端,他在前端展示的時候代表的是一行數據。也就是一個對象

如果想提交多個用戶名和密碼的話是提交多個對象,這樣Form是處理不了的:(下面是Django的Form代碼)

def login(request):
    #獲取用戶輸入
    login_form = AccountForm.LoginForm(request.POST)
    if request.method == 'POST':
        #判斷用戶輸入是否合法
        if login_form.is_valid():#如果用戶輸入是合法的
            username = request.POST.get('username')
            password = request.POST.get('password')
            if models.UserInfo.objects.get(username=username) and models.UserInfo.objects.get(username=username).password == password:
                    request.session['auth_user'] = username
                    return redirect('/index/')
            else:
                return render(request,'account/login.html',{'model': login_form,'backend_autherror':'用戶名或密碼錯誤'})
        else:
            error_msg = login_form.errors.as_data()
            return render(request,'account/login.html',{'model': login_form,'errors':error_msg})

    # 如果登錄成功,寫入session,跳轉index
    return render(request, 'account/login.html', {'model': login_form})

如果想處理多個的話,就得把用戶輸入的多個用戶名和密碼生成一個對象列表然后循環進行判斷

完整代碼如下:

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

 

更多參考:http://www.cnblogs.com/wupeiqi/articles/5341480.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM