引言
以Django為代表的python web應(yīng)用部署時(shí)采用wsgi協(xié)議與服務(wù)器對(duì)接(被服務(wù)器托管)稻励,而這類服務(wù)器通常都是基于多線程的祈噪,也就是說每一個(gè)網(wǎng)絡(luò)請(qǐng)求服務(wù)器都會(huì)有一個(gè)對(duì)應(yīng)的線程來用web應(yīng)用(如Django)進(jìn)行處理。
考慮兩類應(yīng)用場景
用戶量大,高并發(fā)
如秒殺搶購、雙十一某寶購物靠瞎、春節(jié)搶火車票
大量的HTTP持久連接
使用同一個(gè)TCP連接來發(fā)送和接收多個(gè)HTTP請(qǐng)求/應(yīng)答,而不是為每一個(gè)新的請(qǐng)求/應(yīng)答打開新的連接的方法求妹。
對(duì)于HTTP 1.0乏盐,可以在請(qǐng)求的包頭(Header)中添加Connection: Keep-Alive。
對(duì)于HTTP 1.1扒最,所有的連接默認(rèn)都是持久連接丑勤。
對(duì)于這兩種場景,通嘲扇ぃ基于多線程的服務(wù)器很難應(yīng)對(duì)法竞。
C10K問題
對(duì)于前文提出的這種高并發(fā)問題,我們通常用C10K這一概念來描述强挫。C10K——Concurrently handlingten thousandconnections岔霸,即并發(fā)10000個(gè)連接。對(duì)于單臺(tái)服務(wù)器而言俯渤,根本無法承擔(dān)呆细,而采用多臺(tái)服務(wù)器分布式又意味著高昂的成本。如何解決C10K問題八匠?
Tornado
Tornado在設(shè)計(jì)之初就考慮到了性能因素絮爷,旨在解決C10K問題趴酣,這樣的設(shè)計(jì)使得其成為一個(gè)擁有非常高性能的解決方案(服務(wù)器與框架的集合體)。
1.1 Tornado是為何物
Tornado全稱Tornado Web Server坑夯,是一個(gè)用Python語言寫成的Web服務(wù)器兼Web應(yīng)用框架岖寞,由FriendFeed公司在自己的網(wǎng)站FriendFeed中使用,被Facebook收購以后框架在2009年9月以開源軟件形式開放給大眾柜蜈。
特點(diǎn):
作為Web框架仗谆,是一個(gè)輕量級(jí)的Web框架,類似于另一個(gè)Python web框架Web.py淑履,其擁有異步非阻塞IO的處理方式隶垮。
作為Web服務(wù)器,Tornado有較為出色的抗負(fù)載能力秘噪,官方用nginx反向代理的方式部署Tornado和其它Python web應(yīng)用框架進(jìn)行對(duì)比狸吞,結(jié)果最大瀏覽量超過第二名近40%。
性能:
Tornado有著優(yōu)異的性能指煎。它試圖解決C10k問題捷绒,即處理大于或等于一萬的并發(fā),下表是和一些其他Web框架與服務(wù)器的對(duì)比:
Tornado框架和服務(wù)器一起組成一個(gè)WSGI的全棧替代品贯要。單獨(dú)在WSGI容器中使用tornado網(wǎng)絡(luò)框架或者tornaod http服務(wù)器,有一定的局限性椭住,為了最大化的利用tornado的性能崇渗,推薦同時(shí)使用tornaod的網(wǎng)絡(luò)框架和HTTP服務(wù)器
1.2 Tornado與Django
Django
Django是走大而全的方向,注重的是高效開發(fā)京郑,它最出名的是其全自動(dòng)化的管理后臺(tái):只需要使用起ORM宅广,做簡單的對(duì)象定義,它就能自動(dòng)生成數(shù)據(jù)庫結(jié)構(gòu)些举、以及全功能的管理后臺(tái)跟狱。
Django提供的方便,也意味著Django內(nèi)置的ORM跟框架內(nèi)的其他模塊耦合程度高户魏,應(yīng)用程序必須使用Django內(nèi)置的ORM驶臊,否則就不能享受到框架內(nèi)提供的種種基于其ORM的便利。
session功能
后臺(tái)管理
ORM
Tornado
Tornado走的是少而精的方向叼丑,注重的是性能優(yōu)越关翎,它最出名的是異步非阻塞的設(shè)計(jì)方式。
HTTP服務(wù)器
異步編程
WebSockets
2.1 安裝
自動(dòng)安裝
1.查看自己當(dāng)前的環(huán)境是否已安裝
$ pip list
2.安裝
$ pip install tornado
手動(dòng)安裝
下載安裝包tornado-4.3.tar.gz(https://pypi.python.org/packages/source/t/tornado/tornado-4.3.tar.gz)
$ tar xvzf tornado-4.3.tar.gz$cdtornado-4.3$ python setup.py build$ sudo python setup.py install
關(guān)于使用平臺(tái)的說明
Tornado should run on any Unix-like platform, although for the best
performance and scalability only Linux (with epoll) and BSD (with
kqueue) are recommended for production deployment (even though Mac OS X
is derived from BSD and supports kqueue, its networking performance is
generally poor so it is recommended only for development use). Tornado
will also run on Windows, although this configuration is not officially
supported and is recommended only for development use.
Tornado應(yīng)該運(yùn)行在類Unix平臺(tái)鸠信,在線上部署時(shí)為了最佳的性能和擴(kuò)展性纵寝,僅推薦Linux和BSD(因?yàn)槌浞掷肔inux的epoll工具和BSD的kqueue工具,是Tornado不依靠多進(jìn)程/多線程而達(dá)到高性能的原因)星立。
對(duì)于Mac OS X爽茴,雖然也是衍生自BSD并且支持kqueue葬凳,但是其網(wǎng)絡(luò)性能通常不太給力,因此僅推薦用于開發(fā)室奏。
對(duì)于Windows火焰,Tornado官方?jīng)]有提供配置支持,但是也可以運(yùn)行起來窍奋,不過僅推薦在開發(fā)中使用荐健。
2.2 Hello Itcast
上代碼
新建文件hello.py,代碼如下:
# coding:utf-8importtornado.webimporttornado.ioloopclassIndexHandler(tornado.web.RequestHandler):"""主路由處理類"""defget(self):"""對(duì)應(yīng)http的get請(qǐng)求方式"""self.write("Hello Itcast!")if__name__ =="__main__":? ? app = tornado.web.Application([? ? ? ? (r"/", IndexHandler),? ? ])? ? app.listen(8000)? ? tornado.ioloop.IOLoop.current().start()
執(zhí)行如下命令琳袄,開啟tornado:
$ python hello.py
打開瀏覽器江场,輸入網(wǎng)址127.0.0.1:8000(或localhost:8000),查看效果:
代碼講解
1. tornado.web
tornado的基礎(chǔ)web框架模塊
RequestHandler
封裝了對(duì)應(yīng)一個(gè)請(qǐng)求的所有信息和方法窖逗,write(響應(yīng)信息)就是寫響應(yīng)信息的一個(gè)方法址否;對(duì)應(yīng)每一種http請(qǐng)求方式(get、post等)碎紊,把對(duì)應(yīng)的處理邏輯寫進(jìn)同名的成員方法中(如對(duì)應(yīng)get請(qǐng)求方式佑附,就將對(duì)應(yīng)的處理邏輯寫在get()方法中),當(dāng)沒有對(duì)應(yīng)請(qǐng)求方式的成員方法時(shí)仗考,會(huì)返回“405: Method Not Allowed”錯(cuò)誤音同。
? 我們將代碼中定義的get()方法更改為post()后,再用瀏覽器重新訪問(瀏覽器地址欄中輸入網(wǎng)址訪問的方式為get請(qǐng)求方式)秃嗜,演示如下:
# coding:utf-8
? import tornado.web
? import tornado.ioloop
? class IndexHandler(tornado.web.RequestHandler):
? ? ? """主路由處理類"""
? ? ? def post(self): ?# 我們修改了這里
? ? ? ? ? """對(duì)應(yīng)http的post請(qǐng)求方式"""
? ? ? ? ? self.write("Hello Itcast!")
? if __name__ == "__main__":
? ? ? app = tornado.web.Application([
? ? ? ? ? (r"/", IndexHandler),
? ? ? ])
? ? ? app.listen(8000)
? ? ? tornado.ioloop.IOLoop.current().start()
Application
Tornado Web框架的核心應(yīng)用類权均,是與服務(wù)器對(duì)接的接口,里面保存了路由信息表锅锨,其初始化接收的第一個(gè)參數(shù)就是一個(gè)路由信息映射元組的列表叽赊;其listen(端口)方法用來創(chuàng)建一個(gè)http服務(wù)器實(shí)例,并綁定到給定端口(注意:此時(shí)服務(wù)器并未開啟監(jiān)聽)必搞。
2. tornado.ioloop
tornado的核心io循環(huán)模塊必指,封裝了Linux的epoll和BSD的kqueue,tornado高性能的基石恕洲。
以Linux的epoll為例塔橡,其原理如下圖:
IOLoop.current()
? 返回當(dāng)前線程的IOLoop實(shí)例。
IOLoop.start()
? 啟動(dòng)IOLoop實(shí)例的I/O循環(huán),同時(shí)服務(wù)器監(jiān)聽被打開研侣。
總結(jié)Tornado Web程序編寫思路
創(chuàng)建web應(yīng)用實(shí)例對(duì)象谱邪,第一個(gè)初始化參數(shù)為路由映射列表。
定義實(shí)現(xiàn)路由映射列表中的handler類庶诡。
創(chuàng)建服務(wù)器實(shí)例惦银,綁定服務(wù)器端口。
啟動(dòng)當(dāng)前線程的IOLoop。
2.3 httpserver
上一節(jié)我們說在tornado.web.Application.listen()(示例代碼中的app.listen(8000))的方法中扯俱,創(chuàng)建了一個(gè)http服務(wù)器示例并綁定到給定端口书蚪,我們能不能自己動(dòng)手來實(shí)現(xiàn)這一部分功能呢?
現(xiàn)在我們修改上一示例代碼如下:
# coding:utf-8importtornado.webimporttornado.ioloopimporttornado.httpserver# 新引入httpserver模塊classIndexHandler(tornado.web.RequestHandler):"""主路由處理類"""defget(self):"""對(duì)應(yīng)http的get請(qǐng)求方式"""self.write("Hello Itcast!")if__name__ =="__main__":? ? app = tornado.web.Application([? ? ? ? (r"/", IndexHandler),? ? ])# ------------------------------# 我們修改這個(gè)部分# app.listen(8000)http_server = tornado.httpserver.HTTPServer(app)? ? http_server.listen(8000)# ------------------------------tornado.ioloop.IOLoop.current().start()
在這一修改版本中迅栅,我們引入了tornado.httpserver模塊殊校,顧名思義,它就是tornado的HTTP服務(wù)器實(shí)現(xiàn)读存。
我們創(chuàng)建了一個(gè)HTTP服務(wù)器實(shí)例http_server为流,因?yàn)榉?wù)器要服務(wù)于我們剛剛建立的web應(yīng)用,將接收到的客戶端請(qǐng)求通過web應(yīng)用中的路由映射表引導(dǎo)到對(duì)應(yīng)的handler中让簿,所以在構(gòu)建http_server對(duì)象的時(shí)候需要傳出web應(yīng)用對(duì)象app敬察。http_server.listen(8000)將服務(wù)器綁定到8000端口。
實(shí)際上一版代碼中app.listen(8000)正是對(duì)這一過程的簡寫尔当。
單進(jìn)程與多進(jìn)程
我們剛剛實(shí)現(xiàn)的都是單進(jìn)程莲祸,可以通過命令來查看:
$ ps -ef | grep hello.py
我們也可以一次啟動(dòng)多個(gè)進(jìn)程,修改上面的代碼如下:
# coding:utf-8
import tornado.web
import tornado.ioloop
import tornado.httpserver?
class IndexHandler(tornado.web.RequestHandler):
? ? """主路由處理類"""
? ? def get(self):
? ? ? ? """對(duì)應(yīng)http的get請(qǐng)求方式"""
? ? ? ? self.write("Hello Itcast!")
if __name__ == "__main__":
? ? app = tornado.web.Application([
? ? ? ? (r"/", IndexHandler),
? ? ])
? ? http_server = tornado.httpserver.HTTPServer(app)?
? ? # -----------修改----------------
? ? http_server.bind(8000)
? ? http_server.start(0)
? ? # ------------------------------
? ? tornado.ioloop.IOLoop.current().start()
http_server.bind(port)方法是將服務(wù)器綁定到指定端口椭迎。
http_server.start(num_processes=1)方法指定開啟幾個(gè)進(jìn)程锐帜,參數(shù)num_processes默認(rèn)值為1,即默認(rèn)僅開啟一個(gè)進(jìn)程畜号;如果num_processes為None或者<=0缴阎,則自動(dòng)根據(jù)機(jī)器硬件的cpu核芯數(shù)創(chuàng)建同等數(shù)目的子進(jìn)程;如果num_processes>0简软,則創(chuàng)建num_processes個(gè)子進(jìn)程药蜻。
本例中,我們使用http_server.start(0)替饿,而我的虛擬機(jī)設(shè)定cpu核數(shù)為2,演示結(jié)果:
我們?cè)谇懊鎸懙膆ttp_server.listen(8000)實(shí)際上就等同于:
http_server.bind(8000)http_server.start(1)
說明
1.關(guān)于app.listen()
app.listen()這個(gè)方法只能在單進(jìn)程模式中使用贸典。
對(duì)于app.listen()與手動(dòng)創(chuàng)建HTTPServer實(shí)例
http_server = tornado.httpserver.HTTPServer(app) http_server.listen(8000)
這兩種方式视卢,建議大家先使用后者即創(chuàng)建HTTPServer實(shí)例的方式,因?yàn)槠鋵?duì)于理解tornado web應(yīng)用工作流程的完整性有幫助廊驼,便于大家記憶tornado開發(fā)的模塊組成和程序結(jié)構(gòu)据过;在熟練使用后,可以改為簡寫妒挎。
2.關(guān)于多進(jìn)程
雖然tornado給我們提供了一次開啟多個(gè)進(jìn)程的方法绳锅,但是由于:
每個(gè)子進(jìn)程都會(huì)從父進(jìn)程中復(fù)制一份IOLoop實(shí)例,如過在創(chuàng)建子進(jìn)程前我們的代碼動(dòng)了IOLoop實(shí)例酝掩,那么會(huì)影響到每一個(gè)子進(jìn)程鳞芙,勢必會(huì)干擾到子進(jìn)程IOLoop的工作;
所有進(jìn)程是由一個(gè)命令一次開啟的,也就無法做到在不停服務(wù)的情況下更新代碼原朝;
所有進(jìn)程共享同一個(gè)端口驯嘱,想要分別單獨(dú)監(jiān)控每一個(gè)進(jìn)程就很困難。
不建議使用這種多進(jìn)程的方式喳坠,而是手動(dòng)開啟多個(gè)進(jìn)程鞠评,并且綁定不同的端口。
2.4 options
在前面的示例中我們都是將服務(wù)端口的參數(shù)寫死在程序中壕鹉,很不靈活剃幌。
tornado為我們提供了一個(gè)便捷的工具,tornado.options模塊——全局參數(shù)定義晾浴、存儲(chǔ)负乡、轉(zhuǎn)換。
tornado.options.define()
用來定義options選項(xiàng)變量的方法怠肋,定義的變量可以在全局的tornado.options.options中獲取使用敬鬓,傳入?yún)?shù):
name選項(xiàng)變量名,須保證全局唯一性笙各,否則會(huì)報(bào)“Option 'xxx' already defined in ...”的錯(cuò)誤钉答;
default選項(xiàng)變量的默認(rèn)值,如不傳默認(rèn)為None杈抢;
type選項(xiàng)變量的類型数尿,從命令行或配置文件導(dǎo)入?yún)?shù)的時(shí)候tornado會(huì)根據(jù)這個(gè)類型轉(zhuǎn)換輸入的值,轉(zhuǎn)換不成功時(shí)會(huì)報(bào)錯(cuò)惶楼,可以是str右蹦、float、int歼捐、datetime何陆、timedelta中的某個(gè),若未設(shè)置則根據(jù)default的值自動(dòng)推斷豹储,若default也未設(shè)置贷盲,那么不再進(jìn)行轉(zhuǎn)換。可以通過利用設(shè)置type類型字段來過濾不正確的輸入剥扣。
multiple選項(xiàng)變量的值是否可以為多個(gè)巩剖,布爾類型,默認(rèn)值為False钠怯,如果multiple為True佳魔,那么設(shè)置選項(xiàng)變量時(shí)值與值之間用英文逗號(hào)分隔,而選項(xiàng)變量則是一個(gè)list列表(若默認(rèn)值和輸入均未設(shè)置晦炊,則為空列表[])鞠鲜。
help選項(xiàng)變量的幫助提示信息宁脊,在命令行啟動(dòng)tornado時(shí),通過加入命令行參數(shù) --help 可以查看所有選項(xiàng)變量的信息(注意镊尺,代碼中需要加入tornado.options.parse_command_line())朦佩。
tornado.options.options
全局的options對(duì)象,所有定義的選項(xiàng)變量都會(huì)作為該對(duì)象的屬性庐氮。
tornado.options.parse_command_line()
轉(zhuǎn)換命令行參數(shù)语稠,并將轉(zhuǎn)換后的值對(duì)應(yīng)的設(shè)置到全局options對(duì)象相關(guān)屬性上。追加命令行參數(shù)的方式是--myoption=myvalue
新建opt.py弄砍,我們用代碼來看一下如何使用:
# coding:utf-8
import tornado.web
import tornado.ioloop
import tornado.httpserver
import tornado.options # 新導(dǎo)入的options模塊
tornado.options.define("port", default=8000, type=int, help="run server on the given port.") # 定義服務(wù)器監(jiān)聽端口選項(xiàng)
tornado.options.define("itcast", default=[], type=str, multiple=True, help="itcast subjects.") # 無意義仙畦,演示多值情況
class IndexHandler(tornado.web.RequestHandler):
? ? """主路由處理類"""
? ? def get(self):
? ? ? ? """對(duì)應(yīng)http的get請(qǐng)求方式"""
? ? ? ? self.write("Hello Itcast!")
if __name__ == "__main__":
? ? tornado.options.parse_command_line()
? ? print tornado.options.options.itcast # 輸出多值選項(xiàng)
? ? app = tornado.web.Application([
? ? ? ? (r"/", IndexHandler),
? ? ])
? ? http_server = tornado.httpserver.HTTPServer(app)
? ? http_server.listen(tornado.options.options.port)
? ? tornado.ioloop.IOLoop.current().start()
執(zhí)行如下命令開啟程序:
$ python opt.py --port=9000--itcast=python,c++,java,php,ios
效果如下:
tornado.options.parse_config_file(path)
從配置文件導(dǎo)入option,配置文件中的選項(xiàng)格式如下:
myoption = "myvalue"
myotheroption = "myothervalue"
我們用代碼來看一下如何使用音婶,新建配置文件config慨畸,注意字符串和列表按照python的語法格式:
port = 8000
itcast = ["python","c++","java","php","ios"]
修改opt.py文件:
# coding:utf-8
import tornado.web
import tornado.ioloop
import tornado.httpserver
import tornado.options # 新導(dǎo)入的options模塊
tornado.options.define("port", default=8000, type=int, help="run server on the given port.") # 定義服務(wù)器監(jiān)聽端口選項(xiàng)
tornado.options.define("itcast", default=[], type=str, multiple=True, help="itcast subjects.") # 無意義,演示多值情況
class IndexHandler(tornado.web.RequestHandler):
? ? """主路由處理類"""
? ? def get(self):
? ? ? ? """對(duì)應(yīng)http的get請(qǐng)求方式"""
? ? ? ? self.write("Hello Itcast!")
if __name__ == "__main__":
? ? tornado.options.parse_config_file("./config") # 僅僅修改了此處
? ? print tornado.options.options.itcast # 輸出多值選項(xiàng)
? ? app = tornado.web.Application([
? ? ? ? (r"/", IndexHandler),
? ? ])
? ? http_server = tornado.httpserver.HTTPServer(app)
? ? http_server.listen(tornado.options.options.port)
? ? tornado.ioloop.IOLoop.current().start()
說明
1. 日志
當(dāng)我們?cè)诖a中調(diào)用parse_command_line()或者parse_config_file()的方法時(shí)衣式,tornado會(huì)默認(rèn)為我們配置標(biāo)準(zhǔn)logging模塊寸士,即默認(rèn)開啟了日志功能,并向標(biāo)準(zhǔn)輸出(屏幕)打印日志信息碴卧。
如果想關(guān)閉tornado默認(rèn)的日志功能弱卡,可以在命令行中添加--logging=none 或者在代碼中執(zhí)行如下操作:
from tornado.options import options, parse_command_line
options.logging = None
parse_command_line()
2. 配置文件
我們看到在使用prase_config_file()的時(shí)候,配置文件的書寫格式仍需要按照python的語法要求住册,其優(yōu)勢是可以直接將配置文件的參數(shù)轉(zhuǎn)換設(shè)置到全局對(duì)象tornado.options.options中婶博;然而,其不方便的地方在于需要在代碼中調(diào)用tornado.options.define()來定義選項(xiàng)荧飞,而且不支持字典類型凡人,故而在實(shí)際應(yīng)用中大都不使用這種方法。
在使用配置文件的時(shí)候叹阔,通常會(huì)新建一個(gè)python文件(如config.py)挠轴,然后在里面直接定義python類型的變量(可以是字典類型);在需要配置文件參數(shù)的地方耳幢,將config.py作為模塊導(dǎo)入忠荞,并使用其中的變量參數(shù)。
如config.py文件:
# conding:utf-8
# Redis配置
redis_options = {
? ? 'redis_host':'127.0.0.1',
? ? 'redis_port':6379,
? ? 'redis_pass':'',
}
# Tornado app配置
settings = {
? ? 'template_path': os.path.join(os.path.dirname(__file__), 'templates'),
? ? 'static_path': os.path.join(os.path.dirname(__file__), 'statics'),
? ? 'cookie_secret':'0Q1AKOKTQHqaa+N80XhYW7KCGskOUE2snCW06UIxXgI=',
? ? 'xsrf_cookies':False,
? ? 'login_url':'/login',
? ? 'debug':True,
}
# 日志
log_path = os.path.join(os.path.dirname(__file__), 'logs/log')
使用config.py的模塊中導(dǎo)入config帅掘,如下:
# conding:utf-8
import tornado.web
import config
if __name__ = "__main__":
? ? app = tornado.web.Application([], **config.settings)
3.1 Application
settings
前面的學(xué)習(xí)中,我們?cè)趧?chuàng)建tornado.web.Application的對(duì)象時(shí)堂油,傳入了第一個(gè)參數(shù)——路由映射列表修档。實(shí)際上Application類的構(gòu)造函數(shù)還接收很多關(guān)于tornado web應(yīng)用的配置參數(shù),在后面的學(xué)習(xí)中我們用到的地方會(huì)為大家介紹府框。
我們先來學(xué)習(xí)一個(gè)參數(shù):
debug吱窝,設(shè)置tornado是否工作在調(diào)試模式,默認(rèn)為False即工作在生產(chǎn)模式。當(dāng)設(shè)置debug=True 后院峡,tornado會(huì)工作在調(diào)試/開發(fā)模式兴使,在此種模式下,tornado為方便我們開發(fā)而提供了幾種特性:
? ? 自動(dòng)重啟照激,tornado應(yīng)用會(huì)監(jiān)控我們的源代碼文件发魄,當(dāng)有改動(dòng)保存后便會(huì)重啟程序,這可以減少我們手動(dòng)重啟程序的次數(shù)俩垃。需要注意的是励幼,一旦我們保存的更改有錯(cuò)誤,自動(dòng)重啟會(huì)導(dǎo)致程序報(bào)錯(cuò)而退出口柳,從而需要我們保存修正錯(cuò)誤后手動(dòng)啟動(dòng)程序苹粟。這一特性也可單獨(dú)通過autoreload=True設(shè)置;
? ? 取消緩存編譯的模板跃闹,可以單獨(dú)通過compiled_template_cache=False來設(shè)置嵌削;
? ? 取消緩存靜態(tài)文件hash值,可以單獨(dú)通過static_hash_cache=False來設(shè)置望艺;
? ? 提供追蹤信息苛秕,當(dāng)RequestHandler或者其子類拋出一個(gè)異常而未被捕獲后,會(huì)生成一個(gè)包含追蹤信息的頁面荣茫,可以單獨(dú)通過serve_traceback=True來設(shè)置想帅。
使用debug參數(shù)的方法:
import tornado.web
app = tornado.web.Application([], debug=True)
路由映射
先前我們?cè)跇?gòu)建路由映射列表的時(shí)候,使用的是二元元組啡莉,如:
[(r"/", IndexHandler),]
對(duì)于這個(gè)映射列表中的路由港准,實(shí)際上還可以傳入多個(gè)信息,如:
[
? ? (r"/", Indexhandler),
? ? (r"/cpp", ItcastHandler, {"subject":"c++"}),
? ? url(r"/python", ItcastHandler, {"subject":"python"}, name="python_url")
]
對(duì)于路由中的字典咧欣,會(huì)傳入到對(duì)應(yīng)的RequestHandler的initialize()方法中:
from tornado.web import RequestHandler
class ItcastHandler(RequestHandler):
? ? def initialize(self, subject):
? ? ? ? self.subject = subject
? ? def get(self):
? ? ? ? self.write(self.subject)
對(duì)于路由中的name字段浅缸,注意此時(shí)不能再使用元組,而應(yīng)使用tornado.web.url來構(gòu)建魄咕。name是給該路由起一個(gè)名字衩椒,可以通過調(diào)用RequestHandler.reverse_url(name)來獲取該名子對(duì)應(yīng)的url。
# coding:utf-8
import tornado.web
import tornado.ioloop
import tornado.httpserver
import tornado.options
from tornado.options import options, define
from tornado.web import url, RequestHandler
define("port", default=8000, type=int, help="run server on the given port.")
class IndexHandler(RequestHandler):
? ? def get(self):
? ? ? ? python_url = self.reverse_url("python_url")
? ? ? ? self.write('<a href="%s">itcast</a>' %
? ? ? ? ? ? ? ? ? ?python_url)
class ItcastHandler(RequestHandler):
? ? def initialize(self, subject):
? ? ? ? self.subject = subject
? ? def get(self):
? ? ? ? self.write(self.subject)
if __name__ == "__main__":
? ? tornado.options.parse_command_line()
? ? app = tornado.web.Application([
? ? ? ? ? ? (r"/", Indexhandler),
? ? ? ? ? ? (r"/cpp", ItcastHandler, {"subject":"c++"}),
? ? ? ? ? ? url(r"/python", ItcastHandler, {"subject":"python"}, name="python_url")
? ? ? ? ],
? ? ? ? debug = True)
? ? http_server = tornado.httpserver.HTTPServer(app)
? ? http_server.listen(options.port)
? ? tornado.ioloop.IOLoop.current().start()
3.2 輸入
下面幾節(jié)主要講解tornado.web.RequestHandler哮兰。
回想一下毛萌,利用HTTP協(xié)議向服務(wù)器傳參有幾種途徑?
? ? 查詢字符串(query string)喝滞,形如key1=value1&key2=value2阁将;
? ? 請(qǐng)求體(body)中發(fā)送的數(shù)據(jù),比如表單數(shù)據(jù)右遭、json做盅、xml缤削;
? ? 提取uri的特定部分,如/blogs/2016/09/0001吹榴,可以在服務(wù)器端的路由中用正則表達(dá)式截韧じ摇;
? ? 在http報(bào)文的頭(header)中增加自定義字段图筹,如X-XSRFToken=itcast帅刀。
我們現(xiàn)在來看下tornado中為我們提供了哪些方法來獲取請(qǐng)求的信息。
1. 獲取查詢字符串參數(shù)
get_query_argument(name, default=_ARG_DEFAULT, strip=True)
從請(qǐng)求的查詢字符串中返回指定參數(shù)name的值婿斥,如果出現(xiàn)多個(gè)同名參數(shù)劝篷,則返回最后一個(gè)的值。
default為設(shè)值未傳name參數(shù)時(shí)返回的默認(rèn)值民宿,如若default也未設(shè)置娇妓,則會(huì)拋出tornado.web.MissingArgumentError異常。
strip表示是否過濾掉左右兩邊的空白字符活鹰,默認(rèn)為過濾哈恰。
get_query_arguments(name, strip=True)
從請(qǐng)求的查詢字符串中返回指定參數(shù)name的值,注意返回的是list列表(即使對(duì)應(yīng)name參數(shù)只有一個(gè)值)志群。若未找到name參數(shù)着绷,則返回空列表[]。
strip同前锌云,不再贅述荠医。
2. 獲取請(qǐng)求體參數(shù)
get_body_argument(name, default=_ARG_DEFAULT, strip=True)
從請(qǐng)求體中返回指定參數(shù)name的值,如果出現(xiàn)多個(gè)同名參數(shù)桑涎,則返回最后一個(gè)的值彬向。
default與strip同前,不再贅述攻冷。
get_body_arguments(name, strip=True)
從請(qǐng)求體中返回指定參數(shù)name的值娃胆,注意返回的是list列表(即使對(duì)應(yīng)name參數(shù)只有一個(gè)值)。若未找到name參數(shù)等曼,則返回空列表[]里烦。
strip同前,不再贅述禁谦。
說明
對(duì)于請(qǐng)求體中的數(shù)據(jù)要求為字符串胁黑,且格式為表單編碼格式(與url中的請(qǐng)求字符串格式相同),即key1=value1&key2=value2州泊,HTTP報(bào)文頭Header中的"Content-Type"為application/x-www-form-urlencoded 或 multipart/form-data丧蘸。對(duì)于請(qǐng)求體數(shù)據(jù)為json或xml的,無法通過這兩個(gè)方法獲取拥诡。
3. 前兩類方法的整合
get_argument(name, default=_ARG_DEFAULT, strip=True)
從請(qǐng)求體和查詢字符串中返回指定參數(shù)name的值触趴,如果出現(xiàn)多個(gè)同名參數(shù),則返回最后一個(gè)的值渴肉。
default與strip同前冗懦,不再贅述。
get_arguments(name, strip=True)
從請(qǐng)求體和查詢字符串中返回指定參數(shù)name的值仇祭,注意返回的是list列表(即使對(duì)應(yīng)name參數(shù)只有一個(gè)值)披蕉。若未找到name參數(shù),則返回空列表[]乌奇。
strip同前没讲,不再贅述。
說明
對(duì)于請(qǐng)求體中數(shù)據(jù)的要求同前礁苗。 這兩個(gè)方法最常用爬凑。
用代碼來看上述六中方法的使用:
# coding:utf-8
import tornado.web
import tornado.ioloop
import tornado.httpserver
import tornado.options
from tornado.options import options, define
from tornado.web import RequestHandler, MissingArgumentError
define("port", default=8000, type=int, help="run server on the given port.")
class IndexHandler(RequestHandler):
? ? def post(self):
? ? ? ? query_arg = self.get_query_argument("a")
? ? ? ? query_args = self.get_query_arguments("a")
? ? ? ? body_arg = self.get_body_argument("a")
? ? ? ? body_args = self.get_body_arguments("a", strip=False)
? ? ? ? arg = self.get_argument("a")
? ? ? ? args = self.get_arguments("a")
? ? ? ? default_arg = self.get_argument("b", "itcast")
? ? ? ? default_args = self.get_arguments("b")
? ? ? ? try:
? ? ? ? ? ? missing_arg = self.get_argument("c")
? ? ? ? except MissingArgumentError as e:
? ? ? ? ? ? missing_arg = "We catched the MissingArgumentError!"
? ? ? ? ? ? print e
? ? ? ? missing_args = self.get_arguments("c")
? ? ? ? rep = "query_arg:%s<br/>" % query_arg
? ? ? ? rep += "query_args:%s<br/>" % query_args?
? ? ? ? rep += "body_arg:%s<br/>" ?% body_arg
? ? ? ? rep += "body_args:%s<br/>" % body_args
? ? ? ? rep += "arg:%s<br/>" ?% arg
? ? ? ? rep += "args:%s<br/>" % args?
? ? ? ? rep += "default_arg:%s<br/>" % default_arg?
? ? ? ? rep += "default_args:%s<br/>" % default_args?
? ? ? ? rep += "missing_arg:%s<br/>" % missing_arg
? ? ? ? rep += "missing_args:%s<br/>" % missing_args
? ? ? ? self.write(rep)
if __name__ == "__main__":
? ? tornado.options.parse_command_line()
? ? app = tornado.web.Application([
? ? ? ? (r"/", IndexHandler),
? ? ])
? ? http_server = tornado.httpserver.HTTPServer(app)
? ? http_server.listen(options.port)
? ? tornado.ioloop.IOLoop.current().start()
4. 關(guān)于請(qǐng)求的其他信息
RequestHandler.request 對(duì)象存儲(chǔ)了關(guān)于請(qǐng)求的相關(guān)信息,具體屬性有:
method HTTP的請(qǐng)求方式试伙,如GET或POST;
host 被請(qǐng)求的主機(jī)名嘁信;
uri 請(qǐng)求的完整資源標(biāo)示,包括路徑和查詢字符串疏叨;
path 請(qǐng)求的路徑部分潘靖;
query 請(qǐng)求的查詢字符串部分;
version 使用的HTTP版本蚤蔓;
headers請(qǐng)求的協(xié)議頭卦溢,是類字典型的對(duì)象,支持關(guān)鍵字索引的方式獲取特定協(xié)議頭信息秀又,例如:request.headers["Content-Type"]
body請(qǐng)求體數(shù)據(jù)单寂;
remote_ip客戶端的IP地址;
files用戶上傳的文件涮坐,為字典類型凄贩,型如:
{"form_filename1":[, ],"form_filename2":[,],? ... }
tornado.httputil.HTTPFile是接收到的文件對(duì)象,它有三個(gè)屬性:
filename 文件的實(shí)際名字袱讹,與form_filename1不同疲扎,字典中的鍵名代表的是表單對(duì)應(yīng)項(xiàng)的名字;
body 文件的數(shù)據(jù)實(shí)體捷雕;
content_type 文件的類型椒丧。
這三個(gè)對(duì)象屬性可以像字典一樣支持關(guān)鍵字索引,如request.files["form_filename1"][0]["body"]救巷。
我們來實(shí)現(xiàn)一個(gè)上傳文件并保存在服務(wù)器本地的小程序upload.py:
# coding:utf-8
import tornado.web
import tornado.ioloop
import tornado.httpserver
import tornado.options
from tornado.options import options, define
from tornado.web import RequestHandler
define("port", default=8000, type=int, help="run server on the given port.")
class IndexHandler(RequestHandler):
? ? def get(self):
? ? ? ? self.write("hello itcast.")
class UploadHandler(RequestHandler):?
? ? def post(self):
? ? ? ? files = self.request.files
? ? ? ? img_files = files.get('img')
? ? ? ? if img_files:
? ? ? ? ? ? img_file = img_files[0]["body"]
? ? ? ? ? ? file = open("./itcast", 'w+')
? ? ? ? ? ? file.write(img_file)
? ? ? ? ? ? file.close()
? ? ? ? self.write("OK")
if __name__ == "__main__":
? ? tornado.options.parse_command_line()
? ? app = tornado.web.Application([
? ? ? ? (r"/", IndexHandler),
? ? ? ? (r"/upload", UploadHandler),
? ? ])
? ? http_server = tornado.httpserver.HTTPServer(app)
? ? http_server.listen(options.port)
? ? tornado.ioloop.IOLoop.current().start()
5. 正則提取uri
tornado中對(duì)于路由映射也支持正則提取uri壶熏,提取出來的參數(shù)會(huì)作為RequestHandler中對(duì)應(yīng)請(qǐng)求方式的成員方法參數(shù)。若在正則表達(dá)式中定義了名字浦译,則參數(shù)按名傳遞棒假;若未定義名字溯职,則參數(shù)按順序傳遞。提取出來的參數(shù)會(huì)作為對(duì)應(yīng)請(qǐng)求方式的成員方法的參數(shù)帽哑。
# coding:utf-8
import tornado.web
import tornado.ioloop
import tornado.httpserver
import tornado.options
from tornado.options import options, define
from tornado.web import RequestHandler
define("port", default=8000, type=int, help="run server on the given port.")
class IndexHandler(RequestHandler):
? ? def get(self):
? ? ? ? self.write("hello itcast.")
class SubjectCityHandler(RequestHandler):
? ? def get(self, subject, city):
? ? ? ? self.write(("Subject: %s<br/>City: %s" % (subject, city)))
class SubjectDateHandler(RequestHandler):
? ? def get(self, date, subject):
? ? ? ? self.write(("Date: %s<br/>Subject: %s" % (date, subject)))
if __name__ == "__main__":
? ? tornado.options.parse_command_line()
? ? app = tornado.web.Application([
? ? ? ? (r"/", IndexHandler),
? ? ? ? (r"/sub-city/(.+)/([a-z]+)", SubjectCityHandler), # 無名方式
? ? ? ? (r"/sub-date/(?P<subject>.+)/(?P<date>\d+)", SubjectDateHandler), # 命名方式
? ? ])
? ? http_server = tornado.httpserver.HTTPServer(app)
? ? http_server.listen(options.port)
? ? tornado.ioloop.IOLoop.current().start()
建議:提取多個(gè)值時(shí)最好用命名方式谜酒。