Tornado是使用Python編寫的一個強大的、可擴展的Web服務器。它在處理嚴峻的網絡流量時表現得足夠強健,但卻在建立和編寫時有着足夠的輕量級,並可以被用在大量的應用和工具中。
咱們如今所知道的Tornado是基於Bret Taylor和其餘人員爲FriendFeed所開發的網絡服務框架,當FriendFeed被Facebook收購後得以開源。不一樣於那些最多隻能達到10,000個併發鏈接的傳統網絡服務器,Tornado在設計之初就考慮到了性能因素,旨在解決C10K問題,這樣的設計使得其成爲一個擁有很是高性能的框架。此外,它還擁有處理安全性、用戶驗證、社交網絡以及與外部服務(如數據庫和網站API)進行異步交互的工具。
延伸閱讀:C10K問題
基於線程的服務器,如Apache,爲了傳入的鏈接,維護了一個操做系統的線程池。Apache會爲每一個HTTP鏈接分配線程池中的一個線程,若是全部的線程都處於被佔用的狀態而且尚有內存可用時,則生成一個新的線程。儘管不一樣的操做系統會有不一樣的設置,大多數Linux發佈版中都是默認線程堆大小爲8MB。Apache的架構在大負載下變得不可預測,爲每一個打開的鏈接維護一個大的線程池等待數據極易迅速耗光服務器的內存資源。
大多數社交網絡應用都會展現實時更新來提醒新消息、狀態變化以及用戶通知,這就要求客戶端須要保持一個打開的鏈接來等待服務器端的任何響應。這些長鏈接或推送請求使得Apache的最大線程池迅速飽和。一旦線程池的資源耗盡,服務器將不能再響應新的請求。
異步服務器在這一場景中的應用相對較新,但他們正是被設計用來減輕基於線程的服務器的限制的。當負載增長時,諸如Node.js,lighttpd和Tornodo這樣的服務器使用協做的多任務的方式進行優雅的擴展。也就是說,若是當前請求正在等待來自其餘資源的數據(好比數據庫查詢或HTTP請求)時,一個異步服務器能夠明確地控制以掛起請求。異步服務器用來恢復暫停的操做的一個常見模式是當合適的數據準備好時調用回調函數。咱們將會在第五章講解回調函數模式以及一系列Tornado異步功能的應用。
自從2009年9月10日發佈以來,Tornado已經得到了不少社區的支持,而且在一系列不一樣的場合獲得應用。除FriendFeed和Facebook外,還有不少公司在生產上轉向Tornado,包括Quora、Turntable.fm、Bit.ly、Hipmunk以及MyYearbook等。
總之,若是你在尋找你那龐大的CMS或一體化開發框架的替代品,Tornado可能並非一個好的選擇。Tornado並不須要你擁有龐大的模型創建特殊的方式,或以某種肯定的形式處理表單,或其餘相似的事情。它所作的是讓你可以快速簡單地編寫高速的Web應用。若是你想編寫一個可擴展的社交應用、實時分析引擎,或RESTful API,那麼簡單而強大的Python,以及Tornado(和這本書)正是爲你準備的!
1.1.1 Tornado入門
安裝
pip install tornado
一旦Tornado在你的機器上安裝好,你就能夠很好的開始了!壓縮包中包含不少demo,好比創建博客、整合Facebook、運行聊天服務等的示例代碼。咱們稍後會在本書中經過一些示例應用逐步講解,不過你也應該看看這些官方demo
既然咱們已經知道了Tornado是什麼了,如今讓咱們看看它能作什麼吧。咱們首先從使用Tornado編寫一個簡單的Web應用開始。
1.2.1 Hello Tornado
Tornado是一個編寫對HTTP請求響應的框架。做爲程序員,你的工做是編寫響應特定條件HTTP請求的響應的handler。下面是一個全功能的Tornado應用的基礎示例:
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class IndexHandler(tornado.web.RequestHandler):
def get(self):
greeting = self.get_argument('greeting', 'Hello')
self.write(greeting + ', friendly user!')
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(
handlers=[
(r"/", IndexHandler)
]
)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
編寫一個Tornado應用中最多的工做是定義類繼承Tornado的RequestHandler類。在這個例子中,咱們建立了一個簡單的應用,在給定的端口監聽請求,並在根目錄("/")響應請求。python
你能夠在命令行裏嘗試運行這個程序以測試輸出
$ python hello.py --port=8000
curl http://192.168.201.130:8000/
Hello, friendly user!
curl http://192.168.201.130:8000/?greeting=Salutations
Salutations, friendly user!
讓咱們把這個例子分紅小塊,逐步分析它們:
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
一、在程序的最頂部,咱們導入了一些Tornado模塊。雖然Tornado還有另一些有用的模塊,但在這個例子中咱們必須至少包含這四個模塊。
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
二、Tornado包括了一個有用的模塊(
tornado.options
)來從命令行中讀取設置。咱們在這裏使用這個模塊指定咱們的應用監聽HTTP請求的端口。它的工做流程以下:若是一個與
define
語句中同名的設置在命令行中被給出,那麼它將成爲全局
options
的一個屬性。若是用戶運行程序時使用了
--help
選項,程序將打印出全部你定義的選項以及你在
define
函數的
help
參數中指定的文本。若是用戶沒有爲這個選項指定值,則使用
default
的值進行代替。Tornado使用
type
參數進行基本的參數類型驗證,當不合適的類型被給出時拋出一個異常。所以,咱們容許一個整數的
port
參數做爲
options.port
來訪問程序。若是用戶沒有指定值,則默認爲8000。
$ python 1_lesson_helloworld.py -- help
class IndexHandler(tornado.web.RequestHandler):
def get(self):
greeting = self.get_argument('greeting', 'Hello')
self.write(greeting + ', friendly user!')
三、這是Tornado的請求處理函數類。當處理一個請求時,Tornado將這個類實例化,並調用與HTTP請求方法所對應的方法。在這個例子中,咱們只定義了一個
get
方法,也就是說這個處理函數將對HTTP的
GET
請求做出響應。咱們稍後將看到實現不止一個HTTP方法的處理函數。
greeting = self.get_argument('greeting', 'Hello')
四、Tornado的
RequestHandler
類有一系列有用的內建方法,包括
get_argument
,咱們在這裏從一個查詢字符串中取得參數
greeting
的值。(若是這個參數沒有出如今查詢字符串中,Tornado將使用
get_argument
的第二個參數做爲默認值。)
查詢字符串query string,在地址欄中 「?」 號後面的字符串, key01=value01&key02=value02
self.write(greeting + ', friendly user!')
五、RequestHandler
的另外一個有用的方法是
write
,它以一個字符串做爲函數的參數,並將其寫入到HTTP響應中。在這裏,咱們使用請求中
greeting
參數提供的值插入到greeting中,並寫回到響應中。
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
六、這是真正使得Tornado運轉起來的語句。首先,咱們使用Tornado的
options
模塊來解析命令行。而後咱們建立了一個Tornado的
Application
類的實例。傳遞給
Application
類
__init__
方法的最重要的參數是
handlers
。它告訴Tornado應該用哪一個類來響應請求。
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
七、從這裏開始的代碼將會被反覆使用:一旦
Application
對象被建立,咱們能夠將其傳遞給Tornado的
HTTPServer
對象,而後使用咱們在命令行指定的端口進行監聽(經過
options
對象取出。)最後,在程序準備好接收HTTP請求後,咱們建立一個Tornado的
IOLoop
的實例。
讓咱們再看一眼
hello.py
示例中的這一行:
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
這裏的參數
handlers
很是重要,值得咱們更加深刻的研究。它應該是一個元組組成的列表,其中每一個元組的第一個元素是一個用於匹配的正則表達式,第二個元素是一個
RequestHanlder
類。在
hello.py
中,咱們只指定了一個正則表達式-
RequestHanlder
對,但你能夠按你的須要指定任意多個。
Tornado在元組中使用正則表達式來匹配HTTP請求的路徑。(這個路徑是URL中主機名後面的部分,不包括查詢字符串和碎片。)Tornado把這些正則表達式看做已經包含了行開始和結束錨點(即,字符串"/"被看做爲"^/$")。git
若是一個正則表達式包含一個捕獲分組(即,正則表達式中的部分被括號括起來),匹配的內容將做爲相應HTTP請求的參數傳到
RequestHandler對象中。咱們將在下個例子中看到它的用法。
例1-2是一個咱們目前爲止看到的更復雜的例子,它將介紹更多Tornado的基本概念。
import textwrap
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class ReverseHandler(tornado.web.RequestHandler):
def get(self, input):
self.write(input[::-1])
class WrapHandler(tornado.web.RequestHandler):
def post(self):
text = self.get_argument('text')
width = self.get_argument('width', 40)
self.write(textwrap.fill(text, int(width)))
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(
handlers=[
(r"/reverse/(\w+)", ReverseHandler),
(r"/wrap", WrapHandler)
]
)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
如同運行第一個例子,你能夠在命令行中運行這個例子使用以下的命令:
$ python string_service.py --port=8000
這個程序是一個通用的字符串操做的Web服務端基本框架。到目前爲止,你能夠用它作兩件事情。其一,到
/reverse/string
的
GET
請求將會返回URL路徑中指定字符串的反轉形式。
$ curl http://192.168.201.130:8000/reverse/stressed
desserts
$ curl http://192.168.201.130:8000/reverse/slipup
pupils
其二,到
/wrap
的
POST
請求將從參數
text
中取得指定的文本,並返回按照參數
width
指定寬度裝飾的文本。下面的請求指定一個沒有寬度的字符串,因此它的輸出寬度被指定爲程序中的
get_argument
的默認值40個字符。
$ curl http://192.168.201.130:8000/ -d text=sfefgsgsergsdrhsdrtsdfhgsdrhsdrt
$ curl http://192.168.201.130:8000/ -d foo=bar
字符串服務示例和上一節示例代碼中大部分是同樣的。讓咱們關注那些新的代碼。首先,讓咱們看看傳遞給
Application
構造函數的
handlers
參數的值:
app = tornado.web.Application(handlers=[
(r"/reverse/(\w+)", ReverseHandler),
(r"/wrap", WrapHandler)
])
一、在上面的代碼中,
Application
類在"handlers"參數中實例化了兩個
RequestHandler
類對象。第一個引導Tornado傳遞路徑匹配下面的正則表達式的請求:
正則表達式告訴Tornado匹配任何以字符串/reverse/開始並緊跟着一個或多個字母的路徑。括號的含義是讓Tornado保存匹配括號裏面表達式的字符串,並將其做爲請求方法的一個參數傳遞給
RequestHandler
類。讓咱們檢查
ReverseHandler
的定義來看看它是如何工做的:
class ReverseHandler(tornado.web.RequestHandler):
def get(self, input):
self.write(input[::-1])
你能夠看到這裏的get方法有一個額外的參數input。這個參數將包含匹配處理函數正則表達式第一個括號裏的字符串。(若是正則表達式中有一系列額外的括號,匹配的字符串將被按照在正則表達式中出現的順序做爲額外的參數傳遞進來。)程序員
二、如今,讓咱們看一下WrapHandler的定義:
class WrapHandler(tornado.web.RequestHandler):
def post(self):
text = self.get_argument('text')
width = self.get_argument('width', 40)
self.write(textwrap.fill(text, int(width)))
WrapHandler類處理匹配路徑爲/wrap
的請求。這個處理函數定義了一個post方法,也就是說它接收HTTP的POST方法的請求。
咱們以前使用
RequestHandler對象的
get_argument方法來捕獲請求查詢字符串的的參數。一樣,咱們也可使用相同的方法來得到
POST請求傳遞的參數。(Tornado能夠解析URLencoded和multipart結構的
POST請求)。一旦咱們從
POST中得到了文本和寬度的參數,咱們使用Python內建的
textwrap模塊來以指定的寬度裝飾文本,並將結果字符串寫回到HTTP響應中。
1.2.3 關於RequestHandler的更多知識
到目前爲止,咱們已經瞭解了
RequestHandler
對象的基礎:如何從一個傳入的HTTP請求中得到信息(使用
get_argument
和傳入到
get
和
post
的參數)以及寫HTTP響應(使用
write
方法)。除此以外,還有不少須要學習的,咱們將在接下來的章節中進行講解。同時,還有一些關於
RequestHandler
和Tornado如何使用它的只是須要記住。
1.2.3.1 HTTP方法
截止到目前討論的例子,每一個
RequestHandler
類都只定義了一個HTTP方法的行爲。可是,在同一個處理函數中定義多個方法是可能的,而且是有用的。把概念相關的功能綁定到同一個類是一個很好的方法。好比,你可能會編寫一個處理函數來處理數據庫中某個特定ID的對象,既使用
GET
方法,也使用
POST
方法。想象
GET
方法來返回這個部件的信息,而
POST
方法在數據庫中對這個ID的部件進行改變:
# matched with (r"/widget/(\d+)", WidgetHandler)
widget = {'1': 'aaaa', '2': 'bbbb', '3': 'cccc'}
def retrieve_from_db(widget_id):
return True if widget.get(widget_id) else {}
def save_to_db(wid):
widget.update(wid)
class WidgetHandler(tornado.web.RequestHandler):
def get(self, widget_id):
wid = retrieve_from_db(widget_id)
self.write(widget[widget_id] if wid else 'no')
def post(self, widget_id):
wid = retrieve_from_db(widget_id)
if not wid:
wid[widget_id] = self.get_argument('foo')
save_to_db(wid)
self.write(widget)
咱們到目前爲止只是用了
GET
和
POST
方法,但Tornado支持任何合法的HTTP請求(
GET
、
POST
、
PUT
、
DELETE
、
HEAD
、
OPTIONS
)。你能夠很是容易地定義上述任一種方法的行爲,只須要在
RequestHandler
類中使用同名的方法。下面是另外一個想象的例子,在這個例子中針對特定frob ID的
HEAD
請求只根據frob是否存在給出信息,而
GET
方法返回整個對象:
# matched with (r"/frob/(\d+)", FrobHandler)
class FrobHandler(tornado.web.RequestHandler):
def head(self, frob_id):
frob = retrieve_from_db(frob_id)
if frob:
self.set_status(200)
else:
self.set_status(404)
def get(self, frob_id):
frob = retrieve_from_db(frob_id)
if frob:
self.write(widget[frob_id])
curl -i -X HEAD http://192.168.201.130:8000/frob/1
curl http://192.168.201.130:8000/frob/2
1.2.3.2 HTTP狀態碼
從上面的代碼能夠看出,你可使用
RequestHandler類的
set_status()方法顯式地設置HTTP狀態碼。然而,你須要記住在某些狀況下,Tornado會自動地設置HTTP狀態碼。下面是一個經常使用狀況的:
404 Not Found
Tornado會在HTTP請求的路徑沒法匹配任何RequestHandler類相對應的模式時返回404(Not Found)響應碼。github
400 Bad Request
若是你調用了一個沒有默認值的get_argument函數,而且沒有發現給定名稱的參數,Tornado將自動返回一個400(Bad Request)響應碼。web
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)響應碼。瀏覽器
當上述任何一種錯誤發生時,Tornado將默認向客戶端發送一個包含狀態碼和錯誤信息的簡短片斷。若是你想使用本身的方法代替默認的錯誤響應,你能夠重寫
write_error方法在你的
RequestHandler類中。好比,代碼清單1-3是
hello.py示例添加了常規的錯誤消息的版本。
#coding=utf-8
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class IndexHandler(tornado.web.RequestHandler):
def get(self):
greeting = self.get_argument('greeting', 'Hello')
self.write(greeting + ', friendly user!')
def write_error(self, status_code, **kwargs):
self.write("%d \n" % status_code)
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
當咱們嘗試一個
POST
請求時,會獲得下面的響應。通常來講,咱們應該獲得Tornado默認的錯誤響應,但由於咱們覆寫了
write_error
,咱們會獲得不同的東西:
$ curl http://192.168.201.130:8000/ -d foo=bar
405
1.2.4 下一步
如今你已經明白了最基本的東西,咱們渴望你想了解更多。在接下來的章節,咱們將向你展現可以幫助你使用Tornado建立成熟的Web服務和應用的功能和技術。首先是:Tornado的模板系統。