• Django:之不得不说的web框架们


    python的web框架

    Bottle

    Bpttle是一个快速、简洁、轻量级的基于WSIG的微型web框架,此框架只有一个.py文件,除了python的标准库外,其不依赖任何其它模块。

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

    Bottle框架大致可以分为以下部分:

    • 路由系统,将不同请求交由指定函数处理
    • 模版系统,将模版中的特殊语法渲染成字符串,值得一说的是Bottle的模版引擎可以任意指定:Bottle内置模版、mako、jinja2、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

    框架的基本使用

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

    一、路由系统

    路由系统是的url对应指定函数,当用户请求某个url时,就由指定函数处理当前请求,对于Bottle的路由系统可以分为一下几类:

    • 静态路由
    • 动态路由
    • 请求方法路由
    • 二级路由

    1、静态路由

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

    2、动态路由

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

    3、请求方法路由

    @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():
        ...
    

    4、二级路由

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

    二、模板系统

    模板系统用于将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)
    

    1、语法

    • 单值
    • 单行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

    2、函数 

    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

    注:变量或函数前添加 【 ! 】,则会关闭转义的功能

    三、公共组件

    由于Web框架就是用来【接收用户请求】-> 【处理用户请求】-> 【响应相关内容】,对于具体如何处理用户请求,开发人员根据用户请求来进行处理,而对于接收用户请求和相应相关的内容均交给框架本身来处理,其处理完成之后将产出交给开发人员和用户。

    【接收用户请求】

    当框架接收到用户请求之后,将请求信息封装在Bottle的request中,以供开发人员使用

    【响应相关内容】

    当开发人员的代码处理完用户请求之后,会将其执行内容相应给用户,相应的内容会封装在Bottle的response中,然后再由框架将内容返回给用户

    所以,公共组件本质其实就是为开发人员提供接口,使其能够获取用户信息并配置响应内容。

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

    2、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'
    上传文件

    四、服务

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

    一、第一次

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

    二、路由系统

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

    三、模板

    1、模板的使用

    Flask使用的是Jinja2模板,所以其语法和Django无差别

    2、自定义模板方法

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

    四、公共组件

    1、请求

    对于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操作

    2、响应

    当用户请求被开发人员的逻辑处理完成之后,会将结果发送给用户浏览器,那么就需要对请求做出相应的响应。

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

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

    一、快速上手

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
      
    import tornado.ioloop
    import tornado.web
      
      
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.write("Hello, world")
      
    application = tornado.web.Application([
        (r"/index", MainHandler),
    ])
      
      
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()
    

    第一步:执行脚本,监听 8888 端口

    第二步:浏览器客户端访问 /index  -->  http://127.0.0.1:8888/index

    第三步:服务器接受请求,并交由对应的类处理该请求

    第四步:类接受到请求之后,根据请求方式(post / get / delete ...)的不同调用并执行相应的方法

    第五步:方法返回值的字符串内容发送浏览器

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    import tornado.ioloop
    import tornado.web
    from tornado import httpclient
    from tornado.web import asynchronous
    from tornado import gen
    
    import uimodules as md
    import uimethods as mt
    
    class MainHandler(tornado.web.RequestHandler):
            @asynchronous
            @gen.coroutine
            def get(self):
                print 'start get '
                http = httpclient.AsyncHTTPClient()
                http.fetch("http://127.0.0.1:8008/post/", self.callback)
                self.write('end')
    
            def callback(self, response):
                print response.body
    
    settings = {
        'template_path': 'template',
        'static_path': 'static',
        'static_url_prefix': '/static/',
        'ui_methods': mt,
        'ui_modules': md,
    }
    
    application = tornado.web.Application([
        (r"/index", MainHandler),
    ], **settings)
    
    
    if __name__ == "__main__":
        application.listen(8009)
        tornado.ioloop.IOLoop.instance().start()
    异步非阻塞实例

    二、路由系统

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

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

    三、模板

    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的功能:
    1、定义

    # 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

    2、注册

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

    3、使用

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

    四、实用功能

    1、静态文件

    对于静态文件,可以配置静态文件的目录和前段使用时的前缀,并且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()
    静态文件缓存源码

    2、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再来发送请求

    3、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进行用户验证

    五、扩展功能

    1、自定义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

    2、自定义模型版定

    模型绑定有两个主要功能:

    • 自动生成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验证框架
  • 相关阅读:
    内存队列使用Channels
    笔记20210101mongodb
    管道式的开发模式
    企业级应用架构设计
    再入历史旧坑
    路径问题 再次记录
    mongdb驱动的问题
    使用Bumblebee记录
    我和小兔子不得不说的消息v2
    流程设计器jQuery + svg/vml(Demo7
  • 原文地址:https://www.cnblogs.com/wulaoer/p/5405943.html
Copyright © 2020-2023  润新知