zoukankan      html  css  js  c++  java
  • Tornado(一)

    Tornado 特点

    Tornado是一个用Python写的相对简单的、不设障碍的Web服务器架构,用以处理上万的同时的连接口,让实时的Web服务通畅起来。虽然跟现在的一些用Python写的Web架构相似,比如Django,但Tornado更注重速度,能够处理海量的同时发生的流量。 
    FriendFeed的联合创始人Bret Taylor的博客里介绍了更多,他说:把Tornado开源,FriendFeed和Facebook期望第三方能够用以建筑实时的Web服务。其具体的工作原理如上图,看起来很像是FriendFeed的评论系统。 

    Taylor认为Tornado的三个关键部分是: 
             完整的用以构建网站的基础模块。Tornado包含内置的用以解决网络开发最难和最烦的功能模块,包括模板、signed cookies、用户认证、地方化(localization)、aggressive static file caching, cross-site request forgery protection,以及类似Facebook Connect的第三方认证。开发者可以随取所需,并且自由组合,甚至把Tornado与其他架构组合。 
    实时服务。Tornado支持大量的同时发生的信息连接。用Tornado,能够通过HTTP或者Long Polling方便的书写实时服务。要知道,每一个FriendFeed的活跃用户都保持有一个连通FriendFeed服务器的开放通路。 
    高效能。Tornado比大多数用Python写的Web架构更快。根据一些实验,Tornado的速度是一般架构的4倍。

      Tornado 支持二种模式 一种是串行访问处理,异步非阻塞。当客户端访问web,tornado 可以自行定义前面2个的处理方式。天生支持RESTful

      安装

      pip install tornado

      官方:http://www.tornadoweb.org/en/stable/

    一 、HELLO word 

     1 import tornado.ioloop
     2 import tornado.web
     3 
     4 class MainHandler(tornado.web.RequestHandler):
     5     def get(self):
     6         self.write("Hello, world")
     7 
     8 def make_app():
     9     return tornado.web.Application([
    10         (r"/index", MainHandler),
    11     ])
    12 
    13 if __name__ == "__main__":
    14     app = make_app()
    15     app.listen(8888)
    16     tornado.ioloop.IOLoop.current().start()

    访问 ip:8888/index 测试内容

    配置模板

    返回html页面

    #_*_coding:utf-8_*_
    import tornado.ioloop
    import tornado.web
    
    #通过字典定义的方式定义配置文件属性,
    settings = {
        'template_path':'template',
    }
    
    
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            #self.write("Hello, world")
            #返回模板中的页面
            self.render('index.html')
    
    def make_app():
        #把setting这个配置参数带入到方法中
        return tornado.web.Application([
            (r"/index", MainHandler),],**settings)
    
    if __name__ == "__main__":
        app = make_app()
        app.listen(8888)
        tornado.ioloop.IOLoop.current().start()

    然后就可以测试页面了,非常的简单

    这里介绍render都做了什么
    class MainHandler(tornado.web.RequestHandler):
                   def get(self):

                                 1先找到template的路径

                                 2根据路径找到对应的html页面(拼接)

                                 3python open方法读取这个html页面内容,将内容读取到了内存中。实际上读出来的其实就是一个字符串

                                 4 self.write(open('html').read()) 读出这个文件然后写回(self.write方法)

                                 5self.write用socket的send方法,将字符串发送给客户端用户(这里会将这个字符串塞进一个双向队列里面,send方法回去队列中去数据然后发送给客户端),用户就可以看见web页面了(这个就是一个简单的过程)

                                 dic ={ 'name'='aaaa' }

                                  这里在说一下模板渲染,上面定义了一个字典dic。那过程就需要多加一步了。

                                 在上面4步:

                                         4open('html').read() 先把文件读取出来

                                         5渲染html页面(实际就是一个很长的字符串),替换字符串中的特殊模板语言(在html文件中也会有模板语言{{ name }},这个key可以dic中的key对应上)

                                         6self.write 

                          self.render('index.html')

                          #self.render('index.html',**dic)  渲染必须将字典当参数一起传递

    路由系统

    #_*_coding:utf-8_*_
    import tornado.ioloop
    import tornado.web
    
    #通过字典定义的方式定义配置文件属性,
    settings = {
        'template_path':'template',
    }
    
    
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            dic={
                'name':'abc'
            }
            self.render('index.html',**dic)
    
    class Home(tornado.web.RequestHandler):
        def get(self):
            self.render('home.html')
    
    class news(tornado.web.RequestHandler):
        #接受从url传递过来的参数,nid是从url中传递过来的
        def get(self,nid):
            self.write(str(nid))
    '''
     (r"/news/(d+)",news),这里也可以用正则匹配.想要当参数传递必须()中写表达式
    '''
    def make_app():
        #把setting这个配置参数带入到方法中
        return tornado.web.Application([
            (r"/index", MainHandler),
            (r"/home",Home),
            (r"/news/(d+)",news),
        ],**settings)
    
    if __name__ == "__main__":
        app = make_app()
        app.listen(8888)
        tornado.ioloop.IOLoop.current().start()

    匹配二级域名,这个是Tornado独有的,一般都是匹配xxx.xxxx.xxx/后面的参数,现在要匹配 psp.xxx.com/index  ,匹配psp 这个前缀

    #_*_coding:utf-8_*_
    import tornado.ioloop
    import tornado.web
    
    #通过字典定义的方式定义配置文件属性,
    settings = {
        'template_path':'template',
    }
    
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            dic={
                'name':'abc'
            }
            self.render('index.html',**dic)
    
    class psphandler(tornado.web.RequestHandler):
        def get(self):
            self.write("psp.tb.com/index")
    app=tornado.web.Application([
            (r"/index", MainHandler),
        ],**settings)
    
    #对app的add_handlers方法在增加一个匹配项,如果匹配上就掉对应的方法。当url过来的时候如果psp.tb.com匹配不上还是回去其它定义的规则中继续匹配
    app.add_handlers('psp.tb.com',[
        (r"/index",psphandler),
    ])
    
    if __name__ == "__main__":
        app.listen(8888)
        tornado.ioloop.IOLoop.current().start()

    测试结果:

    路由psp

     路由正常规则

    静态文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link href="{{static_url("commons.css")}}" rel="stylesheet" />
    </head>
    <body>
    <h1>index.html</h1>
    <h1>{{ name }}</h1>
    </body>
    </html>
    index.html
     1 #_*_coding:utf-8_*_
     2 import tornado.ioloop
     3 import tornado.web
     4 
     5 '''
     6 必须定义'static_path':'static',否则'static_path_prefix':'/static/',会报错误。
     7 现在不定义'static_path_prefix':'/static/',前端html文件"{{static_url("commons.css")}}"  也会对文件添加md5码。
     8 当文件更新的时候就会非常方便了,客户端也可以发现静态文件更新,再也不用手动强制更新ie缓存了
     9 '''
    10 settings = {
    11     'template_path':'template',
    12     'static_path':'static',
    13     'static_path_prefix':'/static/',
    14 }
    15 
    16 class MainHandler(tornado.web.RequestHandler):
    17     def get(self):
    18         dic={
    19             'name':'abc'
    20         }
    21         self.render('index.html',**dic)
    22 app=tornado.web.Application([
    23         (r"/index", MainHandler),
    24     ],**settings)
    25 
    26 if __name__ == "__main__":
    27     app.listen(8888)
    28     tornado.ioloop.IOLoop.current().start()

    访问展示:

    主要看静态文件后面的自己生成的md5值,然后自己改一下样式重启服务器,客户端刷新就会直接刷新缓存了。

    这里还有一个好处是前端不用管后端static文件夹的改变,后端更改路径不会影响前端

    cookie操作

    #_*_coding:utf-8_*_
    import tornado.ioloop
    import tornado.web
    
    #cookie_secret这个配置的是加"盐"的值
    settings = {
         'template_path':'template',
         'static_path':'static',
         'static_path_prefix':'/static/',
         'cookie_secret':'asdasdasda',
     }
    class HomeHandler(tornado.web.RequestHandler):
        def get(self):
             #读取客户端cookie,k1就是客户端cookie的一个字符串
             #self.set_cookie('k1')
    
             #设置cookie (参数):name, value, domain=None, expires=None, path="/",expires_days=None, **kwargs
             #path="/" 这个参数是设置cookie的作用域范围。www.xxx.com/home 如果这样写就说明这个cookie只作用在home这个url上别的url无法使用
             #set_cookie是不加密的 直接明文就传递给客户端,可以用调试工具查看到
             self.set_cookie('name','abc')
             #加密cookie 进行签名
             #self.set_secure_cookie('name','123')
             self.render('home.html')
    
    def make_app():
        return tornado.web.Application([
            (r"/home", HomeHandler),
        ],**settings)
    
    if __name__ == "__main__":
        app = make_app()
        app.listen(8888)
        tornado.ioloop.IOLoop.current().start()

    演示:

    因为这里无法同时开启self.set_cookie和self.set_secure_cookie。所以这个实验是执行完一个在换另一个。加密的还是相对比较安全的 。

    看源码是如何处理加密:

    源码:

    def set_secure_cookie(self, name, value, expires_days=30, version=None,
                              **kwargs):
            # 这里默认将超时时间设置了expires_days=30,注意看它这直接调用了不加密码的cookie方法self.set_cookie()。
    #self.create_signed_value(name, value,ersion=version)加密方法
    self.set_cookie(name, self.create_signed_value(name, value,ersion=version),xpires_days=expires_days, **kwargs)

    create_signed_value加密方法

    def create_signed_value(self, name, value, version=None):
            #获取加盐的方法self.require_setting("cookie_secret", "secure cookies")
            #在前面的setting增加这几个key就可以
            self.require_setting("cookie_secret", "secure cookies")
    
            secret = self.application.settings["cookie_secret"]
            key_version = None
            if isinstance(secret, dict):
                if self.application.settings.get("key_version") is None:
                    raise Exception("key_version setting must be used for secret_key dicts")
                key_version = self.application.settings["key_version"]
            #这个是全局的变量,虽然跟这个类中的方法名字相同。但是这个不是传给自己的   create_signed_value
            return create_signed_value(secret, name, value, version=version,
                                       key_version=key_version)
    create_signed_value
    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())))
        #用base64.b64encode加密,base64是可以反解回来的
        value = base64.b64encode(utf8(value))
        #有2个版本加密一个是v1一个v2
        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)
    全局create_signed_value
    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())
    加密V1和V2

    这样做比较安全,当黑客如果知道你的加密过程伪造cookie,Tornado会根据它的cookie先去做签名,如果签名跟服务器上的一致那么就OK。但是这里有一个重要的因素是 

    secret = self.application.settings["cookie_secret"]  这个值是定在服务器这边的 很难获取到。如果这值知道了那么久没有任何办法了。

    反解过程可以看self.get_secure_cookie() 过程都在这里面了。 

  • 相关阅读:
    密码保护
    实现搜索功能
    完成个人中心—导航标签
    个人中心标签页导航
    评论列表显示及排序,个人中心显示
    完成评论功能
    从首页问答标题到问答详情页
    首页列表显示全部问答,完成问答详情页布局。
    JavaScript Array Reduce用于数组求和
    【Angular5】 返回前一页面 go back to previous page
  • 原文地址:https://www.cnblogs.com/menkeyi/p/5961103.html
Copyright © 2011-2022 走看看