Django:之不得不說的web框架們

python的web框架css

Bottlehtml

Bpttle是一個快速、簡潔、輕量級的基於WSIG的微型web框架,此框架只有一個.py文件,除了python的標準庫外,其不依賴任何其它模塊。node

pip install bottle
easy_install bottle
apt-get install python-bottle
wget http://bottlepy.org/bottle.py

Bottle框架大體能夠分爲如下部分:python

  • 路由系統,將不一樣請求交由指定函數處理
  • 模版系統,將模版中的特殊語法渲染成字符串,值得一說的是Bottle的模版引擎能夠任意指定:Bottle內置模版、mako、jinja二、cheetah
  • 公共組件,用於提供處理請求相關的信息,如:表單數據、cookies、請求頭等  
  • 服務,Bottle默認支持多種基於WSGI的服務,如:
 1 server_names = {
 2     'cgi': CGIServer,
 3     'flup': FlupFCGIServer,
 4     'wsgiref': WSGIRefServer,
 5     'waitress': WaitressServer,
 6     'cherrypy': CherryPyServer,
 7     'paste': PasteServer,
 8     'fapws3': FapwsServer,
 9     'tornado': TornadoServer,
10     'gae': AppEngineServer,
11     'twisted': TwistedServer,
12     'diesel': DieselServer,
13     'meinheld': MeinheldServer,
14     'gunicorn': GunicornServer,
15     'eventlet': EventletServer,
16     'gevent': GeventServer,
17     'geventSocketIO':GeventSocketIOServer,
18     'rocket': RocketServer,
19     'bjoern' : BjoernServer,
20     'auto': AutoServer,
21 }
View Code

框架的基本使用jquery

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from bottle import template, Bottle
root = Bottle()
 
@root.route('/hello/')
def index():
    return "Hello World"
    # return template('<b>Hello {{name}}</b>!', name="Alex")
 
root.run(host='localhost', port=8080)

1、路由系統git

路由系統是的url對應指定函數,當用戶請求某個url時,就由指定函數處理當前請求,對於Bottle的路由系統能夠分爲一下幾類:程序員

  • 靜態路由
  • 動態路由
  • 請求方法路由
  • 二級路由

一、靜態路由github

@root.route('/hello/')
def index():
    return template('<b>Hello {{name}}</b>!', name="wulaoer")

二、動態路由web

@root.route('/wiki/<pagename>')
def callback(pagename):
    ...
 
@root.route('/object/<id:int>')
def callback(id):
    ...
 
@root.route('/show/<name:re:[a-z]+>')
def callback(name):
    ...
 
@root.route('/static/<path:path>')
def callback(path):
    return static_file(path, root='static')

三、請求方法路由正則表達式

@root.route('/hello/', method='POST')
def index():
    ...
 
@root.get('/hello/')
def index():
    ...
 
@root.post('/hello/')
def index():
    ...
 
@root.put('/hello/')
def index():
    ...
 
@root.delete('/hello/')
def index():
    ...

四、二級路由

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from bottle import template, Bottle

app01 = Bottle()

@app01.route('/hello/', method='GET')
def index():
    return template('<b>App01</b>!')
app1.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from bottle import template, Bottle

app02 = Bottle()


@app02.route('/hello/', method='GET')
def index():
    return template('<b>App02</b>!')
app02.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from bottle import template, Bottle
from bottle import static_file
root = Bottle()
 
@root.route('/hello/')
def index():
    return template('<b>Root {{name}}</b>!', name="Alex")
 
from framwork_bottle import app01
from framwork_bottle import app02
 
root.mount('app01', app01.app01)
root.mount('app02', app02.app02)
 
root.run(host='localhost', port=8080)

2、模板系統

模板系統用於將Html和自定的值二者進行渲染,從而獲得字符串,而後將該字符串返回給客戶端。咱們知道在Bottle中可使用 內置模板系統、makojinja2cheetah等,之內置模板系統爲例:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <h1>{{name}}</h1>
</body>
</html>
hello_template.tpl
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from bottle import template, Bottle
root = Bottle()
 
@root.route('/hello/')
def index():
    # 默認狀況下去目錄:['./', './views/']中尋找模板文件 hello_template.html
    # 配置在 bottle.TEMPLATE_PATH 中
    return template('hello_template.tpl', name='alex')
 
root.run(host='localhost', port=8080)

一、語法

  • 單值
  • 單行Python代碼
  • Python代碼快
  • Python、Html混合
<h1>1、單值</h1>
{{name}}
 
<h1>2、單行Python代碼</h1>
% s1 = "hello"
 
 
<h1>3、Python代碼塊</h1>
<%
    # A block of python code
    name = name.title().strip()
    if name == "Alex":
        name="seven"
%>
 
 
<h1>4、Python、Html混合</h1>
 
% if True:
    <span>{{name}}</span>
% end
<ul>
  % for item in name:
    <li>{{item}}</li>
  % end
</ul>
View Code

二、函數 

include(sub_template, **variables)

# 導入其餘模板文件
 
% include('header.tpl', title='Page Title')
Page Content
% include('footer.tpl')

rebase(name, **variables)

<html>
<head>
  <title>{{title or 'No title'}}</title>
</head>
<body>
  {{!base}}
</body>
</html>
base.tpl
# 導入母版
 
% rebase('base.tpl', title='Page Title')
<p>Page Content ...</p>

defined(name)

# 檢查當前變量是否已經被定義,已定義True,未定義False

get(name, default=None)

# 獲取某個變量的值,不存在時可設置默認值

setdefault(name, default)

# 若是變量不存在時,爲變量設置默認值

擴展:自定義函數

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <h1>自定義函數</h1>
    {{ wupeiqi() }}

</body>
</html>
hello_template.tpl
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from bottle import template, Bottle,SimpleTemplate
root = Bottle()


def custom():
    return '123123'


@root.route('/hello/')
def index():
    # 默認狀況下去目錄:['./', './views/']中尋找模板文件 hello_template.html
    # 配置在 bottle.TEMPLATE_PATH 中
    return template('hello_template.html', name='alex', wupeiqi=custom)

root.run(host='localhost', port=8080)
main.py

注:變量或函數前添加 【 ! 】,則會關閉轉義的功能

3、公共組件

因爲Web框架就是用來【接收用戶請求】-> 【處理用戶請求】-> 【響應相關內容】,對於具體如何處理用戶請求,開發人員根據用戶請求來進行處理,而對於接收用戶請求和相應相關的內容均交給框架自己來處理,其處理完成以後將產出交給開發人員和用戶。

【接收用戶請求】

當框架接收到用戶請求以後,將請求信息封裝在Bottle的request中,以供開發人員使用

【響應相關內容】

當開發人員的代碼處理完用戶請求以後,會將其執行內容相應給用戶,相應的內容會封裝在Bottle的response中,而後再由框架將內容返回給用戶

因此,公共組件本質其實就是爲開發人員提供接口,使其可以獲取用戶信息並配置響應內容。

一、request

Bottle中的request實際上是一個LocalReqeust對象,其中封裝了用戶請求的相關信息:

 1 request.headers
 2     請求頭信息
 3  
 4 request.query
 5     get請求信息
 6  
 7 request.forms
 8     post請求信息
 9  
10 request.files
11     上傳文件信息
12  
13 request.params
14     get和post請求信息
15  
16 request.GET
17     get請求信息
18  
19 request.POST
20     post和上傳信息
21  
22 request.cookies
23     cookie信息
24      
25 request.environ
26     環境相關相關
View Code

二、response

Bottle中的request實際上是一個LocalResponse對象,其中框架即將返回給用戶的相關信息:

 1 response
 2     response.status_line
 3         狀態行
 4  
 5     response.status_code
 6         狀態碼
 7  
 8     response.headers
 9         響應頭
10  
11     response.charset
12         編碼
13  
14     response.set_cookie
15         在瀏覽器上設置cookie
16          
17     response.delete_cookie
18         在瀏覽器上刪除cookie
View Code

實例:

from bottle import route, request

@route('/login')
def login():
    return '''
        <form action="/login" method="post">
            Username: <input name="username" type="text" />
            Password: <input name="password" type="password" />
            <input value="Login" type="submit" />
        </form>
    '''

@route('/login', method='POST')
def do_login():
    username = request.forms.get('username')
    password = request.forms.get('password')
    if check_login(username, password):
        return "<p>Your login information was correct.</p>"
    else:
        return "<p>Login failed.</p>"
基本Form請求
<form action="/upload" method="post" enctype="multipart/form-data">
  Category:      <input type="text" name="category" />
  Select a file: <input type="file" name="upload" />
  <input type="submit" value="Start upload" />
</form>


@route('/upload', method='POST')
def do_upload():
    category   = request.forms.get('category')
    upload     = request.files.get('upload')
    name, ext = os.path.splitext(upload.filename)
    if ext not in ('.png','.jpg','.jpeg'):
        return 'File extension not allowed.'

    save_path = get_save_path_for_category(category)
    upload.save(save_path) # appends upload.filename automatically
    return 'OK'
上傳文件

4、服務

對於Bottle框架其自己未實現相似於Tornado本身基於socket實現Web服務,因此必須依賴WSGI,默認Bottle已經實現而且支持的WSGI有:

 

server_names = {
    'cgi': CGIServer,
    'flup': FlupFCGIServer,
    'wsgiref': WSGIRefServer,
    'waitress': WaitressServer,
    'cherrypy': CherryPyServer,
    'paste': PasteServer,
    'fapws3': FapwsServer,
    'tornado': TornadoServer,
    'gae': AppEngineServer,
    'twisted': TwistedServer,
    'diesel': DieselServer,
    'meinheld': MeinheldServer,
    'gunicorn': GunicornServer,
    'eventlet': EventletServer,
    'gevent': GeventServer,
    'geventSocketIO':GeventSocketIOServer,
    'rocket': RocketServer,
    'bjoern' : BjoernServer,
    'auto': AutoServer,
}
WSGI

使用時,只需在主app執行run方法時指定參數便可:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from bottle import Bottle
root = Bottle()
 
@root.route('/hello/')
def index():
    return "Hello World"
# 默認server ='wsgiref'
root.run(host='localhost', port=8080, server='wsgiref')

默認server="wsgiref",即:使用Python內置模塊wsgiref,若是想要使用其餘時,則須要首先安裝相關類庫,而後才能使用。如:

# 若是使用Tornado的服務,則須要首先安裝tornado才能使用

class TornadoServer(ServerAdapter):
    """ The super hyped asynchronous server by facebook. Untested. """
    def run(self, handler): # pragma: no cover
        # 導入Tornado相關模塊
        import tornado.wsgi, tornado.httpserver, tornado.ioloop
        container = tornado.wsgi.WSGIContainer(handler)
        server = tornado.httpserver.HTTPServer(container)
        server.listen(port=self.port,address=self.host)
        tornado.ioloop.IOLoop.instance().start()
bottle.py源碼

PS:以上WSGI中提供了19種,若是想要使期支持其餘服務,則須要擴展Bottle源碼來自定義一個ServerAdapter

更多參見:http://www.bottlepy.org/docs/dev/index.html

Flash

Flask是一個基於Python開發而且依賴jinja2模板和Werkzeug WSGI服務的一個微型框架,對於Werkzeug本質是Socket服務端,其用於接收http請求並對請求進行預處理,而後觸發Flask框架,開發人員基於Flask框架提供的功能對請求進行相應的處理,並返回給用戶,若是要返回給用戶複雜的內容時,須要藉助jinja2模板來實現對模板的處理,即:將模板和數據進行渲染,將渲染後的字符串返回給用戶瀏覽器。

「微」(micro) 並不表示你須要把整個 Web 應用塞進單個 Python 文件(雖然確實能夠 ),也不意味着 Flask 在功能上有所欠缺。微框架中的「微」意味着 Flask 旨在保持核心簡單而易於擴展。Flask 不會替你作出太多決策——好比使用何種數據庫。而那些 Flask 所選擇的——好比使用何種模板引擎——則很容易替換。除此以外的一切都由可由你掌握。如此,Flask 能夠與您珠聯璧合。

默認狀況下,Flask 不包含數據庫抽象層、表單驗證,或是其它任何已有多種庫能夠勝任的功能。然而,Flask 支持用擴展來給應用添加這些功能,如同是 Flask 自己實現的同樣。衆多的擴展提供了數據庫集成、表單驗證、上傳處理、各類各樣的開放認證技術等功能。Flask 也許是「微小」的,但它已準備好在需求繁雜的生產環境中投入使用。

安裝

pip install Flask
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from werkzeug.wrappers import Request, Response

@Request.application
def hello(request):
    return Response('Hello World!')

if __name__ == '__main__':
    from werkzeug.serving import run_simple
    run_simple('localhost', 4000, hello)
werkzeug

1、第一次

from flask import Flask
app = Flask(__name__)
 
@app.route("/")
def hello():
    return "Hello World!"
 
if __name__ == "__main__":
    app.run()

2、路由系統

  • @app.route('/user/<username>')
  • @app.route('/post/<int:post_id>')
  • @app.route('/post/<float:post_id>')
  • @app.route('/post/<path:path>')
  • @app.route('/login', methods=['GET', 'POST'])

經常使用路由系統有以上五種,全部的路由系統都是基於一下對應關係來處理:

DEFAULT_CONVERTERS = {
    'default':          UnicodeConverter,
    'string':           UnicodeConverter,
    'any':              AnyConverter,
    'path':             PathConverter,
    'int':              IntegerConverter,
    'float':            FloatConverter,
    'uuid':             UUIDConverter,
}

注:對於Flask默認不支持直接寫正則表達式的路由,不過能夠經過自定義來實現,見:https://segmentfault.com/q/1010000000125259

3、模板

一、模板的使用

Flask使用的是Jinja2模板,因此其語法和Django無差異

二、自定義模板方法

Flask中自定義模板方法的方式和Bottle類似,建立一個函數並經過參數的形式傳入render_template,如:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <h1>自定義函數</h1>
    {{ww()|safe}}

</body>
</html>
index.html
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask,render_template
app = Flask(__name__)
 
 
def wupeiqi():
    return '<h1>Wupeiqi</h1>'
 
@app.route('/login', methods=['GET', 'POST'])
def login():
    return render_template('login.html', ww=wupeiqi)
 
app.run()

4、公共組件

一、請求

對於Http請求,Flask會講請求信息封裝在request中(werkzeug.wrappers.BaseRequest),提供的以下經常使用方法和字段以供使用:

request.method
request.args
request.form
request.values
request.files
request.cookies
request.headers
request.path
request.full_path
request.script_root
request.url
request.base_url
request.url_root
request.host_url
request.host
@app.route('/login', methods=['POST', 'GET'])
def login():
    error = None
    if request.method == 'POST':
        if valid_login(request.form['username'],
                       request.form['password']):
            return log_the_user_in(request.form['username'])
        else:
            error = 'Invalid username/password'
    # the code below is executed if the request method
    # was GET or the credentials were invalid
    return render_template('login.html', error=error)
表單處理Demo
from flask import request
from werkzeug import secure_filename

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/' + secure_filename(f.filename))
    ...
上傳文件Demo
from flask import request

@app.route('/setcookie/')
def index():
    username = request.cookies.get('username')
    # use cookies.get(key) instead of cookies[key] to not get a
    # KeyError if the cookie is missing.




from flask import make_response

@app.route('/getcookie')
def index():
    resp = make_response(render_template(...))
    resp.set_cookie('username', 'the username')
    return resp
Cookie操做

二、響應

當用戶請求被開發人員的邏輯處理完成以後,會將結果發送給用戶瀏覽器,那麼就須要對請求作出相應的響應。

a.字符串

@app.route('/index/', methods=['GET', 'POST'])
def index():
    return "index"

b.模板引擎

from flask import Flask,render_template,request
app = Flask(__name__)
 
@app.route('/index/', methods=['GET', 'POST'])
def index():
    return render_template("index.html")
 
app.run()

c.重定向

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, redirect, url_for
app = Flask(__name__)
 
@app.route('/index/', methods=['GET', 'POST'])
def index():
    # return redirect('/login/')
    return redirect(url_for('login'))
 
@app.route('/login/', methods=['GET', 'POST'])
def login():
    return "LOGIN"
 
app.run()

d.錯誤頁面

from flask import Flask, abort, render_template
app = Flask(__name__)

@app.route('/e1/', methods=['GET', 'POST'])
def index():
    abort(404, 'Nothing')
app.run()
指定URL,簡單錯誤
from flask import Flask, abort, render_template
app = Flask(__name__)
 
@app.route('/index/', methods=['GET', 'POST'])
def index():
    return "OK"
 
@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404
 
app.run()

e.設置相應信息

使用make_response能夠對相應的內容進行操做

from flask import Flask, abort, render_template,make_response
app = Flask(__name__)
 
@app.route('/index/', methods=['GET', 'POST'])
def index():
    response = make_response(render_template('index.html'))
    # response是flask.wrappers.Response類型
    # response.delete_cookie
    # response.set_cookie
    # response.headers['X-Something'] = 'A value'
    return response
 
app.run()

三、Session

除請求對象以外,還有一個 session 對象。它容許你在不一樣請求間存儲特定用戶的信息。它是在 Cookies 的基礎上實現的,而且對 Cookies 進行密鑰簽名要使用會話,你須要設置一個密鑰。

  • 設置:session['username'] = 'xxx'

  • 刪除:session.pop('username', None)
from flask import Flask, session, redirect, url_for, escape, request
 #導入模塊
app = Flask(__name__)#定義app的url
 
@app.route('/')#url路徑
def index():#定義index函數
    if 'username' in session:#斷定用戶是否存在
        return 'Logged in as %s' % escape(session['username'])#返回用戶登陸名
    return 'You are not logged in'#返回用戶登陸信息
 
@app.route('/login', methods=['GET', 'POST'])#定義登陸方式
def login():#定義登陸函數
    if request.method == 'POST':#斷定登陸方式
        session['username'] = request.form['username']#斷定用戶名
        return redirect(url_for('index'))#跳轉頁面
    return '''
        <form action="" method="post">#登陸方式
            <p><input type=text name=username>#用戶輸入框
            <p><input type=submit value=Login>#登陸鍵
        </form>
    '''
 
@app.route('/logout')#退出
def logout():#退出函數
    # remove the username from the session if it's there
    session.pop('username', None)#用戶不存在
    return redirect(url_for('index'))#用戶推出跳轉到的頁面
 
# set the secret key.  keep this really secret:
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'#生成的祕藥

Flask還有衆多其餘功能,更多參見:
    http://docs.jinkan.org/docs/flask/
    http://flask.pocoo.org/

Tornado

Tornado 是 FriendFeed 使用的可擴展的非阻塞式 web 服務器及其相關工具的開源版本。這個 Web 框架看起來有些像web.py 或者 Google 的 webapp,不過爲了能有效利用非阻塞式服務器環境,這個 Web 框架還包含了一些相關的有用工具 和優化。

Tornado 和如今的主流 Web 服務器框架(包括大多數 Python 的框架)有着明顯的區別:它是非阻塞式服務器,並且速度至關快。得利於其 非阻塞的方式和對 epoll 的運用,Tornado 每秒能夠處理數以千計的鏈接,這意味着對於實時 Web 服務來講,Tornado 是一個理想的 Web 框架。咱們開發這個 Web 服務器的主要目的就是爲了處理 FriendFeed 的實時功能 ——在 FriendFeed 的應用裏每個活動用戶都會保持着一個服務器鏈接。(關於如何擴容 服務器,以處理數以千計的客戶端的鏈接的問題,請參閱 C10K problem。)

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

1、快速上手

#!/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()
異步非阻塞實例

2、路由系統

路由系統其實就是 url 和 類 的對應關係,這裏不一樣於其餘框架,其餘不少框架均是 url 對應 函數,Tornado中每一個url對應的是一個類。

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

3、模板

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

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

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

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>老男孩</title>
    <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
    {% block CSS %}{% end %}
</head>
<body>

    <div class="pg-header">

    </div>
    
    {% block RenderBody %}{% end %}
   
    <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>
    
    {% block JavaScript %}{% end %}
</body>
</html>

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

{% block RenderBody %}
    <h1>Index</h1>

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

{% end %}

{% block JavaScript %}
    
{% end %}
index.html
#!/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',
}
  
application = tornado.web.Application([
    (r"/index", MainHandler),
], **settings)
  
  
if __name__ == "__main__":
    application.listen(80)
    tornado.ioloop.IOLoop.instance().start()

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

  • 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的功能:
一、定義

# 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

二、註冊

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

三、使用

<!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>
    {% module custom(123) %}
    {{ tab() }}
</body>
index.html

4、實用功能

一、靜態文件

對於靜態文件,能夠配置靜態文件的目錄和前段使用時的前綴,而且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()
main.py
<!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>
</body>
</html>
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()
靜態文件緩存源碼

二、csrf

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

settings = {
    "xsrf_cookies": True,
}
application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/login", LoginHandler),
], **settings)
配置
<form action="/new_message" method="post">
  {{ xsrf_form_html() }}
  <input type="text" name="message"/>
  <input type="submit" value="Post"/>
</form>
普通表單使用

注:Ajax使用時,本質上就是去獲取本地的cookie,攜帶cookie再來發送請求

三、cookie

Tornado中能夠對cookie進行操做,而且還能夠對cookie進行簽名以放置僞造。

a、基本操做

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!")
Code

b、簽名

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=")
Code
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()
Demo-基於cookie進行用戶驗證
#!/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()
Demo-Toando內部提供基於cookie進行用戶驗證

5、擴展功能

一、自定義Session

a.知識儲備

#!/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']
View Code

b.session實現機制

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3   
 4 import tornado.ioloop
 5 import tornado.web
 6 from hashlib import sha1
 7 import os, time
 8   
 9 session_container = {}
10   
11 create_session_id = lambda: sha1('%s%s' % (os.urandom(16), time.time())).hexdigest()
12   
13   
14 class Session(object):
15   
16     session_id = "__sessionId__"
17   
18     def __init__(self, request):
19         session_value = request.get_cookie(Session.session_id)
20         if not session_value:
21             self._id = create_session_id()
22         else:
23             self._id = session_value
24         request.set_cookie(Session.session_id, self._id)
25   
26     def __getitem__(self, key):
27         return session_container[self._id][key]
28   
29     def __setitem__(self, key, value):
30         if session_container.has_key(self._id):
31             session_container[self._id][key] = value
32         else:
33             session_container[self._id] = {key: value}
34   
35     def __delitem__(self, key):
36         del session_container[self._id][key]
37   
38   
39 class BaseHandler(tornado.web.RequestHandler):
40   
41     def initialize(self):
42         # my_session['k1']訪問 __getitem__ 方法
43         self.my_session = Session(self)
44   
45   
46 class MainHandler(BaseHandler):
47   
48     def get(self):
49         print self.my_session['c_user']
50         print self.my_session['c_card']
51         self.write('index')
52   
53 class LoginHandler(BaseHandler):
54   
55     def get(self):
56         self.render('login.html', **{'status': ''})
57   
58     def post(self, *args, **kwargs):
59   
60         username = self.get_argument('name')
61         password = self.get_argument('pwd')
62         if username == 'wupeiqi' and password == '123':
63   
64             self.my_session['c_user'] = 'wupeiqi'
65             self.my_session['c_card'] = '12312312309823012'
66   
67             self.redirect('/index')
68         else:
69             self.render('login.html', **{'status': '用戶名或密碼錯誤'})
70   
71 settings = {
72     'template_path': 'template',
73     'static_path': 'static',
74     'static_url_prefix': '/static/',
75     'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh',
76     'login_url': '/login'
77 }
78   
79 application = tornado.web.Application([
80     (r"/index", MainHandler),
81     (r"/login", LoginHandler),
82 ], **settings)
83   
84   
85 if __name__ == "__main__":
86     application.listen(8888)
87     tornado.ioloop.IOLoop.instance().start()
View Code

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

二、自定義模型版定

模型綁定有兩個主要功能:

  • 自動生成html表單
  • 用戶輸入驗證

在以前學習的Django中爲程序員提供了很是便捷的模型綁定功能,可是在Tornado中,一切須要本身動手!!!

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

因爲請求的驗證時,須要考慮是否能夠爲空以及正則表達式的複用,因此:

#!/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()
Form驗證框架
相關文章
相關標籤/搜索