Tornado

PENGJUNQIAO發表於2022-03-01

Tornado

文件:https://tornado-zh-cn.readthedocs.io/zh_CN/latest/

github:https://github.com/tornadoweb/tornado

介紹

Tornado是使用Python開發的全棧式(full-stack)Web框架和非同步網路庫,最早由4名Google前軟體工程師(佈雷特·泰勒)2007創辦的Friendfeed(一個社交聚合網站)開發而來的。通過使用非阻塞IO,Tornado可以處理數以萬計的開放連線,是long polling、WebSockets和其他需要為使用者維護長連線應用的理想選擇。

目前最新版本6.1, 我們實際專案開發是使用的不可能是最新版本,所以在此我們在tornado基礎階段所學所用的版本為6.0.

特點

  • 開源的輕量級全棧式Web框架,提供了一整套完善的非同步編碼方案。
  • 高效能
    基於協程,底層就是基於asyio來實現的完整的協程排程
    採用非同步非阻塞IO處理方式,不依賴多程式或多執行緒
    採用單程式單執行緒非同步IO的網路模式,其高效能源於Tornado基於Linux的Epoll(UNIX為kqueue)的非同步網路IO,具有出色的抗負載能力
    Tornado為了實現高併發和高效能,使用了一個IOLoop事件迴圈來處理socket的讀寫事件
  • WSGI全棧替代產品,Tornado把應用(Application)和伺服器(Server)結合起來,既是WSGI應用也可以是WSGI服務,通俗來講就是說,Tornado既是web伺服器也是web框架,甚至可以通過Tornado替代uwsgi/gunicorn來執行Flask或者django框架

django,flask和tornado對比

內建功能模組來說: django > flask > tornado
使用入門門檻: django < flask < tornado

Tornado 可以被分為以下四個主要部分:

  • Web 框架 (包括用來建立 Web 應用程式的 RequestHandler 類, 還有很多其它支援的類).
  • HTTP 客戶端和伺服器的實現 (HTTPServer 和 AsyncHTTPClient).
  • 非同步網路庫 (IOLoop 和 IOStream), 對 HTTP 的實現提供構建模組, 還可以用來實現其他協議.
  • 協程庫 (tornado.gen) 讓使用者通過更直接的方法來實現非同步程式設計, 而不是通過回撥的方式.

安裝

mkvirtualenv tornado_demo
pip install tornado==6.0.4
cd ~/Desktop
mkdir tdemo
cd tdemo

入門

專案基本執行

server.py

from tornado import ioloop
from tornado import web

class Home(web.RequestHandler):
    def get(self):
		# self.write 響應資料
        self.write("hello!")

def make_app():
    # Application是tornado web框架的核心應用類,是與伺服器對應的介面,裡面儲存了路由對映表
    # handlers 設定路由列表
    return web.Application(handlers=[
        (r"/", Home),
    ])

if __name__ == "__main__":
    # 建立應用例項物件
    app = make_app()
    # 設定監聽的埠和地址
    app.listen(8888)
    # ioloop,全域性的tornado事件迴圈,是伺服器的引擎核心,start表示建立IO事件迴圈,等待客戶端連線
    ioloop.IOLoop.current().start()

終端執行專案

server.py,程式碼:

from tornado import ioloop
from tornado import web
from tornado.options import define,options,parse_command_line
define("port", default=8888, type=int,help="設定監聽埠號,預設為8888")
class Home(web.RequestHandler):
    def get(self):
		# self.write 響應資料
        self.write("hello!")

def make_app():
    # handlers 設定路由列表
    return web.Application(handlers=[
        (r"/", Home),
    ])

if __name__ == "__main__":
    # 解析終端啟動命令,格式:python server.py --port=埠號
    parse_command_line()
    # 建立應用例項物件
    app = make_app()
    # 設定監聽的埠和地址
    app.listen(options.port) # options.port 接收引數
    # ioloop,全域性的tornado事件迴圈,是伺服器的引擎核心,start表示建立IO事件迴圈
    ioloop.IOLoop.current().start()

開啟除錯模式

開啟自動載入和除錯模式,開啟了debug模式以後, 專案在編輯python檔案的時候自動重啟專案並且在出現異常時顯示錯誤跟蹤資訊

server.py, 程式碼:

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

# 配置資訊
settings = {
    'debug' : True,
}

define("port", default=8888, type=int,help="設定監聽埠號,預設為8888")

# 類檢視
class Home(web.RequestHandler):
    # 檢視方法
    def get(self):
		# self.write 響應資料
        self.write("hello!")

def make_app():
    # handlers 設定路由列表
    return web.Application(handlers=[
        (r"/", Home),
    ],**settings) # 載入配置

if __name__ == "__main__":
    # 建立應用例項物件
    parse_command_line()
    app = make_app()
    # 設定監聽的埠和地址
    app.listen(options.port)
    # ioloop,全域性的tornado事件迴圈,是伺服器的引擎核心,start表示建立IO事件迴圈
    ioloop.IOLoop.current().start()

路由拆分

程式碼:

from tornado import ioloop
from tornado import web
from tornado.options import define,options,parse_command_line
# 專案配置
settings = {
    "debug": True, # 開啟debug模式
}

# 檢視類必須要直接或者間接繼承於 web.RequestHandler
class Home(web.RequestHandler):
    def get(self): # http請求
        # 響應資料
        self.write("<h1>hello! oldboyEdu</h1>")
        self.write("hello tornado!") # 這裡也是可以被執行的

        return
        self.write("hello world") # 只有在return以後,才不會被執行

# 路由列表
urls = [
    # (r"uri路徑", 檢視類),
    (r"/", Home),
]

if __name__ == "__main__":

    # 定義終端命令列引數
    define(name="port",default=8888,type=int) # python 主程式.py --port=8888
    define(name="host", default="127.0.0.1", type=str)  # python 主程式.py --port=8888 --host=127.0.0.1
    define(name="debug", default=False, type=bool) # # python 主程式.py --debug=True
    # 啟動終端命令列引數解析方法
    parse_command_line()
    # 建立web應用例項物件

    # Application是tornado web框架的核心應用類,是與伺服器對應的介面,裡面儲存了路由對映表
    settings["debug"] = options.debug
    app = web.Application(
        urls,
        **settings,
    )
    # 設定監聽的埠和地址
    app.listen(port=options.port,address=options.host)
    # ioloop,全域性的tornado事件迴圈,是伺服器的引擎核心,start表示建立IO事件迴圈,等待客戶端連線
    ioloop.IOLoop.current().start()

檢視編寫

在tornado中, tornado.web框架本身就預設提供了rest風格的API介面模式. 可以直接通過CBV(類檢視)對外提供基本的http 檢視介面.

from tornado import ioloop
from tornado import web
from tornado.options import define,options,parse_command_line
# 專案配置
settings = {
    "debug": True, # 開啟debug模式
}

# 檢視類必須要直接或者間接繼承於 web.RequestHandler
class Home(web.RequestHandler):
    def get(self): # http請求
        # 響應資料
        self.write("<h1>hello! oldboyEdu</h1>")
        self.write("hello tornado!") # 這裡也是可以被執行的

        return
        self.write("hello world") # 只有在return以後,才不會被執行

    def post(self):
        self.write("hello!post")

    def put(self):
        self.write("hello!put")

    def patch(self):
        self.write("hello!patch")

    def delete(self):
        self.write("hello!delete")

# 路由列表
urls = [
    # (r"uri路徑", 檢視類),
    (r"/", Home),
]

if __name__ == "__main__":

    # 定義終端命令列引數
    define(name="port",default=8888,type=int) # python 主程式.py --port=8888
    define(name="host", default="127.0.0.1", type=str)  # python 主程式.py --port=8888 --host=127.0.0.1
    define(name="debug", default=False, type=bool) # # python 主程式.py --debug=True
    # 啟動終端命令列引數解析方法
    parse_command_line()
    # 建立web應用例項物件

    # Application是tornado web框架的核心應用類,是與伺服器對應的介面,裡面儲存了路由對映表
    settings["debug"] = options.debug
    app = web.Application(
        urls,
        **settings,
    )
    # 設定監聽的埠和地址
    app.listen(port=options.port,address=options.host)
    # ioloop,全域性的tornado事件迴圈,是伺服器的引擎核心,start表示建立IO事件迴圈,等待客戶端連線
    ioloop.IOLoop.current().start()

多程式模式

from tornado import ioloop
from tornado import web,httpserver
from tornado import autoreload
from tornado.options import define,options,parse_command_line

settings = {
    'debug' : False,
}

define("port", default=8888, type=int,help="設定監聽埠號,預設為8888")

class Home(web.RequestHandler):
    def get(self):
        # self.write 響應資料
        self.write("hello!get")

    def post(self):
        self.write("hello!post")

    def put(self):
        self.write("hello!put")

    def patch(self):
        self.write("hello!patch")

    def delete(self):
        self.write("hello!delete")

# 路由列表
urls = [
    (r"/", Home),
]

if __name__ == "__main__":
    app = web.Application(urls,**settings)
    # 建立應用例項物件
    parse_command_line()
    server = httpserver.HTTPServer(app)
    # 設定監聽的埠和地址
    server.bind(options.port)
    server.start(0) # 0表示程式=CPU核數
    # ioloop,全域性的tornado事件迴圈,是伺服器的引擎核心,start表示建立IO事件迴圈
    ioloop.IOLoop.current().start()

請求與響應

請求

tornado.httputil.HTTPServerRequest

server.py,程式碼:

from tornado import ioloop
from tornado import web

# 專案配置
settings = {
    "debug": True, # 開啟debug模式
}

# 檢視類必須要直接或者間接繼承於 web.RequestHandler
class Home(web.RequestHandler):
    def get(self): # http請求方法
        # 響應資料
        # print(self.settings) # 配置資訊
        # print(self.request)  # 獲取http請求處理的例項物件
        # # HTTPServerRequest(protocol='http', host='127.0.0.1:8000', method='GET', uri='/', version='HTTP/1.1', remote_ip='127.0.0.1')

        print("通訊協議: ",self.request.protocol) # 協議
        print("請求方法: ",self.request.method) # Http請求方法
        print("uri地址: ",self.request.uri)    # uri地址
        print("url地址: ",self.request.full_url())    # 完整url地址
        print("協議版本: ",self.request.version) # 協議版本
        print("請求頭: ")
        print(self.request.headers) # 請求頭 HTTPHeaders
        print("地址埠: ", self.request.host)  # 地址埠

        self.write("hello!get") # 這裡也是可以被執行的
    def post(self):
        # print("請求體: ", self.request.body) # 請求體[原始資料]
        # import json
        # body = json.loads(self.request.body.decode())
        # print(body) # {'jsonrpc': '2.0', 'id': 1, 'method': 'Live.stream.list', 'params': {}}

        # print("上傳檔案: ",self.request.files)# 上傳檔案
        # print("cookie資訊: ")
        # print(self.request.cookies) # cookie資訊
        # print("當前客戶端的IP地址: ", self.request.remote_ip) # 客戶端IP地址
        print(self.request.request_time())  # 請求處理的花費時間

        self.write("hello!post")

    def put(self):
        # 開發中很少使用
        print("查詢字串引數列表: ",self.request.query_arguments) # 查詢字串引數列表
        print("請求體引數列表: ",self.request.body_arguments) # 請求體引數列表
        self.write("hello!put")

# 路由列表
urls = [
    # (r"uri路徑", 檢視類),
    (r"/", Home),
]

if __name__ == "__main__":
    # Application是tornado web框架的核心應用類,是與伺服器對應的介面,裡面儲存了路由對映表
    app = web.Application(
        urls,
        **settings,
    )
    # 設定監聽的埠和地址
    # 啟動http多程式服務
    app.listen(port=8000,address="0.0.0.0")
    # ioloop,全域性的tornado事件迴圈,是伺服器的引擎核心,start表示建立IO事件迴圈,等待客戶端連線
    ioloop.IOLoop.current().start()

接收查詢字串

server.py,程式碼:

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

settings = {
    'debug' : True,
}

define("port", default=8888, type=int,help="設定監聽埠號,預設為8888")
class Home(web.RequestHandler):
    def get(self):
        # print(self.request.arguments["name"][0].decode())
        # name = self.get_argument("name") # self.get_query_argument("name")
        # print(name) # xiaoming
        names = self.get_arguments("name") # # self.get_query_arguments("name")
        print(names) # ['xiaoming', '123']
        self.write("hello world")

# 設定路由列表
urls = [
    (r"/", Home),
]

if __name__ == "__main__":
    # 建立應用例項物件
    parse_command_line()
    app = web.Application(urls,**settings)
    # 設定監聽的埠和地址
    app.listen(options.port)
    # ioloop,全域性的tornado事件迴圈,是伺服器的引擎核心,start表示建立IO事件迴圈
    ioloop.IOLoop.current().start()

瀏覽器:http://127.0.0.1:8888/?name=xiaoming&name=xiaohong

接收請求體

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

settings = {
    'debug' : True,
}

define("port", default=8888, type=int,help="設定監聽埠號,預設為8888")
class Home(web.RequestHandler):
    def get(self):
        # print(self.request.arguments["name"][0].decode())
        # name = self.get_argument("name") # self.get_query_argument("name")
        # print(name) # xiaoming
        names = self.get_arguments("name") # # self.get_query_arguments("name")
        print(names) # ['xiaoming', '123']
        self.write("hello!get")

    def post(self):
        print(self.request.arguments) # {'name': [b'xiaoming', b'xiaohong']}
        print(self.request.body_arguments) # {'name': [b'xiaohong']}
        print(self.get_argument("name")) # xiaohong
        print(self.get_body_argument("name")) # xiaohong
        print(self.get_arguments("name")) # ['xiaoming', 'xiaohong']
        print(self.get_body_arguments("name")) # ['xiaohong']
        self.write("hello!post")
        
# 設定路由列表
urls = [
    (r"/", Home),
]

if __name__ == "__main__":
    # 建立應用例項物件
    parse_command_line()
    app = web.Application(urls,**settings)
    # 設定監聽的埠和地址
    app.listen(options.port)
    # ioloop,全域性的tornado事件迴圈,是伺服器的引擎核心,start表示建立IO事件迴圈
    ioloop.IOLoop.current().start()

接收路由引數

from tornado import ioloop
from tornado import web

# 專案配置
settings = {
    "debug": True, # 開啟debug模式
}

# 檢視類必須要直接或者間接繼承於 web.RequestHandler
class Home(web.RequestHandler):
    def get(self,cat,id): # 路由引數的形參,分別匹配路由中對應小括號中的url地址內容
        self.write("hello!cat=%s,id=%s" % (cat,id))

class Index(web.RequestHandler):
    def get(self,cat,id):
        self.write("hello!cat=%s,id=%s" % (cat, id))

# 路由列表
urls = [
    # (r"uri路徑", 檢視類),
    # (r"/(引數1的正則)/(引數2的正則).html", Home), # 位置引數
    (r"/([0-9]+)-([0-9]+).html", Home), # 位置引數
    # (r"/index/(?P<引數名>正則).html", Index), # 命名引數
    (r"/index/(?P<id>[0-9]+)-(?P<cat>[0-9]+).html", Index), # 命名引數
]

if __name__ == "__main__":
    # Application是tornado web框架的核心應用類,是與伺服器對應的介面,裡面儲存了路由對映表
    app = web.Application(
        urls,
        **settings,
    )
    # 設定監聽的埠和地址
    # 啟動http多程式服務
    app.listen(port=8000,address="0.0.0.0")
    # ioloop,全域性的tornado事件迴圈,是伺服器的引擎核心,start表示建立IO事件迴圈,等待客戶端連線
    ioloop.IOLoop.current().start()

響應

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

settings = {
    'debug': True,
}

define("port", default=8888, type=int, help="設定監聽埠號,預設為8888")

from datetime import datetime
class Home(web.RequestHandler):
    def set_default_headers(self):
        self.set_header("time", int(datetime.now().timestamp()))
    def get(self):
    def get(self):
        # write 會自動識別
        # self.write("<h1>hello world</h1>") # 響應html文件
        # self.write({"msg":"ok"}) # 響應json資料

        # 注意,json的資料格式也可以是列表,tornado中預設不支援返回list,所以如果返回list,則需要重寫write
        # self.write([1,2,3])
        
        # 重新設定響應頭的內容 set_header[修改]
        self.set_header("Content-Type","text/json; charset=gbk")
        # 自定義響應頭 add_header[新增]
        self.add_header("Company","OldBoyEdu")
        self.add_header("Server","OldBoyEduServer/1.0")
        # self.clear_header("Server")  # 從響應頭中刪除指定名稱的響應頭資訊

    def post(self):
        # self.set_status(404,"No User") # 第二個參數列示響應描述,如果不設定,則顯示原來對應的
        # self.send_error(500,reason="伺服器炸了!")
        self.send_error(404, msg="伺服器炸了!", info="快報警") # 要使用send_error必須先宣告send_error方法

    def write_error(self, status_code, **kwargs):
        self.write("<h1>完蛋啦...</h1>")
        self.write("<p>錯誤資訊:%s</p>" % kwargs["msg"])
        self.write("<p>錯誤描述:%s</p>" % kwargs["info"])

    def put(self):
        # 頁面響應
        self.redirect("http://www.oldboyedu.com")

# 設定路由列表
urls = [
    (r"/", Home),
]

if __name__ == "__main__":
    # 建立應用例項物件
    parse_command_line()
    app = web.Application(urls, **settings)
    # 設定監聽的埠和地址
    app.listen(options.port)
    # ioloop,全域性的tornado事件迴圈,是伺服器的引擎核心,start表示建立IO事件迴圈
    ioloop.IOLoop.current().start()

cookie

# 獲取和設定cookie
self.set_cookie(name, value)
self.get_cookie(name)


# 獲取和設定cookie[加密]
settings = {cookie_secret:"u3h7AQnM2WdTL1o"}

self.set_secure_cookie(name, value)
self.get_secure_cookie(name)

# 刪除cookie
self.clear_cookie(name)

# 清空cookie
self.clear_all_cookie()

tornado沒有提供session操作,如果需要使用到session可以自己實現或者引入第三方模組。

靜態檔案

import os
settings = {
    'debug': True,
    # 靜態檔案儲存路徑
    "static_path": os.path.join(os.path.dirname(__file__), 'static'),
    # 靜態檔案url地址字首
    "static_url_prefix":"/static/", # 必須前後有斜槓
}

頁面響應

from tornado import ioloop
from tornado import web
from tornado.options import define, options, parse_command_line
import os
settings = {
    'debug': True,
    # 靜態檔案儲存路徑
    "static_path": os.path.join(os.path.dirname(__file__), 'static'),
    # 靜態檔案url地址字首
    "static_url_prefix":"/static/", # 必須前後有斜槓
    "template_path": os.path.join(os.path.dirname(__file__), 'templates'),
}

define("port", default=8888, type=int, help="設定監聽埠號,預設為8888")

class Home(web.RequestHandler):
    def get(self):
        self.render("index.html")

# 設定路由列表
urls = [
    (r"/", Home),
]

if __name__ == "__main__":
    # 建立應用例項物件
    parse_command_line()
    app = web.Application(urls, **settings)

    # 設定監聽的埠和地址
    app.listen(options.port)
    # ioloop,全域性的tornado事件迴圈,是伺服器的引擎核心,start表示建立IO事件迴圈
    ioloop.IOLoop.current().start()

templates/index.html,程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    tornado預設內建了一套非常強大的模板引擎<br>
    這套模板引擎是基於jinja2模板引擎的基礎上進行了改造而成的。<br>
    當然jinja2是基於django的DTL模板引擎基礎上改造而成的。<br>
    所以flask和tornado進行比較的時候,從來不提tornado抄襲模板引擎這個事,反而會和django去比較模板引擎的問題。
</body>
</html>

相關文章