zoukankan      html  css  js  c++  java
  • Tornado框架入门

    关于Tornado框架的个人学习心得。因为在学习Tornado前已经学习了Django。所以在这里许多知识点我没有特别详细的说明并且会跟与django做对比去学习!

    Tornado框架

    Tornado其实是一个十分轻量级的web服务器框架,组件十分的少,学习起来十分的轻松简单。因为tornado提供的开发功能并不强大,所以许多web开发中常用的功能组件都需要自己去写而不像django一样提供许多功能强大的组件直接供我们调用(如ORM、FROM表单验证和Session等等)。而Tornado最强大的一点是它的异步非阻塞功能!
    所以本篇除了介绍它的基本操作外,还会自定义一些在web开发中常常需要用到的组件。而对于异步非阻塞的功能我在进阶篇中再去描述自己的一些愚见

    安装:pip install tornado

    基本操作

    web服务器框架的基本操作简单说就是由路由系统接收用户发来的请求,并把请求转发给视图函数(C)处理,在视图处理过程中可能会涉及调用数据库操作(M),然后由视图函数交给模板引擎(V)渲染最后返回给用户。这就是web应用非常流行的MVC设计模式。学习tornado我们也是需要从这几个功能入手学习!

    快速上手

    先简单的快速上手Tornado的使用,使用Tornado大致上就是执行处理以下这些事情:

    1. 第一步:执行脚本,监听 8888 端口
    2. 第二步:浏览器客户端访问 /index --> http://127.0.0.1:8888/index
    3. 第三步:服务器接受请求,并交由对应的类处理该请求
    4. 第四步:类接受到请求之后,根据请求方式(post / get / delete ...)的不同调用并执行相应的方法
    5. 第五步:方法返回值的字符串内容发送浏览器
    #!/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") #相当于diango的return Httpresponse,即返回响应
            #self.render('模板名') #去指定的模板路径去读取模板返回给前端
            #self.redirect('URI') #跳转
    
    settings = {
        "template_path":'views',
    } # 配置模板的路径(默认从当前执行文件目录去找,但是我们一般把模板放在views目录中)
    
    application = tornado.web.Application([
        (r"/index", MainHandler),  # 路由映射(url-->某个类)
    ],**settings) #把配置文件放到里面  
      
      
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start() # 把socket启动起来,监听请求
    

    如果我们把所有的东西都写在一个.py文件中,业务量大的话就显得十分杂乱,所以我们可以灵性的把功能分到不同的目录中去!

    每个目录放不同的py文件:

    • controllers负责业务处理,放视图类的.py
    • models负责数据库操作,放操作数据库.py
    • views负责放我们的模板(一般都是HTML文件,就是我们django的template)

    路由系统

    路由系统其实就是 url 和"类“的对应关系,这里不同于其他框架,其他很多框架均是 url对应函数,Tornado中每个url对应的是一个类。 这个类就是视图,在MVC中也叫控制器,它是负责处理业务的模块!

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
      
    import tornado.ioloop
    import tornado.web
      
    """
    路由系统:
    	url --> 类 (根据method执行不同的方法)
    """  
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.write("Hello, world")
            #self.render('模板名') #去指定的模板路径去读取模板返回给前端
            #self.redirect('URI') #跳转
      
    class LoginHandler(tornado.web.RequestHandler):
        #登陆功能控制器(视图)
        def get(self, *args,**kwagrs):
            # get请求执行该方法
            self.render('login.html',msg='')
        
        def post(self,*args,**kwargs):
            # post请求执行该方法
            username = self.get_agrument('user') #get和post里面都去取
            password = self.get_argument('pwd')
            if username == "root" and password == '123':
                self.set_cookie('is_login','Ture') #设置cookie
                self.redireact('https://deehuang.github.io/')
            else:
                # 渲染模板并把msg传到模板中(可以传字典)
                self.render('login.html',msg='用户名或密码错误')
    
    class HomeHandler(tornado.web.RequestHandler):
        #主页功能控制器(视图)
        def get(self,*args,**kwargs):
            login_staus = self.get_cookie('is_login') #获取cookie
            if not login_staus:
                self.redirect('/login')
                return #如果不return函数会继续往下执行
            self.write('欢迎登陆')
      
    application = tornado.web.Application([
        (r"/index", MainHandler),
        (r"/login", LoginHandler),
        (r"/home", HomeHandler),
    ]) 
      
    if __name__ == "__main__":
        application.listen(80)
        tornado.ioloop.IOLoop.instance().start()
    

    请求到视图类中会根据请求方式去执行不同的类方法,例如post请求去执行的就是post方法(注意:类中的方法是小写的)

    取请求的内容一般会用self.get_argument()方法。它是getpost方式都去取数据
    如果要单独取get请求传来的数据可以使用self.get_query_argument()
    如果要单独取post请求传来的数据可以使用self.get_body_argument()
    如果要取全部的键值对只需要在使用self.get_arguments()即可(同适用于get和post方式取值)
    如果要取文件内容可以使用self.request.files['文件名']

    其实用户请求的所有信息都封装在了控制器对象中的reques对象(self.request)中,上面的get_argument等等方法内部其实都是去request中去取值!

    总结控制器中常用的请求相关的方法:

    """控制器:"""
    	class Foo(tornado.web.RequestHandler):
    		def get(self):
    			self.render() #渲染模板,返回模板响应
    			self.write() #返回响应
    			self.redirect() #跳转
    			
    			self.get_argument() #取请求内容(get&post都取)
    			self.get_arguments()
    			self.get_cookie() #获取cookie
    			self.set_cookie() #设置cookie
    			self.get_secure_cookie() #获取加密的cookie(依赖配置文件)
    			self.set_secure_cookie() #设置加密的cookie(依赖配置文件)
    			
    			self.request.files['filename'] #获取上传的文件
    			self._headers #获取请求头信息
    					
    
    
    """取文件内容的操作:"""
       		file_metas = self.request.files["fff"] #拿到的是一个文件列表(因前端可能传多个文件)
            # print(file_metas)
            for meta in file_metas:  #遍历每个文件
                file_name = meta['filename'] #拿到文件名
                with open(file_name,'wb') as up:
                    up.write(meta['body']) #meta['body']拿到文件内容
    
    

    补充:加密的cookie(依赖配置文件)

    1. 设置配置文件,需要添加一个随机字符串密钥(用来给cookie安全加密)
    2. 设置cookie的时候使用self.set_secure_cookie()
    3. 取cookie的时候使用self.get_secure_cookie()

    实际上对于加密cookie内部实现的本质是:

    写cookie过程:

    • 将值进行base64加密
    • 对除值以外的内容进行签名,哈希算法(无法逆向解析)
    • 拼接 签名 + 加密值

    读cookie过程:

    • 读取 签名 + 加密值
    • 对签名进行验证
    • base64解密,获取值内容

    如果还想获取更多请求的信息可以遵循这样的寻找顺序:
    控制器对象(可以去父类tornado.web.RequestHandler里面去找)
    控制器对象中找不到就去self.request去找(它的类型是tornado.httputil.HTTPSeverRequest
    PS:寻找信息主要去它们的构造方法__init__()里面去找就行了

    模板

    请求首先到路由系统,根据路由匹配分发到不同的控制器中去处理业务。控制器处理完业务后一般返回三个结果:
    redirect()write()render()
    redirect就是跳转,write()就是直接写返回给浏览器。两个功能都十分简单明了
    而render较为复杂因为它做的就是模板引擎的渲染!就是这里要介绍的模板!

    Tornao中的模板语言和django中类似,模板引擎将模板文件载入内存,然后将数据嵌入其中,最终获取到一个完整的字符串,再将字符串返回给请求者。

    Tornado 的模板支持“控制语句”和“表达语句”:
    控制语句是使用{% 和 %}包起来的 例如 {% if len(items) > 2 %}。
    表达语句是使用{{ 和 }}包起来的,例如 {{ items[0] }}。
    
    控制语句和对应的Python语句的格式基本完全相同。我们支持 if、for、while和try,这些语句逻辑结束的位置需要用{% end %}做标记。还通过extends和block 语句实现了模板继承。这些在 [template模块](http://github.com/facebook/tornado/blob/master/tornado/template.py)的代码文档中有着详细的描述。
    
    """Tornado常用的模板语法"""
    {{ li[0] }} #索引取值
    
    {% for i in range(10) %}  {% end %}  #循环语句
    
    #继承
    {% block CSS %}{% end %} #母版
    {% extends '母版名'}  {% block CSS %}{% end %} #子版 需要先在文件头使用extends标签引入母版
    
    在模板中默认提供了一些函数、字段、类以供模板使用:
    escape: tornado.escape.xhtml_escape 的別名
    xhtml_escape: tornado.escape.xhtml_escape 的別名
    url_escape: tornado.escape.url_escape 的別名
    json_encode: tornado.escape.json_encode 的別名
    squeeze: tornado.escape.squeeze 的別名
    linkify: tornado.escape.linkify 的別名
    datetime: Python 的 datetime 模组
    handler: 当前的 RequestHandler 对象
    request: handler.request 的別名
    current_user: handler.current_user 的別名
    locale: handler.locale 的別名
    _: handler.locale.translate 的別名
    static_url: for handler.static_url 的別名
    xsrf_form_html: handler.xsrf_form_html 的別名
    
    PS:Tornado的循环控制语句等都统一以{% end %}标签结尾,与django不太一样
    

    模板语言还支持自定制拓展方法和类,它是通过UIMethodUIModule这两个组件去实现的( 这两个组件类似于Django的simple_tag )。
    其实通过UIMethod定制方法已经能实现所有的我们想生成的内容了,那么UIModule定制类存在的意义是什么呢?UIModule除了和UIMethod一样可以帮我们生成内容还可以帮我们添加CSS,JS。

    下面来看下它们的实现方式:

    1. 定义:

      • 定制方法:

        #模块名为uimethods.py
         
        def tab(request): 
        	# 默认会传入HTTPSeverRequest 即默认将请求的信息传过来了
            return 'UIMethod'
        
      • 定制类:

        """模块名为uimodules"""
        from tornado.web import UIModule
        from tornado import escape #转义模块
        
        class custom(UIModule):
        	def css_files(self):
        		#导入css文件,在页面使用链接式引入css文件
        		#默认会从静态文件夹static中去找(静态文件需在settings配置)
        		return '文件路径'
            
        	def embedded_css(self):
        		#嵌入CSS
        		#在页面<head>自动加入<style type='text/css'> 并把返回结果写入其中
        		return '.c1{display:None}'
            
            def javascript_files(self):
                #导入js文件,页面生成<script src='/static/xxx'>
        		#默认会从静态文件夹static中去找(静态文件需在settings配置)
                return "文件路径"
            
            def embedded_javascript(self):
                #嵌入js
                #页面会生成<script>并把返回内容写到其中
                return 'js代码'
        		
            def render(self, *args, **kwargs):
            	#模板直接调用类就会执行render方法
                return escape.xhtml_escape('<h1>deehuang</h1>') 
                #把要传入前端的html字符串进行转义
                #使得后端渲染到模板的html字符串不能被浏览器渲染(xss防御机制)
        

        *Ps:UIMethod中如果return的是一个html标签,tornado内部会自动帮我们转义为字符串到浏览器显示而不是渲染该标签。如果我们想关掉自动转义功能需要在settings里面设置autoescape:None。但实际运用中不允许这样使用(会遭到xss攻击),所以我们只能在前端模板中使用raw标签告诉模板引擎这句是不转义的:

        {% raw tab() %}
        

        而UIModules中是不会自动转义的,需要我们使用escape方法来对要传入前端的html字符串进行转义

    2. 注册:在配置文件中settings中把定制的方法或类模块放进去,这样Tornado才找得到

      import uimodules as md
      import uimethods as mt
      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)
      
    3. 使用

      {% module Custom(123) %} # 定制的类
      {{ tab() }} #定制的方法
      

    拓展:模板引擎内部是怎么实现的?

    对于模板语言的整个流程,其本质就是处理html文件内容将html文件内容转换成函数,然后为该函数提供全局变量环境(即:我们想要嵌套进html中的值和框架自带的值),再之后执行该函数从而获取到处理后的结果,再再之后则执行UI_Modules继续丰富返回结果,例如:添加js文件、添加js内容块、添加css文件、添加css内容块、在body内容第一行插入数据、在body内容最后一样插入数据,最终,通过soekct客户端对象将处理之后的返回结果(字符串)响应给请求用户。


    实用功能

    静态文件

    对于静态文件,可以配置静态文件的目录和前端使用时的前缀

    settings = {
        'template_path': 'template',
        'static_path': 'static',
        #前端模板使用使的前缀 模板标签{{ static_url('模块名') }}就会从/static/模块名去找
        'static_url_prefix': '/static/', 
    }
     
    application = tornado.web.Application([
        (r"/index", MainHandler),
    ], **settings)
    

    csrf

    Tornado中的跨站请求伪造和Django中的相似

    """设置配置文件"""
    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再来发送请求"""
    function getCookie(name) {
        var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
        return r ? r[1] : undefined;
    }
    
    jQuery.postJSON = function(url, args, callback) {
        args._xsrf = getCookie("_xsrf");
        $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
            success: function(response) {
            callback(eval("(" + response + ")"));
        }});
    };
    

    拓展功能之自定义Session

    在这里我们希望自定制一个session组件来方便我们在tornado的控制器中处理业务的时候可以实现对session的快速调用存储。

    1. 知识储备:

      • 关于多继承:
        super是自动按照顺序查找(深度优先)
        如果不想按顺序查找可以使用类名.方法名(self)的方式去指定执行某个父类的属性方法

        有个老生常谈的问题self是谁?self永远是调用方法的对象

      • 关于面向对象:

        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'] # 对象['key']会触发__getitem__
        #obj['k2'] = 'deehuang' # 对象['key']='xxx'会触发__setitem__
        #del obj['k1'] #触发__delitem__
        
      • Tornado控制器中的钩子(Hook)
        在控制器中,提供了一个我们可以自定义拓展操作的钩子函数 initialize。请求过来后首先会实例化我们的控制器对象,实例化的时候就会执行钩子函数initialize方法(因为在实例化过程执行构造方法的最后调用了钩子)。最后再去根据请求方式执行相应的函数。

        class MainHandler(tornado.web.RequestHandler):
        	def initialize(self):
                self.A = 123
                
            def get(self):
                print(self.A)
                self.write('Hello world')
        
        """结合多继承的知识使用,完成对控制器拓展操作"""
        class Foo(tornado.web.RequestHandler):
            def initalize(self):
                self.A = 123
                super(Foo,self).initialize() #使用super顺序调用其他父类不影响后面类的定制方法
        
        class MainHandler(Foo):
            def get(self):
                print(self.A)
                self.write('Hello world')
        
    2. session的实现机制:

      • 生成随机字符串

      • 写入用户Cookie

      • 后台存储

      • 在控制器中self.session['xx']实现session机制,就需要用到上面说到的知识储备了!

    3. session框架

      import tornado.ioloop
      import tornado.web
      import hashlib
      import os, time
      
      """
      session格式:
      #一个随机字符串代表的是一个用户
      #每个用户都对应一个值,它是一个字典用以存储用户的信息(在这里叫做用户)
      {
      '每个用户都有一个随机字符串': {'uesr':deehuang,is_login:ture}
      }
      """  
      
      class Cache(object):
          """
          session可以储存在内存,数据库,硬盘或缓存中,使用构造配置文件来让用户可以指定存储方式
          Cache表示将session保存在内存中
          """
          def __init__(self):
              self.session_container = {}  
          
          def __contanins__(self,item):
              """使用 x in obj 语法会触发该函数,用来判断sessionid是否在该容器里面"""
              return item in self.session_container
          
          def initial(self,random_str):
              """用来给每个用户的sessionid 创建字典"""
              self.session_container[random_str] = {}
      	
          def open(self):
              """
              存储在内存的话不需要用到open
              这里只是作规范,因为如果储存在文件或者数据库的话都要涉及打开或者连接操作   
              """
              pass
          
          def close(self):
              """
              存储在内存的话不需要用到close
              这里只是作规范,因为如果储存在文件或者数据库的话都要涉及打开或者连接操作   
              """
              pass
          
          def get(self,random_str,key):
              #获取用户字典信息 
              #避免取值的时候字典中没有匹配的键会报错,使用get方法去字典取值(不存在返回None)
              self.session_container[random_str].get(key)
          
          def set(self,key,random_str,value):
              #设置用户信息
               self.session_container[random_str][key] = value
          
          def delete(self,random_str,key):
              #删除用户字典中的某一键值对
      		del self.session_container[random_str][key]
          
          def clear(self,random_str):
              # 清除用户的session字典
              del self.session_container[random_str]
      
      class File(object):
          """session存储在文件,方法同上规范化"""
          pass
      class Memcache(object):
          """session存储在缓存,方法同上规范化"""
          pass
      class DataBase(object):
          """session存储在数据库,方法同上规范化"""
          pass
      
      P = Cache  #配置文件,表示的是session的存储方式
      
      class Session(object):  
          def __init__(self, handler):
              # 从钩子函数传过来的控制器对象中有设置和获取cookie的方法
              self.handler = handler
              self.random_str = None
              self.ppp = P()  # 储存session的容器
              self.ppp.open()
        		
              #去用户请求传来的cookie中获取sessionid
              client_random_str = self.handler.get_cookie('session_id')
              if not client_random_str:
                  #如果没有sessionid:表示是新用户 则给它创建一个随机字符串(sessionid)
          		self.random_str = self.create_random_str()
                  self.ppp.initial(self.random_str) #创建用户字典
              else:
                  #有sessionid去session中去匹配用户然后将信息写入用户字典中
                  #因为id有可能是用户伪造的并非是我们sever生成所以要在这里做一些判断:
                  if client_random_str in self.ppp: 
                      """老用户"""
                  	self.random_str = client_random_str
                   else:
                      """非法用户(伪造s_id)"""
                      self.random_str = self.create_random_str()
                      self.ppp.initial(self.random_str) #创建用户字典
             	#把s_id设置到cookie中并设置超时时间(参数expries=当前时间+失效时间,单位是s)
              #如果sessionid存在该方法内部不会重复去设置,只是会更新失效时间而已!
              ctime = time.time() #获取当前时间
              self.handler.set_cookie('session_id',self.random_str,expires=ctime+1800)      	 self.ppp.close()
              
          def create_random_str(self):
              """生成随机字符串"""
              v = str(time.time())
              m = hashlib.md5()
              m.update(bytes(v,encoding='utf-8'))
              return m.hexdigest()
        
          def __getitem__(self, key):
              """获取session中用户信息"""
              self.ppp.open()
              v =  self.ppp.get(self.random_str,key)
              self.ppp.close()
              return v
        
          def __setitem__(self, key, value):
              """my_session['key']=value会触发该方法并将key和value传入"""
              #后台存储--->设置用户session字典
              self.ppp.open()
              self.ppp.set(self.random_str,key,value)
              self.ppp.close()
              
          def __delitem__(self, key):
              self.ppp.open()
              del self.ppp.delete(self.random_str,key) 
              self.ppp.close()
        
        	def clear(self):
              """清空当前用户的session"""
              self.ppp.open()
              self.ppp.clear(random_str)
              self.ppp.close()
              
      class BaseHandler(tornado.web.RequestHandler):
        	"""拓展控制器功能"""
          def initialize(self):
              #self是MianHandler对象
              # my_session['k1']访问 __getitem__ 方法
              self.my_session = Session(self) #把对象传到Session中
        
        
      class HomeHandler(BaseHandler):
        
          def get(self):
              user = self.my_session['user']
              if not user:
                  # 用户字典中没有user值,表示没有登陆
                  self.redirect('http://deehuang.github.io')
              else:
                  self.write(user)
        
      class LoginHandler(BaseHandler):
        
          def get(self):
              self.my_session['user'] = 'root'
              self.redirect('/home')
              
      settings = {
          'template_path': 'template',
          'static_path': 'static',
          'static_url_prefix': '/static/',
          'cookie_secret': 'woshisuijimiyao',
          'login_url': '/login'
      }
        
      application = tornado.web.Application([
          (r"/home", HomeHandler),
          (r"/login", LoginHandler),
      ], **settings)
        
        
      if __name__ == "__main__":
          application.listen(8888)
          tornado.ioloop.IOLoop.instance().start()
      

      session框架的原理是不分语言不分web框架的,每个框架内部都是这样去实现的

    结语

    以上是个人学习之路,如有误,欢迎指正!参考文献

  • 相关阅读:
    菜鸟学存储:网络存储IP SAN与IB SAN
    读xml高手
    预先加载图片
    xred520
    最简单准确的硬盘整数分区设置操作方法
    Google 每天处理约 20000TB 的数据
    IE 8 无法正常使用网站后台编辑器问题
    常用的JS技术1
    adodb stream 使用说明
    [Tools] JDGUI(Java Decompiler)
  • 原文地址:https://www.cnblogs.com/deehuang/p/14394808.html
Copyright © 2011-2022 走看看