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

Tornado

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

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

複製代碼
C10K問題

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

快速入手

一、安裝Tornadohtml

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

二、第一個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爲例):vue

準備階段node

    加載配置文件python

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

    建立socket   sk = socketjquery

循環階段ios

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

運行流程:

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

 三、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 的別名

二、自定義模板

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

一、定義

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
複製代碼

二、註冊

複製代碼
#!/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
複製代碼

實用功能

一、靜態文件

複製代碼
#!/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()
複製代碼

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

簡單回顧下:

一、自動生成一段字符串

二、將字符串發送到客戶端的瀏覽器,同時把字符串當作key放在session裏。(能夠理解爲session就是一個字典)

三、在用戶的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失效策略。

二、上面是Django的原理是同樣的,那麼咱們來本身寫一個Session

2.一、儲備知識點

複製代碼
#!/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.二、代碼

複製代碼
#!/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()
複製代碼

二、自定義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

 

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

相關文章
相關標籤/搜索