Tornado全稱Tornado Web Server,是一個用Python語言寫成的Web服務器兼Web應用框架,由FriendFeed公司在自己的網站FriendFeed中使用,被Facebook收購以後框架在2009年9月以開源軟件形式開放給大衆。
Tornado與其他Web框架的區別
以Django爲代表的python web應用部署時採用wsgi協議與服務器對接(被服務器託管),而這類服務器通常都是基於多線程的,也就是說每一個網絡請求服務器都會有一個對應的線程來用web應用(如Django)進行處理。
考慮兩類應用場景
-
用戶量大,高併發
如秒殺搶購、雙十一某寶購物、春節搶火車票
-
大量的HTTP持久連接
使用同一個TCP連接來發送和接收多個HTTP請求/應答,而不是爲每一個新的請求/應答打開新的連接的方法。
對於HTTP 1.0,可以在請求的包頭(Header)中添加Connection: Keep-Alive。
對於HTTP 1.1,所有的連接默認都是持久連接。
對於這兩種場景,通常基於多線程的服務器很難應對。
對於前面提出的這種高併發問題,我們通常用C10K這一概念來描述。C10K—— Concurrently handling ten thousandconnections,即併發10000個連接。對於單臺服務器而言,根本無法承擔,而採用多臺服務器分佈式又意味着高昂的成本。如何解決C10K問題?
Tornado在設計之初就考慮到了性能因素,旨在解決C10K問題,這樣的設計使得其成爲一個擁有非常高性能的解決方案(服務器與框架的集合體)。
Hello Word
第一個py
安裝Tornado部分略過,我們直接進入正題,PyCharm沒有新建Tornado項目的選項,我們直接新建一個py即可。
1 # -*- coding=utf-8 -*-
2 import tornado.web
3 import tornado.ioloop
4
5
6 class Index(tornado.web.RequestHandler):
7 # 封裝一個類
8 def get(self):
9 # get請求進入該方法
10 # 返回字符串
11 self.write('Hello World')
12
13
14 class Home(tornado.web.RequestHandler):
15 def get(self):
16 self.write('Home')
17
18
19 if __name__ == '__main__':
20 app = tornado.web.Application([
21 # 相當於路由
22 (r'/', Index),
23 (r'/home', Home),
24 ])
25 # 指定端口
26 app.listen(8000)
27 # 開啓
28 tornado.ioloop.IOLoop.current().start()
我們直接啓動即可,然後使用postman或者瀏覽器訪問(我習慣使用postman)
http://localhost:8000
我們測試一下結果
當服務器收到請求時會進入Application,進入路由順序查找匹配。匹配到進入相關class,再根據方法進行處理
如果沒有對應class報404,沒有相應的方法報405
開啓多進程
之前說過Tornado的特點便是多進程,但是上面的代碼是單進程的,我們需要修改代碼來開啓多進程
# -*- coding=utf-8 -*-
import tornado.web
import tornado.httpserver
import tornado.ioloop
class Index(tornado.web.RequestHandler):
# 封裝一個類
def get(self):
# get請求進入該方法
# 返回字符串
self.write('Hello World')
class Home(tornado.web.RequestHandler):
def get(self):
self.write('Home')
if __name__ == '__main__':
app = tornado.web.Application([
# 相當於路由
(r'/', Index),
(r'/home', Home),
])
# 手動生成server
http_server = tornado.httpserver.HTTPServer(app)
# 指定端口
http_server.bind(8000)
# 開啓多進程
http_server.start(0)
# 開啓
tornado.ioloop.IOLoop.current().start()
注意:指定多進程在linux上可行,在windows會報錯,因爲fork這個系統命令,只在linux中才有用。所以windows請留空(默認爲1)或者填1
http_server.start(num_processes=1)方法指定開啓幾個進程,參數num_processes默認值爲1,即默認僅開啓一個進程;如果num_processes爲None或者<=0,則自動根據機器硬件的cpu核芯數創建同等數目的子進程;如果num_processes>0,則創建num_processes個子進程。
雖然tornado給我們提供了一次開啓多個進程的方法,但是由於:
- 每個子進程都會從父進程中複製一份IOLoop實例,如過在創建子進程前我們的代碼動了IOLoop實例,那麼會影響到每一個子進程,勢必會干擾到子進程IOLoop的工作;
- 所有進程是由一個命令一次開啓的,也就無法做到在不停服務的情況下更新代碼;
- 所有進程共享同一個端口,想要分別單獨監控每一個進程就很困難。
不建議使用這種多進程的方式,而是手動開啓多個進程,並且綁定不同的端口。
Tornado options組件(命令行加參數)
像端口這種易改變的配置寫在代碼裏則會有解耦性的問題,這個時候我們就需要Tornado的options組件。他可以幫助我們實現全局參數的定義存儲和轉換。
tornado.options.define()
參數有
- name 選項變量名,須保證全局唯一性,否則會報“Option 'xxx' already defined in ...”的錯誤;
- default 選項變量的默認值,如不傳默認爲None;
- type 選項變量的類型,從命令行或配置文件導入參數的時候tornado會根據這個類型轉換輸入的值,轉換不成功時會報錯,可以是str、float、int、datetime、timedelta中的某個,若未設置則根據default的值自動推斷,若default也未設置,那麼不再進行轉換。可以通過利用設置type類型字段來過濾不正確的輸入。
- multiple 選項變量的值是否可以爲多個,布爾類型,默認值爲False,如果multiple爲True,那麼設置選項變量時值與值之間用英文逗號分隔,而選項變量則是一個list列表(若默認值和輸入均未設置,則爲空列表[])。
- help 選項變量的幫助提示信息,在命令行啓動tornado時,通過加入命令行參數 --help 可以查看所有選項變量的信息(注意,代碼中需要加入tornado.options.parse_command_line())。
define("port", default=8000, help="run on the given port", type=int)
此處做到可以在啓動時輸入一個port,來指定端口,不傳默認爲8000,輸入類型爲int
python tdo_helloword.py --port=9000
指定端口爲9000
那麼代碼修改爲
# -*- coding=utf-8 -*-
import tornado.web
import tornado.ioloop
import tornado.httpserver
import tornado.options
# 可以多行
tornado.options.define("port", default=8000, help="run on the given port", type=int)
class Index(tornado.web.RequestHandler):
# 封裝一個類
def get(self):
# get請求進入該方法
# 返回字符串
self.write('Hello World')
class Home(tornado.web.RequestHandler):
def get(self):
self.write('Home')
if __name__ == '__main__':
# 注意加上這句
tornado.options.parse_command_line()
app = tornado.web.Application([
# 相當於路由
(r'/', Index),
(r'/home', Home),
])
# 手動生成server
http_server = tornado.httpserver.HTTPServer(app)
# 指定端口
http_server.bind(tornado.options.options.port)
# 開啓多進程
http_server.start(1)
# 開啓
tornado.ioloop.IOLoop.current().start()
我們在命令行加入port參數
python tdo_helloword.py --port=9999
查看效果
Tornado options組件(從配置文件導入)
配置文件格式要對
我們要更新一下代碼
# -*- coding=utf-8 -*-
import tornado.web
import tornado.ioloop
import tornado.httpserver
import tornado.options
# 可以多行
tornado.options.define("port", default=8000, help="run on the given port", type=int)
class Index(tornado.web.RequestHandler):
# 封裝一個類
def get(self):
# get請求進入該方法
# 返回字符串
self.write('Hello World')
class Home(tornado.web.RequestHandler):
def get(self):
self.write('Home')
if __name__ == '__main__':
# 注意加上這句
# tornado.options.parse_command_line()
# 從文件讀取配置
tornado.options.parse_config_file('./config')
app = tornado.web.Application([
# 相當於路由
(r'/', Index),
(r'/home', Home),
])
# 手動生成server
http_server = tornado.httpserver.HTTPServer(app)
# 指定端口
http_server.bind(tornado.options.options.port)
# 開啓多進程
http_server.start(1)
# 開啓
tornado.ioloop.IOLoop.current().start()
這樣便是讀取配置文件來啓動tornado
關閉日誌
在我們訪問網站的時候,我們會發現屏幕打印了訪問和返回的記錄,我們可以將他關閉
想要關閉,我們可以在開啓時加上--logging=none
python td_helloword.py --logging=none
或者修改代碼爲
1 # -*- coding=utf-8 -*-
2 import tornado.web
3 import tornado.ioloop
4 import tornado.httpserver
5 import tornado.options
6 # 可以多行
7 tornado.options.define("port", default=8000, help="run on the given port", type=int)
8
9
10 class Index(tornado.web.RequestHandler):
11 # 封裝一個類
12 def get(self):
13 # get請求進入該方法
14 # 返回字符串
15 self.write('Hello World')
16
17
18 class Home(tornado.web.RequestHandler):
19 def get(self):
20 self.write('Home')
21
22
23 if __name__ == '__main__':
24 # 不打印日誌
25 tornado.options.options.logging = None
26 tornado.options.parse_command_line()
27 # 從文件讀取配置
28 # tornado.options.parse_config_file('./config')
29 app = tornado.web.Application([
30 # 相當於路由
31 (r'/', Index),
32 (r'/home', Home),
33 ])
34 # 手動生成server
35 http_server = tornado.httpserver.HTTPServer(app)
36 # 指定端口
37 http_server.bind(tornado.options.options.port)
38 # 開啓多進程
39 http_server.start(1)
40 # 開啓
41 tornado.ioloop.IOLoop.current().start()
路由
路由的匹配
Tornado的路由匹配採用的是正則匹配
一般情況下不需要多複雜的正則,正則的基本規則如下(站長之家)
舉個例子
(r'/sum/(\d+)', Sum),
該代碼指匹配 /sum/ 後跟至少一個數字且只有數字的情況
* 需要注意的是網絡上傳輸都是字符串
類 Sum編寫
class Sum(tornado.web.RequestHandler):
# 數字類
def get(self, sum):
# 獲取數字並返回
self.write('%s,%s' % (type(sum), sum))
我們訪問 http://localhost:8001/sum/12 時
返回正常
我們訪問 http://localhost:8001/sum/1a2 http://localhost:8001/sum/a12 http://localhost:8001/sum/12a http://localhost:8001/sum/a 時,均會報出404錯誤,證明沒有匹配到路由
同理,當我們需要匹配兩個參數時
(r'/(\w+)/stuggle/(\d+)', Stugggle),
接收時接收兩個參數即可
def get(self, st, ins):
pass
post參數
與get一樣,post請求會尋找到該視圖的 post 方法
我們給視圖 Hello 增加一個post
class Hello(tornado.web.RequestHandler):
# 封裝一個類
def get(self):
# get請求進入該方法
self.write('Hello')
def post(self):
# post請求
txt = self.get_argument('txt')
self.write(txt)
self.get_argument('txt') 指獲取post傳參中 Key 爲 txt 的值,路由無需改動
get參數
get獲取參數與上面的post沒有差別
我們修改get方法來進行測試
def get(self):
# get請求進入該方法
arg = self.get_argument('arg')
arg1 = self.get_argument('arg1')
self.write('%s+%s' % (arg,arg1))
但是需要注意的是, get_argument 在獲取不到該key時會報錯,拋出400錯誤
get_argument 方法其實可以接收三個參數
get_argument(name,default=_ARG_DEFAULT,strip=True)
第一個參數就是key的值,第二個參數爲如果接收不到默認的值,第三個是默認去除前後空格
一般情況下我們第二個參數傳 None
def get(self):
# get請求進入該方法
arg = self.get_argument('arg', None)
arg1 = self.get_argument('arg1', None)
self.write('%s+%s' % (arg,arg1))
def post(self):
# post請求
txt = self.get_argument('txt', None)
self.write(txt)
這樣就增加了兼容性
本章我們來學習 Tornado 支持的請求方式
請求方式
Tornado支持任何合法的HTTP請求(GET、POST、PUT、DELETE、HEAD、OPTIONS)。你可以非常容易地定義上述任一種方法的行爲,只需要在 RequestHandler 類中使用同名的方法。(也就是在視圖中定義同名的方法)
關於請求方式對應的操作和返回碼可以參考 http://www.runoob.com/w3cnote/restful-architecture.html 不過具體還是要與業務進行匹配。
狀態碼
使用RequestHandler類的set_status()方法顯式地設置HTTP狀態碼。然而,你需要記住在某些情況下,Tornado會自動地設置HTTP狀態碼。
比如如果訪問一個無法匹配的路由,則會返回404報錯,而且狀態碼是404
但是我們在代碼中沒有指定返回的狀態碼,常用的有:
404 Not Found
Tornado會在HTTP請求的路徑無法匹配任何RequestHandler類相對應的模式時返回404(Not Found)響應碼。
400 Bad Request
如果你調用了一個沒有默認值的get_argument函數,並且沒有發現給定名稱的參數,Tornado將自動返回一個400(Bad Request)響應碼。
405 Method Not Allowed
如果傳入的請求使用了RequestHandler中沒有定義的HTTP方法(比如,一個POST請求,但是處理函數中只有定義了get方法),Tornado將返回一個405(Methos Not Allowed)響應碼。
500 Internal Server Error
當程序遇到任何不能讓其退出的錯誤時,Tornado將返回500(Internal Server Error)響應碼。你代碼中任何沒有捕獲的異常也會導致500響應碼。
200 OK
如果響應成功,並且沒有其他返回碼被設置,Tornado將默認返回一個200(OK)響應碼。
我們也可以自定義錯誤返回,只需在class中定義一個名爲 write_error 的方法
比如
def write_error(self, status_code, **kwargs):
self.write('%s' % status_code)
該段代碼會將 錯誤code以字符串的形式返回回去
需要注意的是該方法是寫在類中,也就是說該方法只能作用於一個視圖。