zoukankan      html  css  js  c++  java
  • Tornado

    1. 概述

    Tornado是一个可扩展的非阻塞Web服务器以及相关工具的总称。Tornado每秒可以处理数以千计的连接,所以对于实时Web服务来说,Tornado是一个理想的Web框架

    • 完备的 Web 框架:
      • URL路由映射
      • Request上下文
      • 基于模板的页面渲染
    • 性能与 Twisted、Gevent 等底层框架相媲美
      • 异步 I/O 支持
      • 超时事件处理
    • 除了做Web服务,还可以用来做爬虫应用、物联网关、游戏服务器等后台应用
    • 自带组件(强大)
      • 高效的HTTP服务器:可直接用于生产环境!
      • 基于异步框架的HTTPClient
    • 支持WebSocket
    • 丰富的用户身份认证功能

    因为 Tornado 的上述特点,Tornado常被用作大型站点的接口服务框架,而不像Django那样着眼于建立完整的大型网站。

    2. URL路由映射

    1. 固定字串路径

      Handlers = [
          ("/", MainHandler),
          ("/entry", EntryHandler),
          ("/entry/2019", Entry2019Handler)
      ]
      
    2. 正在表达式定义路径

      def make_app():
          return tornado.web.Application([
              ("/id/([^/]+)", MainHandler),
          ])
      

    3. 定义Handler的行为函数

    RequestHandler 是对一类问题处理的单元。

    需要子类继承并定义具体行为的函数在RequestHandler中被称为接入点函数(Entry point),上面的 Hello World 实例中的get()函数就是典型的接入点函数。

    3.1. 默认的回调函数

    • initialize()

      class ProfileHandler(RequestHandler):
          def initialize(self,database):
              self.database=database
      
    • prepare()

      用于调用请求处理(get、post等)方法之前的初始化处理,通常用来做资源初始化操作。

    • on_finish()

      用于请求处理结束后的一些清理工作,通常用来清理对象占用的内存或者关闭数据库连接等工作。

    3.2. HTTP Action处理函数

    • RequestHandler.get(*args,**kwargs)
    • RequestHandler.post(*args,**kwargs)
    • RequestHandler.head(*args,**kwargs)
    • RequestHandler.delete(*args,**kwargs)
    • RequestHandler.patch(*args,**kwargs)
    • RequestHandler.put(*args,**kwargs)
    • RequestHandler.options(*args,**kwargs)

    4. 获取请求参数

    输入捕捉是指在RequestHandler中用于获取客户端输入的工具函数和属性。比如获取URL参数、Post提交参数等。

    • get_argument(name)、get_arguments(name)

      RequestHandler.get_argument(name) 与 RequestHandler.get_arguments(name) 都是返回给定参数的值。get_argument 是获取单个值, 而 get_arguments 在参数存在多个值得情况下使用,返回多个值的列表。

      注意:使用这两个方法获取的事 URL 中查询的参数与 POST 提交的参数的参数合集。

    • get_query_argument(name)、get_query_arguments(name)

      功能与上面两个方法类似,唯一区别是这两个方法仅仅从 URL 中查询参数。

    • get_body_argument(name)、get_body_arguments(name)

      功能尚与上面四个方法类似,唯一区别是这两个方法仅仅从 POST 提交的参数中查询。

    • get_cookie(name,default=None)

      根据 Cookie 名称获取 Cookie 的值

    提示:实际开发中一般会使用 get_argument、get_arguments 这两个方法,因为他们会包含其他方法的查询结果。

    4.1. RequestHandler.request

    返回 tornado.httputil.HTTPServerRequest 对象实例的属性,通过该对象可以获取关于 HTTP 请求的一切信息,比如:

    class DetailHandler(RequestHandler):
        def get(self):
            ip = self.request.remote_ip  # 获取客户端的IP地址
            host = self.request.host  # 获取请求的主机地址
            result = "ip地址为%s,host为%s"%(ip, host)
            return self.write(result)
    

    常用的 httputil.HTTPServerRequest 对象属性如下表:

    属性名 说明
    method HTTP 请求方法,例如:GET、POST
    uri 客户端请求的 uri 的完整内容。
    path uri 路径名,即不包含查询字符串
    query uri 中的查询字符串
    version 客户端发送请求时使用的 HTTP 版本,例如:HTTP/1.1
    headers 以字典方式的形式返回 HTTP Headers
    body 以字符串的形式返回 HTTP 消息体
    remote_ip 客户端的 IP 地址
    protocol 请求协议,例如:HTTP、HTTPS
    host 请求消息的主机名
    arguments 客户端提交的所有参数。
    files 以字典形式返回客户端上传的文件,每个文件名对应一个 HTTPFile
    cookies 客户端提交的 Cookies 字典

    5. 设置应答参数

    • RequestHandler.set_status(status_code,reason=None)

      设置 HTTP Response 中的返回码,如果有描述性的语句,则可以赋值给 reason 参数。

    • RequestHandler.set_header(name,value)

      以键值对的方式设置 HTTP Response 中的 HTTP 头参数,使用 set_header 配置的 Header 值将覆盖之前配置的 Header。

    • RequestHandler.add_header(name,value)

      以键值对的方式设置 HTTP Response 中的 HTTP 头参数。与 set_header 不同的是 add_header 配置的 Header 值将不会覆盖之前配置的 Header。

    • RequestHandler.write(chunk)

      将给定的块作为 HTTP Body 发送客户端。在一般情况下,用本函数输出字符串给客户端。
      如果给定的块是一个字典,则会将这个块以 JSON 格式发送给客户端,同时将 HTTP Header 中的 Content_Type 设置为 application/json.

    • RequestHandler.finish(chunk=None)

      本方法通知 Tornado.Response 的生成工作已完成,chunk 参数是需要传递给客户端的 HTTP body。调用 finish() 后,Tornado 将向客户端发送 HTTP Response。
      本方法适用于对 RequestHandler 的异步请求处理,在同步或协程访问处理的函数中,无须调用 finish() 函数。

    • RequestHandler.render(template_name,**kwargs)

      用给定的参数渲染模块,可以在本函数中传入模板文件名称和模板参数。

      import tornado.web
      class MainHandler(tornado.web.RequestHandler):
          def get(self):
              items=["Python","C++","Java"]
              #第一个参数是模板名称,后面是模板参数
              self.render("template.html",title="Tornado Template",items=items)
      
    • RequestHandler.redirect(url,permanent=False,status=None)

      进行页面重定向。在 RequestHandler 处理过程中,可以随时调用 redirect() 函数进行页面重定向。

    • RequestHandler.clear()

      清空所有在本次请求中之前写入的 Header 和 Body 内容。

    • RequestHandler.set_cookie(name,value)

      按键值对设置 Response 中的 Cookie 的值。

    • RequestHandler.clear_all_cookies(path="/",domain=None)

      清空本次请求中的所有 Cookie。

    6. 异步与协程化

    Tornado有两种方式可改变同步的处理流程:

    • 异步化(已过期,不再讨论)

      针对RequestHandler的处理函数使用 @tornado.web.asynchronous 修饰器,将默认的同步机制改为异步机制。该方法已经过期。

    • 协程化

      针对RequestHandler的处理函数使用 @tornado.gen.coroutine 修饰器,将默认的同步机制改为协程机制。

    Tornado协程结合了同步处理和异步处理的有点,使得代码即清晰易懂,又能够适应海量客户端的高并发请求。

    class MainHandler(tornado.web.RequestHandler):
        @tornado.gen.coroutine
        def get(self):
            http = tornado.httpclient.AsyncHTTPClient()
            response = yield http.fetch("http://www.baidu.com")
            self.write(response.body)
    

    协程化的关键技术点如下:

    • @tornado.gen.coroutine 装饰MainHandler的get()、post()等处理函数。
    • 使用异步对象处理耗时操作,比如本例的AsyncHTTPClient。
    • 调用yield关键字获取异步对象的处理结果。

    7. Cookie机制

    Cookie是很多网站为了辨别用户的身份而存储在用户本地终端(Client Side)d的数据,在Tornado中使用RequestHandler.get_cookie()、RequestHandler.set_cookie()可以方便地对Cookie进行读写。

    session_id = 1
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            global session_id
            if not self.get_cookie("session"):
                self.set_cookie("session",str(session_id))
                session_id += 1
                self.write("设置新的session")
            else:
                self.write("已经具有session")
    

    因为Cookie总是被保存在客户端,所以如何保存其不被篡改是服务器端程序必须解决的问题。Tornado为Cookie提供了信息加密机制,使得客户端无法随意解析和修改Cookie的键值。

    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            ...
            if not self.get_secure_cookie("session"):
                #set_secure_cookie代替set_cookie
                self.set_secure_cookie("session",str(session_id))
    ...
    
    if __name__ == '__main__':
        app=tornado.web.Application([
            ("/",MainHandler)
        ],cookie_secret="JIA_MI_MI_YAO")
        app.listen("8888")
        tornado.ioloop.IOLoop.current().start()
    

    对比上面的简单Cookie实例可以发现不同之处:

    • tornado.web.Application 对象初始化时赋予 cookie_secret 参数,该参数值时一个字符串,用于保存本网站Cookie加密时的密钥。
    • 读取: 使用 RequestHandler.get_secure_cookie 代替原来的get_cookie()。
    • 写入: 用 RequestHandler.set_secure_cookie 替换原来的set_cookie()。

    这样,就不需要担心Cookie伪造的问题了,但是cookie_secret参数值作为加密密钥,需要好好保护,不能泄露。

    8. 用户身份认证

    在RequestHandler类中有一个 current_user 属性用于保存当前请求的用户名。RequestHandler.get_current_user 的默认值是None,在get()、post()等处理函数中可以随时读取该属性以获取当前的用户名。RequestHandler.current_user 是一个只读属性,所以如果想要设置该属性值,需要重载 RequestHandler.get_current_user() 函数以设置该属性值。

    使用 current_user 属性及 get_current_user() 方法来实现用户身份控制。

    import tornado.web
    import tornado.ioloop
    import uuid  # UUID 生成库
    
    dict_sessions = {}  # 保存所有登录的Session
    
    class BaseHandler(tornado.web.RequestHandler):  #公共基类
        #写入current_user的函数
        def get_current_user(self):
            session_id=self.get_secure_cookie("session_id")
            return dict_sessions.get(session_id)
    
    class MainHandler(BaseHandler):
        @tornado.web.authenticated  #需要身份认证才能访问的处理器
        def get(self):
            name=tornado.escape.xhtml_escape(self.current_user)
            self.write("Hello,"+name)
    
    class LoginHandler(BaseHandler):
        def get(self):   #登陆页面
            self.write('<html><>body'
                       '<form action="/login" method="post">'
                       'Name:<input type="text" name="name">'
                       '<input type="submit" value="Sign in">'
                       '</form></body></html>')
        def post(self):  #验证是否运行登陆
            if len(self.get_argument("name"))<3:
                self.redirect("/login")
            session_id=str(uuid.uuid1())
            dict_sessions[session_id]=self.get_argument("name")
            self.set_secure_cookie("session_id",session_id)
            self.redirect("/")
    
    setting = {
        "cookie_secret":"SECRET_DONT_LEAK", #Cookie加密秘钥
        "login_url":"/login"  #定义登陆页面
    }
    
    application = tornado.web.Application([
        (r"/",MainHandler),        #URL映射定义
        (r"/login",LoginHandler)
    ],**setting)
    
    if __name__ == '__main__':
        application.listen(8888)
        tornado.ioloop.IOLoop.current().start()     #挂起监听
    

    注意:加入身份认证的所有页面处理器需要继承自BaseHandler类,而不是直接继承原来的tornado.web.RequestHandler类。

    9. 防止跨站攻击

    跨站请求伪造(Cross-site request forgery,CSRF 或XSRF)是一种对网站的恶意利用。通过CSRF,攻击者可以冒用用户的身份,在用户不知情的情况下执行恶意操作。

    下图展示了 CSRF 的基本原理。其中 Site1 是存在 CSRF 漏洞的网站,而 SIte2 是存在攻击行为的恶意网站。

    上图内容解析如下:

    1. 用户首先访问了存在 CSRF 漏洞网站 Site1,成功登陆并获取了 Cookie,此后,所有该用户对 Site1 的访问均会携带 Site1 的 Cookie,因此被 Site1 认为是有效操作。

    2. 此时用户又访问了带有攻击行为的站点 Site2,而 Site2 的返回页面中带有一个访问 Site1 进行恶意操作的连接,但却伪装成了合法内容,比如下面的超链接看上去是一个抽奖信息,实际上却是想 Site1 站点提交提款请求

      <a href='http:三百万元抽奖,免费拿' >
      
    3. 用户一旦点击恶意链接,就在不知情的情况下向 Site1 站点发送了请求。因为之前用户在 Site1 进行过登陆且尚未退出,所以 Site1 在收到用户的请求和附带的 Cookie 时将被认为该请求是用户发送的正常请求。此时,恶意站点的目的也已经达到。

    为了防范 CSRF 攻击,要求每个请求包括一个参数值作为令牌的匹配存储在 Cookie 中的对应值。

    Tornado 应用可以通过一个 Cookie 头和一个隐藏的 HTML 表单元素向页面提供令牌。这样,当一个合法页面的表单被提交时,它将包括表单值和已存储的 Cookie。如果两者匹配,则 Tornado 应用认可请求有效。

    开启 Tornado 的 CSRF 防范功能需要两个步骤。

    1. 开启 xsrf_cookies=True 参数:

      application=tornado.web.Application([
              (r'/',MainHandler),
          ],
          cookie_secret='DONT_LEAK_SECRET',
          xsrf_cookies=True,
      )
      
    2. 在每个具有 HTML 表达的模板文件中,为所有表单添加 xsrf_form_html() 函数标签:

      <form action="/login" method="post">
          {% module xsrf_form_html() %}  # 为表单添加隐藏元素以防止跨站请求
          <input type="text" name="message"/>
          <input type="submit" value="Post"/>
      </form>
      

      这里的 {% module xsrf_form_html() %}起到了为表单添加隐藏元素以防止跨站请求的作用。

    Tornado的安全Cookie支持和XSRF防范框架减轻了应用开发者的很多负担,没有他们,开发者需要思考很多防范的细节措施,因此Tornado内建的安全功能也非常有用。

    10. 部署

    之前着重讲解Tornado的编程知识点,所有之前的例子都使用最简单的IOLoop启动方式运行。本节学习如何优化Tornado的运行方式,以达到快捷、易用及资源利用优化的目的。

    10.1. WebServer的debug运行模式

    • py文件的更新后,自动重启Sevser程序;

    • 错误追溯,显示到浏览器中;

    • 禁用模板缓存

      在运营环境中模板缓存能提高效率,但在调试期间占用了更多的系统资源,所以将其禁用有利于开发者进行调试。

    def make_app():
        return tornado.web.Application([
                #此处写入映射
            ],
            debug=True  #调试模式
        )
    

    10.2. 支持 Ctrl+C 退出

    try:
        tornado.ioloop.IOLoop.current().start()
    except KeyboardInterrupt:
        tornado.ioloop.IOLoop.current().stop()
        # 此处执行资源回收工作
        print("Program exit!")
    

    10.3. 部署静态文件

    10.3.1. 配置对URL路径

    def make_app():
        return tornado.web.Application([
                #此处写入映射
            ],
            static_path=os.path.join(os.path.dirname(__file__),'static'),
            static_url_prefix="/_static/"  # 挂载点,默认为"/static/"
        )
    

    10.3.2. StaticFileHandler

    如果除了http://mysite.com/static目录还有其他存放静态文件的URL,则可以用RequestHandler的子类StaticFileHandler进行配置,比如:

    def make_app():
        return tornado.web.Application([
                #此处写入映射
    
                #这里配置了3个StaticFileHandler
                (r'/css/(.*)',tornado.web.StaticFileHandler,{'path':'assets/css'}),
                (r'/images/png/(.*)',tornado.web.StaticFileHandler,{'path':'assets/image'}),
                (r'/js/(.*)',tornado.web.StaticFileHandler,{'path':'assets/js','default_filename':'templates/index.html'}),
            ],
            static_path=os.path.join(os.path.dirname(__file__),'static')
        )
    

    本例中除了static_path,还用StaticFileHandler配置了另外3个静态文件目录。

    • 所有对 http://mysite.com/css/* 的访问被映射到相对路径assets/css中。
    • http://mysite.com/images/png/* 的访问被映射到assets/images目录中。
    • http://mysite.com/js/* 的访问被映射到assets/js目录中;该条StaticFileHandler的参数中还被配置了 default_filename 参数,即当用户访问了 http://mysite.com/js 目录本身时,将返回 templates/index.html 文件。

    10.3.3. 优化静态文件访问(缓存以减少重复传送)

    优化静态文件访问的目的在于减少静态文件的重复传送,提高网络及服务器的利用效率,通过在模板文件中用static_url方法修饰静态文件链接可以达到这个目的:

    <html>
        <body>
            <div><img src="{{static_url('images/logo.png')}}"/><div>
        </body>
    </html>
    

    本例中的静态图像链接将被设置为类似 /static/images/logo.png?v=5ad4e 的形式,其中的 v=5ad4e 是logo.png文件内容的哈希值,当Tornado静态文件处理器发现该参数时,将通知浏览器该文件可以无限期缓存,因此避免了之后访问该文件时的反复传输。

    11. WebSocket

    Tornado 第三章:HTML5 WebSocket概念及应用

  • 相关阅读:
    读书笔记,《我还是喜欢东京——带你感受城市细节》
    学习笔记:Maven的ArcheType的学习笔记
    如何从中企动力(新网)转移域名到阿里云(万网)
    Maven自定义Archetype(zz)
    读书笔记,《Java 8实战》第五章,使用流
    读书笔记,《Java 8实战》,第四章,引入流
    读书笔记,《Java 8实战》,第三章,Lambda表达式
    读书笔记,《Java8实战》第一章,为什么要关心 Java8
    读书笔记,《深入理解java虚拟机》,第三章 垃圾收集器与内存分配策略
    行业知识:关于发电量与碳排放和等效植树的换算关系
  • 原文地址:https://www.cnblogs.com/brt2/p/13275356.html
Copyright © 2011-2022 走看看