看一份代碼先了解tornado的helloworld
#!/usr/bin/python3 # -*- coding: utf-8 -*- # @Time : 2020/4/26 0026 13:57 # @Author : Mr.jiang # @File : hello.py # @Software: PyCharm import tornado.web import tornado.ioloop import tornado.options import tornado.httpserver # 定義一個默認值為8888,類型為整型 tornado.options.define('port', default=8888, type=int) class Index(tornado.web.RequestHandler): def get(self): self.write("Hello Tornado") def main(): # 用於轉換命令行的參數來取代默認值 # python server.py --port=8002 tornado.options.parse_command_line() # tornado.options.parse_config_file("/etc/chnservices.conf") app = tornado.web.Application([ (r'/', Index) ]) # --------------------- # app.listen(8000) # 簡寫了下面的步驟 # http_server = tornado.httpserver.HTTPServer(app) # http_server.listen(8000) # 然后這個步驟 http_server.listen(8000) 又是下面兩句話的簡寫 # http_server.bind(8000) # http_server.start(1) httpserver = tornado.httpserver.HTTPServer(app) # 獲取命令傳的值 print('port', tornado.options.options.port) # 將服務器綁定到指定端口 httpserver.bind(tornado.options.options.port) # 可以開啟多進程,單進程&多進程 # httpserver.start(num_processes=1) 指定開多少個進程 # 默認僅僅開一個進程 # 如果num_processes為None或者<=0,則自動根據機器硬件的cpu核芯數創建同等數目的子進程; # num_processes>0,則創建num_processes個子進程。 httpserver.start(1) tornado.ioloop.IOLoop.current().start() # 不建議使用這種多進程的方式,而是手動開啟多個進程,並且綁定不同的端口。 """ 關於多進程 雖然tornado給我們提供了一次開啟多個進程的方法,但是由於: 每個子進程都會從父進程中復制一份IOLoop實例,如過在創建子進程前我們的代碼動了IOLoop實例,那么會影響到每一個子進程,勢必會干擾到子進程IOLoop的工作; 所有進程是由一個命令一次開啟的,也就無法做到在不停服務的情況下更新代碼; 所有進程共享同一個端口,想要分別單獨監控每一個進程就很困難。 """ if __name__ == '__main__': main()
並注意:
不建議使用這種多進程的方式,而是手動開啟多個進程,並且綁定不同的端口。
回想Django的部署方式
以Django為代表的python web應用部署時采用wsgi協議與服務器對接(被服務器托管),而這類服務器通常都是基於多線程的,也就是說每一個網絡請求服務器都會有一個對應的線程來用web應用(如Django)進行處理。
考慮兩類應用場景
用戶量大,高並發
如秒殺搶購、雙十一某寶購物、春節搶火車票
大量的HTTP持久連接
使用同一個TCP連接來發送和接收多個HTTP請求/應答,而不是為每一個新的請求/應答打開新的連接的方法。
對於HTTP 1.0,可以在請求的包頭(Header)中添加Connection: Keep-Alive。
對於HTTP 1.1,所有的連接默認都是持久連接。
對於這兩種場景,通常基於多線程的服務器很難應對。
C10K問題
對於前文提出的這種高並發問題,我們通常用C10K這一概念來描述。C10K—— Concurrently handling ten thousandconnections,即並發10000個連接。對於單台服務器而言,根本無法承擔,而采用多台服務器分布式又意味着高昂的成本。如何解決C10K問題?
Tornado
Tornado在設計之初就考慮到了性能因素,旨在解決C10K問題,這樣的設計使得其成為一個擁有非常高性能的解決方案(服務器與框架的集合體)。
1 關於Tornado
知識點
了解什么是Tornado框架
了解Tornado與Django的區別
1.1 Tornado是為何物
Tornado全稱Tornado Web Server,是一個用Python語言寫成的Web服務器兼Web應用框架,由FriendFeed公司在自己的網站FriendFeed中使用,被Facebook收購以后框架在2009年9月以開源軟件形式開放給大眾。
特點:
作為Web框架,是一個輕量級的Web框架,類似於另一個Python web框架Web.py,其擁有異步非阻塞IO的處理方式。
作為Web服務器,Tornado有較為出色的抗負載能力,官方用nginx反向代理的方式部署Tornado和其它Python web應用框架進行對比,結果最大瀏覽量超過第二名近40%。
性能: Tornado有着優異的性能。它試圖解決C10k問題,即處理大於或等於一萬的並發,下表是和一些其他Web框架與服務器的對比:
Tornado框架和服務器一起組成一個WSGI的全棧替代品。單獨在WSGI容器中使用tornado網絡框架或者tornaod http服務器,有一定的局限性,為了最大化的利用tornado的性能,推薦同時使用tornaod的網絡框架和HTTP服務器
1.2 Tornado與Django
Django
Django是走大而全的方向,注重的是高效開發,它最出名的是其全自動化的管理后台:只需要使用起ORM,做簡單的對象定義,它就能自動生成數據庫結構、以及全功能的管理后台。
Django提供的方便,也意味着Django內置的ORM跟框架內的其他模塊耦合程度高,應用程序必須使用Django內置的ORM,否則就不能享受到框架內提供的種種基於其ORM的便利。
session功能
后台管理
ORM
Tornado
Tornado走的是少而精的方向,注重的是性能優越,它最出名的是異步非阻塞的設計方式。
HTTP服務器
異步編程
WebSockets
2 初識Tornado
知識點
Tornado的安裝
了解Tornado的原理
掌握Tornado的基本寫法
掌握Tornado的基本模塊
tornado.web
tornado.ioloop
tornado.httpserver
tornado.options
2.1 安裝
自動安裝
查看自己當前的環境是否已安裝
pip list
安裝
pip install tornado
手動安裝
- 下載安裝包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 $ cd tornado-4.3 $ python setup.py build $ sudo python setup.py install
關於使用平台的說明
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應該運行在類Unix平台,在線上部署時為了最佳的性能和擴展性,僅推薦Linux和BSD(因為充分利用Linux的epoll工具和BSD的kqueue工具,是Tornado不依靠多進程/多線程而達到高性能的原因)。
對於Mac OS X,雖然也是衍生自BSD並且支持kqueue,但是其網絡性能通常不太給力,因此僅推薦用於開發。
對於Windows,Tornado官方沒有提供配置支持,但是也可以運行起來,不過僅推薦在開發中使用。
2.2 Hello Itcast
上代碼
新建文件hello.py,代碼如下:
# coding:utf-8 import tornado.web import tornado.ioloop class IndexHandler(tornado.web.RequestHandler): """主路由處理類""" def get(self): """對應http的get請求方式""" self.write("Hello Itcast!") if __name__ == "__main__": app = tornado.web.Application([ (r"/", IndexHandler), ]) app.listen(8000) tornado.ioloop.IOLoop.current().start()
執行如下命令,開啟tornado:
python hello.py
打開瀏覽器,輸入網址127.0.0.1:8000(或localhost:8000),查看效果:
代碼講解
1. tornado.web
tornado的基礎web框架模塊
RequestHandler
封裝了對應一個請求的所有信息和方法,write(響應信息)就是寫響應信息的一個方法;對應每一種http請求方式(get、post等),把對應的處理邏輯寫進同名的成員方法中(如對應get請求方式,就將對應的處理邏輯寫在get()方法中),當沒有對應請求方式的成員方法時,會返回“405: Method Not Allowed”錯誤。
我們將代碼中定義的get()方法更改為post()后,再用瀏覽器重新訪問(瀏覽器地址欄中輸入網址訪問的方式為get請求方式),演示如下:
Application
Tornado Web框架的核心應用類,是與服務器對接的接口,里面保存了路由信息表,其初始化接收的第一個參數就是一個路由信息映射元組的列表;其listen(端口)方法用來創建一個http服務器實例,並綁定到給定端口(注意:此時服務器並未開啟監聽)。
2. tornado.ioloop
tornado的核心io循環模塊,封裝了Linux的epoll和BSD的kqueue,tornado高性能的基石。 以Linux的epoll為例,其原理如下圖:
IOLoop.current()
返回當前線程的IOLoop實例。
IOLoop.start()
啟動IOLoop實例的I/O循環,同時服務器監聽被打開。
總結Tornado Web程序編寫思路
創建web應用實例對象,第一個初始化參數為路由映射列表。
定義實現路由映射列表中的handler類。
創建服務器實例,綁定服務器端口。
啟動當前線程的IOLoop。
2.3 httpserver
上一節我們說在tornado.web.Application.listen()(示例代碼中的app.listen(8000))的方法中,創建了一個http服務器示例並綁定到給定端口,我們能不能自己動手來實現這一部分功能呢?
現在我們修改上一示例代碼如下:
# coding:utf-8 import tornado.web import tornado.ioloop import tornado.httpserver # 新引入httpserver模塊 class IndexHandler(tornado.web.RequestHandler): """主路由處理類""" def get(self): """對應http的get請求方式""" self.write("Hello Itcast!") if __name__ == "__main__": app = tornado.web.Application([ (r"/", IndexHandler), ]) # ------------------------------ # 我們修改這個部分 # app.listen(8000) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(8000) # ------------------------------ tornado.ioloop.IOLoop.current().start()
在這一修改版本中,我們引入了tornado.httpserver模塊,顧名思義,它就是tornado的HTTP服務器實現。
我們創建了一個HTTP服務器實例http_server,因為服務器要服務於我們剛剛建立的web應用,將接收到的客戶端請求通過web應用中的路由映射表引導到對應的handler中,所以在構建http_server對象的時候需要傳出web應用對象app。http_server.listen(8000)將服務器綁定到8000端口。
實際上一版代碼中app.listen(8000)正是對這一過程的簡寫。
單進程與多進程
我們剛剛實現的都是單進程,可以通過命令來查看:
ps -ef | grep hello.py
我們也可以一次啟動多個進程,修改上面的代碼如下:
# coding:utf-8 import tornado.web import tornado.ioloop import tornado.httpserver class IndexHandler(tornado.web.RequestHandler): """主路由處理類""" def get(self): """對應http的get請求方式""" 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)方法是將服務器綁定到指定端口。
http_server.start(num_processes=1)方法指定開啟幾個進程,參數num_processes默認值為1,即默認僅開啟一個進程;如果num_processes為None或者<=0,則自動根據機器硬件的cpu核芯數創建同等數目的子進程;如果num_processes>0,則創建num_processes個子進程。
本例中,我們使用http_server.start(0),而我的虛擬機設定cpu核數為2,演示結果:
我們在前面寫的http_server.listen(8000)實際上就等同於:
http_server.bind(8000) http_server.start(1)
說明
1.關於app.listen()
app.listen()這個方法只能在單進程模式中使用。
對於app.listen()與手動創建HTTPServer實例
http_server = tornado.httpserver.HTTPServer(app) http_server.listen(8000)
這兩種方式,建議大家先使用后者即創建HTTPServer實例的方式,因為其對於理解tornado web應用工作流程的完整性有幫助,便於大家記憶tornado開發的模塊組成和程序結構;在熟練使用后,可以改為簡寫。
2.關於多進程
雖然tornado給我們提供了一次開啟多個進程的方法,但是由於:
每個子進程都會從父進程中復制一份IOLoop實例,如過在創建子進程前我們的代碼動了IOLoop實例,那么會影響到每一個子進程,勢必會干擾到子進程IOLoop的工作;
所有進程是由一個命令一次開啟的,也就無法做到在不停服務的情況下更新代碼;
所有進程共享同一個端口,想要分別單獨監控每一個進程就很困難。
不建議使用這種多進程的方式,而是手動開啟多個進程,並且綁定不同的端口。
2.4 options
在前面的示例中我們都是將服務端口的參數寫死在程序中,很不靈活。
tornado為我們提供了一個便捷的工具,tornado.options模塊——全局參數定義、存儲、轉換。
tornado.options.define()
用來定義options選項變量的方法,定義的變量可以在全局的tornado.options.options中獲取使用,傳入參數:
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())。
tornado.options.options
全局的options對象,所有定義的選項變量都會作為該對象的屬性。
tornado.options.parse_command_line()
轉換命令行參數,並將轉換后的值對應的設置到全局options對象相關屬性上。追加命令行參數的方式是--myoption=myvalue
新建opt.py,我們用代碼來看一下如何使用:
# coding:utf-8 import tornado.web import tornado.ioloop import tornado.httpserver import tornado.options # 新導入的options模塊 tornado.options.define("port", default=8000, type=int, help="run server on the given port.") # 定義服務器監聽端口選項 tornado.options.define("itcast", default=[], type=str, multiple=True, help="itcast subjects.") # 無意義,演示多值情況 class IndexHandler(tornado.web.RequestHandler): """主路由處理類""" def get(self): """對應http的get請求方式""" self.write("Hello Itcast!") if __name__ == "__main__": tornado.options.parse_command_line() print tornado.options.options.itcast # 輸出多值選項 app = tornado.web.Application([ (r"/", IndexHandler), ]) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(tornado.options.options.port) tornado.ioloop.IOLoop.current().start()
執行如下命令開啟程序:
python opt.py --port=9000 --itcast=python,c++,java,php,ios
tornado.options.parse_config_file(path)
從配置文件導入option,配置文件中的選項格式如下:
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 # 新導入的options模塊 tornado.options.define("port", default=8000, type=int, help="run server on the given port.") # 定義服務器監聽端口選項 tornado.options.define("itcast", default=[], type=str, multiple=True, help="itcast subjects.") # 無意義,演示多值情況 class IndexHandler(tornado.web.RequestHandler): """主路由處理類""" def get(self): """對應http的get請求方式""" self.write("Hello Itcast!") if __name__ == "__main__": tornado.options.parse_config_file("./config") # 僅僅修改了此處 print tornado.options.options.itcast # 輸出多值選項 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. 日志
當我們在代碼中調用parse_command_line()或者parse_config_file()的方法時,tornado會默認為我們配置標准logging模塊,即默認開啟了日志功能,並向標准輸出(屏幕)打印日志信息。
如果想關閉tornado默認的日志功能,可以在命令行中添加--logging=none 或者在代碼中執行如下操作:
from tornado.options import options, parse_command_line options.logging = None parse_command_line()
2. 配置文件
我們看到在使用prase_config_file()的時候,配置文件的書寫格式仍需要按照python的語法要求,其優勢是可以直接將配置文件的參數轉換設置到全局對象tornado.options.options中;然而,其不方便的地方在於需要在代碼中調用tornado.options.define()來定義選項,而且不支持字典類型,故而在實際應用中大都不使用這種方法。
在使用配置文件的時候,通常會新建一個python文件(如config.py),然后在里面直接定義python類型的變量(可以是字典類型);在需要配置文件參數的地方,將config.py作為模塊導入,並使用其中的變量參數。
如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的模塊中導入config,如下:
# conding:utf-8 import tornado.web import config if __name__ = "__main__": app = tornado.web.Application([], **config.settings) ...
2.5 練習
嘗試解釋清Tornado利用epoll機制實現支持高並發的原因。
能夠不參考課件熟練默寫出Tornado的基本代碼案例。
3 深入Tornado
知識點
Application設置
debug模式
路由設置擴展
RequestHandler的使用
輸入方法
輸出方法
可重寫接口
3.1 Application
settings
前面的學習中,我們在創建tornado.web.Application的對象時,傳入了第一個參數——路由映射列表。實際上Application類的構造函數還接收很多關於tornado web應用的配置參數,在后面的學習中我們用到的地方會為大家介紹。
我們先來學習一個參數:
debug,設置tornado是否工作在調試模式,默認為False即工作在生產模式。當設置debug=True 后,tornado會工作在調試/開發模式,在此種模式下,tornado為方便我們開發而提供了幾種特性:
自動重啟,tornado應用會監控我們的源代碼文件,當有改動保存后便會重啟程序,這可以減少我們手動重啟程序的次數。需要注意的是,一旦我們保存的更改有錯誤,自動重啟會導致程序報錯而退出,從而需要我們保存修正錯誤后手動啟動程序。這一特性也可單獨通過autoreload=True設置;
取消緩存編譯的模板,可以單獨通過compiled_template_cache=False來設置;
取消緩存靜態文件hash值,可以單獨通過static_hash_cache=False來設置;
提供追蹤信息,當RequestHandler或者其子類拋出一個異常而未被捕獲后,會生成一個包含追蹤信息的頁面,可以單獨通過serve_traceback=True來設置。
使用debug參數的方法:
import tornado.web
app = tornado.web.Application([], debug=True)
路由映射
先前我們在構建路由映射列表的時候,使用的是二元元組,如:
[(r"/", IndexHandler),]
對於這個映射列表中的路由,實際上還可以傳入多個信息,如:
[ (r"/", Indexhandler), (r"/cpp", ItcastHandler, {"subject":"c++"}), url(r"/python", ItcastHandler, {"subject":"python"}, name="python_url") ]
對於路由中的字典,會傳入到對應的RequestHandler的initialize()方法中:
from tornado.web import RequestHandler class ItcastHandler(RequestHandler): def initialize(self, subject): self.subject = subject def get(self): self.write(self.subject)
對於路由中的name字段,注意此時不能再使用元組,而應使用tornado.web.url來構建。name是給該路由起一個名字,可以通過調用RequestHandler.reverse_url(name)來獲取該名子對應的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 輸入
下面幾節主要講解tornado.web.RequestHandler。
回想一下,利用HTTP協議向服務器傳參有幾種途徑?
查詢字符串(query string),形如key1=value1&key2=value2;
請求體(body)中發送的數據,比如表單數據、json、xml;
提取uri的特定部分,如/blogs/2016/09/0001,可以在服務器端的路由中用正則表達式截取;
在http報文的頭(header)中增加自定義字段,如X-XSRFToken=itcast。
我們現在來看下tornado中為我們提供了哪些方法來獲取請求的信息。
1. 獲取查詢字符串參數
get_query_argument(name, default=_ARG_DEFAULT, strip=True)
從請求的查詢字符串中返回指定參數name的值,如果出現多個同名參數,則返回最后一個的值。
default為設值未傳name參數時返回的默認值,如若default也未設置,則會拋出tornado.web.MissingArgumentError異常。
strip表示是否過濾掉左右兩邊的空白字符,默認為過濾。
get_query_arguments(name, strip=True)
從請求的查詢字符串中返回指定參數name的值,注意返回的是list列表(即使對應name參數只有一個值)。若未找到name參數,則返回空列表[]。
strip同前,不再贅述。
2. 獲取請求體參數
get_body_argument(name, default=_ARG_DEFAULT, strip=True)
從請求體中返回指定參數name的值,如果出現多個同名參數,則返回最后一個的值。
default與strip同前,不再贅述。
get_body_arguments(name, strip=True)
從請求體中返回指定參數name的值,注意返回的是list列表(即使對應name參數只有一個值)。若未找到name參數,則返回空列表[]。
strip同前,不再贅述。
說明
對於請求體中的數據要求為字符串,且格式為表單編碼格式(與url中的請求字符串格式相同),即key1=value1&key2=value2,HTTP報文頭Header中的"Content-Type"為application/x-www-form-urlencoded 或 multipart/form-data。對於請求體數據為json或xml的,無法通過這兩個方法獲取。
3. 前兩類方法的整合
get_argument(name, default=_ARG_DEFAULT, strip=True)
從請求體和查詢字符串中返回指定參數name的值,如果出現多個同名參數,則返回最后一個的值。
default與strip同前,不再贅述。
get_arguments(name, strip=True)
從請求體和查詢字符串中返回指定參數name的值,注意返回的是list列表(即使對應name參數只有一個值)。若未找到name參數,則返回空列表[]。
strip同前,不再贅述。
說明
對於請求體中數據的要求同前。 這兩個方法最常用。
用代碼來看上述六中方法的使用:
# 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()
注意:以上方法返回的都是unicode字符串
思考
什么時候設置default,什么時候不設置default?
default的默認值_ARG_DEFAULT是什么?
什么時候使用strip,亦即什么時候要截斷空白字符,什么時候不需要?
4. 關於請求的其他信息
RequestHandler.request 對象存儲了關於請求的相關信息,具體屬性有:
method HTTP的請求方式,如GET或POST;
host 被請求的主機名;
uri 請求的完整資源標示,包括路徑和查詢字符串;
path 請求的路徑部分;
query 請求的查詢字符串部分;
version 使用的HTTP版本;
headers 請求的協議頭,是類字典型的對象,支持關鍵字索引的方式獲取特定協議頭信息,例如:request.headers["Content-Type"]
body 請求體數據;
remote_ip 客戶端的IP地址;
files 用戶上傳的文件,為字典類型,型如:
{ "form_filename1":[<tornado.httputil.HTTPFile>, <tornado.httputil.HTTPFile>], "form_filename2":[<tornado.httputil.HTTPFile>,], ... }
tornado.httputil.HTTPFile是接收到的文件對象,它有三個屬性:
filename 文件的實際名字,與form_filename1不同,字典中的鍵名代表的是表單對應項的名字;
body 文件的數據實體;
content_type 文件的類型。 這三個對象屬性可以像字典一樣支持關鍵字索引,如request.files["form_filename1"][0]["body"]。
我們來實現一個上傳文件並保存在服務器本地的小程序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中對於路由映射也支持正則提取uri,提取出來的參數會作為RequestHandler中對應請求方式的成員方法參數。若在正則表達式中定義了名字,則參數按名傳遞;若未定義名字,則參數按順序傳遞。提取出來的參數會作為對應請求方式的成員方法的參數。
# 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()
建議:提取多個值時最好用命名方式。