tornado入门基础学习总结

tornado之hello world

tornado 服务启动比Django简单很多,有点像flask:

from tornado import (
    web,
    ioloop
)


class MainHandler(web.RequestHandler):
    def get(self, *args, **kwargs):
        self.write("hello, world")

if __name__ == '__main__':
    app = web.Application([
        ("/", MainHandler)
    ])
    app.listen(8888)
    ioloop.IOLoop.current().start()
  • 自定义类必须继承web.RequestHandler, 当客户端发起不同的http方法的时候,只需要重载handler中的对应的方法即可:

    class MainHandler(web.RequestHandler):
        def get(self, *args, **kwargs):
            """
            客户端发起get请求
            :param args:
            :param kwargs:
            :return:
            """
            self.write("hello, world")
    
        def post(self, *args, **kwargs):
            """
            客户端发起post请求
            :param args:
            :param kwargs:
            :return:
            """
            pass
        
        def delete(self, *args, **kwargs):
            """
            客户端发起delete请求
            :param args:
            :param kwargs:
            :return:
            """
            pass
    
  • 为了充分发挥tornado异步地性能,最好定义异步方法,需要注意的是异步方法中不要有同步阻塞的代码:

    class MainHandler(web.RequestHandler):
        async def get(self, *args, **kwargs):
            self.write("hello, world")
    
  • 像Django一样将debug设为true修改程序自动重启:

    if __name__ == '__main__':
        app = web.Application([
            ("/", MainHandler)
        ], debug=True)
        app.listen(8888)
        ioloop.IOLoop.current().start()
    

    需要注意的是,将debug设为True后,修改完程序,pycharm控制台会显示程序正常退出:
    在这里插入图片描述

    但是tornado服务仍在运行,不过是在后台运行:
    在这里插入图片描述

    如果我们想关闭该程序就只能通过任务管理器关闭后台一个叫做python的进程:
    在这里插入图片描述

    否则再次启动程序时会提示端口已被占用。

    也可以在pycharm终端运行该程序文件,当代码变更后就会出现新的命令行,此时程序在后台运行:
    在这里插入图片描述

    此时如果想终止程序只需要按CTRL+C就可以了:
    在这里插入图片描述

    如果想查看控制台信息可以使用pycharm调试模式启动该程序,该模式在退出调试模式的同时程序也会退出:
    在这里插入图片描述

tornado中为什么不能写同步方法

  • tornado程序是基于单线程的协程模式,所以如果写同步方法会导致同时请求多个url时出现阻塞现象:

    import time
    
    from tornado import (
        web,
        ioloop
    )
    
    
    class MainHandler(web.RequestHandler):
        async def get(self, *args, **kwargs):
            # 休眠5秒模仿同步代码,当网页同时出现多个请求时会出现阻塞现象
            # 如:第一个请求等待5秒,第二个请求等待10秒
            time.sleep(5)
            self.write("hello, world2")
    
    
    if __name__ == '__main__':
        app = web.Application([
            ("/", MainHandler)
        ], debug=True)
        app.listen(8888)
        ioloop.IOLoop.current().start()
    

    即使同时请求的是不同的url(/和/2)请求也会出现阻塞现象:

    class MainHandler(web.RequestHandler):
        async def get(self, *args, **kwargs):
            # 休眠5秒模仿同步代码,当网页同时请求多个url时会出现阻塞现象
            # 如:第一个请求等待5秒,第二个请求等待10秒
            time.sleep(5)
            self.write("hello, world")
    
    class MainHandler2(web.RequestHandler):
        async def get(self, *args, **kwargs):
            self.write("hello, world2")
    
    
    if __name__ == '__main__':
        app = web.Application([
            ("/", MainHandler),
            ("/2", MainHandler) # 即使请求的是不同的url也会阻塞
        ], debug=True)
        app.listen(8888)
        ioloop.IOLoop.current().start()
    

所以一定不能再请求方法中写同步代码,否则就不能发挥异步框架的性能

tornado中的url配置

  • 可以将url抽离为变量,url可以使用正则匹配参数,并将参数传递给请求方法:

    from tornado import web
    import tornado
    
    class MainHandler(web.RequestHandler):
        async def get(self, *args, **kwargs):
            self.write("hello, world")
    
    
    class PeopleIdHandler(web.RequestHandler):
        async def get(self, id,  *args, **kwargs):
            self.write("用户id:{}".format(id))
    
    
    class PeopleNameHandler(web.RequestHandler):
        async def get(self, name, *args, **kwargs):
            self.write("用户姓名:{}".format(name))
    
    
    # 配置url
    urls = [
        tornado.web.URLSpec("/", MainHandler, name="index"),
        # (\d+)会自动匹配url中的数字和字母,作为参数传递至请求方法中, 
        # ?P<id>是为了增强代码的可读性,需要注意的是正则<>内的参数必须与方法中的参数名称一样
        tornado.web.URLSpec("/people/(?P<id>\d+)/?", PeopleIdHandler, name="people_id"), 
        tornado.web.URLSpec("/people/(?P<name>\w+)/?", PeopleNameHandler, name="people_name"), 
    ]
    
    if __name__ == '__main__':
        app = web.Application(urls, debug=True)
        app.listen(8000)
        tornado.ioloop.IOLoop.current().start()
    
  • 如果想使访问时浏览器自动在url末尾加上斜杠/ ,可以在url配置中加上,这样在访问时不管输入http://localhost:8000/people/1还是http://localhost:8000/people/1/都可以正常访问不会出现404:

    urls = [
        ("/", MainHandler),
        ("/people/(\d+)/?", PeopleIdHandler),
        ("/people/(\w+)/?", PeopleNameHandler),
    ]
    
  • 使用tornado.web.URLSpec配置url命名空间:

    urls = [
        tornado.web.URLSpec("/", MainHandler, name="index"),
        tornado.web.URLSpec("/people/(\d+)/?", PeopleIdHandler, name="people_id"),
        tornado.web.URLSpec("/people/(\w+)/?", PeopleNameHandler, name="people_name"),
    ]
    

    使用self.reverse_url()来获取命名空间的url:

    class PeopleIdHandler(web.RequestHandler):
        async def get(self, id,  *args, **kwargs):
            # 跳转至命名空间为people_name的url,带有参数zzy
            self.redirect(self.reverse_url("people_name", 'zzy'))
    
  • 通过url配置给handler传入初始值,需要注意传递people_db的参数必须是个字典:

    people_db = {
        "name": "people"
    }
    
    # 配置url
    urls = [
        tornado.web.URLSpec("/", MainHandler, name="index"),
        tornado.web.URLSpec("/people/(?P<id>\d+)/?", PeopleIdHandler, people_db, name="people_id"),
        tornado.web.URLSpec("/people/(?P<name>\w+)/?", PeopleNameHandler, name="people_name"),
    ]
    

    然后在对应的逻辑类中重写initialize方法接受参数:

    class PeopleIdHandler(web.RequestHandler):
        def initialize(self, name):
            self.name = name
    
        async def get(self, id,  *args, **kwargs):
            # 跳转至命名空间为people_name的url,带有参数zzy
            self.redirect(self.reverse_url("people_name", self.name))
    

define、options、parse_command_line

options定义启动程序时传递的命令行参数以及类型:

from tornado import web
import tornado
from tornado.options import define, options, parse_command_line

# define, 定义一些可以在命令行中传递的参数以及类型
define('port', default=8008, help="run on the given port", type=int)
define('debug', default=True, help="set tornado debug mode", type=bool)

options.parse_command_line()

class MainHandler(web.RequestHandler):
    async def get(self, *args, **kwargs):
        self.write("hello, world")

# 配置url
urls = [
    tornado.web.URLSpec("/", MainHandler, name="index")
]

if __name__ == '__main__':
    app = web.Application(urls, debug=options.debug)
    app.listen(options.port)
    tornado.ioloop.IOLoop.current().start()

在这里插入图片描述

也可以通过配置文件读取参数:

# 通过命令行读取参数
# options.parse_command_line()
# 通过配置文件读取参数
options.parse_config_file("conf.cfg")

在这里插入图片描述

RequestHandler常用方法

  • 初始化handler类接收参数的方法initialize

        def initialize(self, db):
            # 初始化handler类接收参数的过程
            self.db = db
    
  • 用于真正调用请求处理之前的初始化方法prepare:

        def prepare(self):
            # 用于真正调用请求处理之前的初始化方法
            # 如:打印日志,打开文件
            pass
    
  • 关闭句柄,清理内存on_finish

        def on_finish(self):
            # 关闭句柄,清理内存
            pass
    
  • http 请求方法

        def get(self, *args, **kwargs):
            pass
        
        def post(self, *args, **kwargs):
            pass
        
        def delete(self, *args, **kwargs):
            pass
        
        def patch(self, *args, **kwargs):
            pass
    

    获取参数输入内容的方法:

        def get(self, *args, **kwargs):
            """
            get_query_argument 和 get_query_arguments 为获取get请求参数的方法
            如果name不存在就会抛出400异常
            :param args:
            :param kwargs:
            :return:
            """
            # 获取的是字符串,默认取最后一个name的值
            self.get_query_argument("name")
            # 获取的是列表,存放所有的name的值
            self.get_query_arguments("name")
    
        def post(self, *args, **kwargs):
            """
            get_argument 和 get_arguments 为获取post请求参数的方法
            :param args:
            :param kwargs:
            :return:
            """
            # 获取的是字符串,取最后一个name的值
            data1 = self.get_argument("name")
            # 获取的是列表,如果url后边跟上name参数会将该name参数的值也放入列表中
            data2 = self.get_arguments("name")
    
            # 获取所有的参数
            data3 = self.request.arguments
    
            # 如果请求没有传递headers = {
            #     "Content-type": "application/x-www-form-urlencoded;",
            # }
            # 获取json数据, 我们必须先从body中获取参数解码,然后转换为dict对象
            # 才能调用get_body_argument 和 get_body_arguments 方法获取json参数
            # 如果请求头传递了headers,我们可以直接使用get_body_argument获取参数
            param = self.request.body.decode('utf-8')
            json_data = json.loads(param)
            data4 = self.get_body_argument("name")
            data5 = self.get_body_arguments("name")
    

    输出内容的方法:

    • 设置异常状态码set_status

              try:
                  data4 = self.get_body_argument("name")
                  data5 = self.get_body_arguments("name")
              except Exception as e:
                  self.set_status(500)
      
    • 输出至浏览器显示方法write,因为tornado为长连接,所以可以连续写多个write方法,将内容连接起来:

          def get(self, *args, **kwargs):
          
              self.write("hello")
              self.write("world")
      

在这里插入图片描述

显示的内容并没有换行,write是将内容写入缓冲区,然后再将缓冲区中的所有内容显示在浏览器中。

  • 如果想关闭长连接,可以调用finish方法:

    # http 方法
        def get(self, *args, **kwargs):
    		# 浏览器将会只显示hello不显示world
            self.write("hello")
            self.finish()
            self.write("world")
    

在这里插入图片描述

在调用finish方法的时候可以传递参数,可以传递字符串,字典,byte类型都可以回显:

```
    def get(self, *args, **kwargs):
        self.write("hello")
        self.finish("\r\nover")
        self.write("world")
```

在这里插入图片描述

  • 自定义错误显示方法write_error

        def write_error(self, status_code, **kwargs):
            """自定义错误页面显示"""
            pass
    

RequestHandler的子类

  1. RedirectHandler: 重定向类

    handler本身的重定向方法self.redirect("", permanent=True)作用一样。

    • permanent为True时是永久重定向状态码为301,为False时是临时重定向状态码为302
    • RedirectHandler默认使用永久重定向
    • RedirectHandler使用是在url中直接配置,重定向更加简单方便,但是为永久重定向
    if __name__ == '__main__':
        app = web.Application([
            ("/", MainHandler),
            # 访问"/2"时重定向至"/"
            ("/2", RedirectHandler, {"url": "/"})
        ], debug=True)
        app.listen(8888)
        ioloop.IOLoop.current().start()
    
  2. StaticFileHandler:处理静态文件类

    tornado读取静态文件有两种方式:

    1. 配置静态文件路径等信息,启动时将配置内容以参数的形式传递

      from tornado import (
          web,
          ioloop,
      )
      from tornado.web import (
          RedirectHandler,
          StaticFileHandler
      )
      
      
      class MainHandler(web.RequestHandler):
          async def get(self, *args, **kwargs):
              self.write("hello, world")
      
      
      # 如果配置内容过多,可以将配置内容写入字典中然后在启动应用的时候以解包的方式传递使用
      settings = {
          # 静态文件路径,即使访问static文件夹下子文件里的静态文件也是可以的
          "static_path": "C:/Users/ZZY/Desktop/code/chapter02/static",
          # 访问静态文件输入的url, 不写默认为/static/
          "static_url_prefix": "/static2/"
      }
      
      
      if __name__ == '__main__':
          app = web.Application([
              ("/", MainHandler),
              # 访问"/2"时重定向至"/"
              ("/2", RedirectHandler, {"url": "/"}),
          ], debug=True, **settings)  # 解包的方式将settings传递进来
          app.listen(8888)
          ioloop.IOLoop.current().start()
      
    2. 也可以通过StaticFileHandler类配置url访问静态文件:

      from tornado import (
          web,
          ioloop,
      )
      from tornado.web import (
          RedirectHandler,
          StaticFileHandler
      )
      
      
      class MainHandler(web.RequestHandler):
          async def get(self, *args, **kwargs):
              self.write("hello, world")
      
      
      # # 如果配置内容过多,可以将配置内容写入字典中然后在启动应用的时候以解包的方式传递使用
      # settings = {
      #     # 静态文件路径,即使访问static文件夹下子文件里的静态文件也是可以的
      #     "static_path": "C:/Users/ZZY/Desktop/code/chapter02/static",
      #     # 访问静态文件url, 不写默认为/static/
      #     "static_url_prefix": "/static2/"
      # }
      
      
      if __name__ == '__main__':
          app = web.Application([
              ("/", MainHandler),
              # 访问"/2"时重定向至"/"
              ("/2", RedirectHandler, {"url": "/"}),
              # 访问静态文件,需要传入path
              ("/static/(.*)", StaticFileHandler, {"path": "C:/Users/ZZY/Desktop/code/chapter02/static"}),
          ], debug=True)
          app.listen(8888)
          ioloop.IOLoop.current().start()
      

tornado的template

模板使用

  1. 配置模板文件路径:

    # 如果配置内容过多,可以将配置内容写入字典中然后在启动应用的时候以解包的方式传递使用
    settings = {
        # 静态文件路径,即使访问static文件夹下子文件里的静态文件也是可以的
        "static_path": "C:/Users/ZZY/Desktop/code/chapter02/static",
        # 访问静态文件url, 不写默认为/static/
        "static_url_prefix": "/static2/",
        # 模板路径配置文件
        "template_path": "templates"
    }
    
  2. 渲染模板:
    在这里插入图片描述

模板语法

  • 后端渲染:

    class MainHandler(web.RequestHandler):
        async def get(self, *args, **kwargs):
            orders = [
                {
                    "name": "小米T恤 忍者米兔双截棍 军绿 XXL",
                    "image": "http://i1.mifile.cn/a1/T11lLgB5YT1RXrhCrK!40x40.jpg",
                    "price": 39,
                    "nums": 3,
                    "detail": "<a href='http://www.baidu.com'>查看详情</a>"
                },
                {
                    "name": "招财猫米兔 白色",
                    "image": "http://i1.mifile.cn/a1/T14BLvBKJT1RXrhCrK!40x40.jpg",
                    "price": 49,
                    "nums": 2,
                    "detail": "<a href='http://www.baidu.com'>查看详情</a>"
                },
                {
                    "name": "小米圆领纯色T恤 男款 红色 XXL",
                    "image": "http://i1.mifile.cn/a1/T1rrDgB4DT1RXrhCrK!40x40.jpg",
                    "price": 59,
                    "nums": 1,
                    "detail": "<a href='http://www.baidu.com'>查看详情</a>"
                }
            ]
            self.render("index.html", orders=orders)
    
  • 遍历取值:

    {% for order in orders %}
    {{ order['image'] }}
    {% end %}
    
  • 加载静态文件:

    <link href="{{ static_url('css/public.css') }}" type="text/css" rel="stylesheet">
    

    使用该语法需要tornado配置如下,访问url必须是static:

    # 如果配置内容过多,可以将配置内容写入字典中然后在启动应用的时候以解包的方式传递使用
    settings = {
        # 静态文件路径,即使访问static文件夹下子文件里的静态文件也是可以的
        "static_path": "C:/Users/ZZY/Desktop/code/chapter02/static",
        # 访问静态文件url, 不写默认为/static/
        "static_url_prefix": "/static/",
        # 模板路径配置文件
        "template_path": "templates"
    }
    
  • 数学运算:

    {{ order['price']*order['nums'] }}
    
  • 引用方法:

    定义一个方法计算总价格,将该方法传递给前端文件:

    class MainHandler(web.RequestHandler):
    
        def cal_total(self, price, nums):
            return price*nums
    
        async def get(self, *args, **kwargs):
            orders = [
                {
                    "name": "小米T恤 忍者米兔双截棍 军绿 XXL",
                    "image": "http://i1.mifile.cn/a1/T11lLgB5YT1RXrhCrK!40x40.jpg",
                    "price": 39,
                    "nums": 3,
                    "detail": "<a href='http://www.baidu.com'>查看详情</a>"
                },
                {
                    "name": "招财猫米兔 白色",
                    "image": "http://i1.mifile.cn/a1/T14BLvBKJT1RXrhCrK!40x40.jpg",
                    "price": 49,
                    "nums": 2,
                    "detail": "<a href='http://www.baidu.com'>查看详情</a>"
                },
                {
                    "name": "小米圆领纯色T恤 男款 红色 XXL",
                    "image": "http://i1.mifile.cn/a1/T1rrDgB4DT1RXrhCrK!40x40.jpg",
                    "price": 59,
                    "nums": 1,
                    "detail": "<a href='http://www.baidu.com'>查看详情</a>"
                }
            ]
            self.render("index.html", orders=orders, cal_total=self.cal_total)
    

    在index.html中引用该方法:

    <div class="col col-4">{{ cal_total(order['price'], order['nums']) }}</div>
    

    也可以在前端文件中直接通过 import 方法不需要通过传递方法名称来使用:

    • 在utils.py文件中存在计算总价格的方法:

      {% from utils import cal_total %}
      

      需要注意的是前端引入方法的路径是以后端文件相对于引入文件来说的:
      在这里插入图片描述

  • 设置变量:

    {% set total = 0 %}
    { total = total + order['price']*order['nums'] }
    
  • 使用Python语法:

    在tornado的模板中我们可以使用Python的语法只需要使用{% %}将语法括起来就可以了,如:

    {% if order['price'] > 0 %}
    <div class="col col-2">{{ order['price'] }}元</div>
    {% else %}
    <div>免费</div>
    

模板继承和重载

  • 抽离公共部分至base页面,将非公共部分使用占位符:
    在这里插入图片描述

    index继承base页面:
    在这里插入图片描述

tornado模板中UIModule

有时候公共模块不容易抽离出来,或者公共模块在不同的继承中需要显示不同的样式。这个时候就需要使用tornado的UIModule,将小的模块抽离为组件。

  1. 在templates模板文件夹下建立一个文件夹,将需要抽离出的模块抽离出来:
    在这里插入图片描述

  2. 建立一个module类重载tornado.web.UIModule,重写其中的render方法,返回组件:

    class OrderModule(web.UIModule):
    
        def cal_total(self, price, nums):
            return price * nums
    
        def render(self, order, *args, **kwargs) -> str:
            """
            ui模板子组件:订单列表
            :param order: 父组件传递参数
            :param args:
            :param kwargs:
            :return:
            """
            return self.render_string("ui_modules/order_list.html", order=order, cal_total=self.cal_total)
    
  3. 配置ui_module组件全局名称对应的方法:

    settings = {
        # 静态文件路径,即使访问static文件夹下子文件里的静态文件也是可以的
        "static_path": "C:/Users/ZZY/Desktop/code/chapter02/static",
        # 访问静态文件url, 不写默认为/static/
        "static_url_prefix": "/static/",
        # 模板路径配置文件
        "template_path": "templates",
        # 配置UIModule组件配置
        "ui_modules": {
            "OrderModule": OrderModule
        }
    }
    
  4. 使用OrderModule组件,传入参数order:

     {% for order in orders %}
     	{% module OrderModule(order) %}
     {% end %}
    

公共CSS和JS也可以抽离为公共组件

  1. 将公共css抽离出来:
    在这里插入图片描述

  2. 在组件类中重写css_file方法:

    class OrderModule(web.UIModule):
    
        def cal_total(self, price, nums):
            return price * nums
    
        def render(self, order, *args, **kwargs) -> str:
            """
            ui模板子组件:订单列表
            :param order: 父组件传递参数
            :param args:
            :param kwargs:
            :return:
            """
            return self.render_string("ui_modules\order_list.html", order=order, cal_total=self.cal_total)
    
        def css_files(self):
            return ["ui_modules/order_list.css"]
    

odule组件,传入参数order:

 {% for order in orders %}
 	{% module OrderModule(order) %}
 {% end %}

公共CSS和JS也可以抽离为公共组件

  1. 将公共css抽离出来:

    在这里插入图片描述

  2. 在组件类中重写css_file方法:

    class OrderModule(web.UIModule):
    
        def cal_total(self, price, nums):
            return price * nums
    
        def render(self, order, *args, **kwargs) -> str:
            """
            ui模板子组件:订单列表
            :param order: 父组件传递参数
            :param args:
            :param kwargs:
            :return:
            """
            return self.render_string("ui_modules\order_list.html", order=order, cal_total=self.cal_total)
    
        def css_files(self):
            return ["ui_modules/order_list.css"]
    
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一切如来心秘密

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值