3、Tornado進階html
3.1 Applicationpython
settingsweb
debug,設置tornado是否工做在調試模式,默認爲False即工做在生產模式。當設置debug=True 後,tornado會工做在調試/開發模式,在此種模式下,tornado爲方便咱們開發而提供了幾種特性:正則表達式
自動重啓,tornado應用會監控咱們的源代碼文件,當有改動保存後便會重啓程序,這能夠減小咱們手動重啓程序的次數。須要注意的是,一旦咱們保存的更改有錯誤,自動重啓會致使程序報錯而退出,從而須要咱們保存修正錯誤後手動啓動程序。這一特性也可單獨經過autoreload=True設置;json
取消緩存編譯的模板,能夠單獨經過compiled_template_cache=False來設置;小程序
取消緩存靜態文件hash值,能夠單獨經過static_hash_cache=False來設置;瀏覽器
提供追蹤信息,當RequestHandler或者其子類拋出一個異常而未被捕獲後,會生成一個包含追蹤信息的頁面,能夠單獨經過serve_traceback=True來設置。緩存
使用debug參數的方法:服務器
import tornado.web app = tornado.web.Application([], debug=True)
路由映射app
先前咱們在構建路由映射列表的時候,使用的是二元元組,如:
[(r'/index', IndexHandle)]
對於這個映射列表中的路由,實際上還能夠傳入多個信息,如:
[ (r"/index", IndexHandle), url(r"/test", TestHandle, {"subject":"python"}, name="python_url") ]
對於路由中的字典,會傳入到對應的RequestHandler的initialize()方法中:
from tornado.web import RequestHandler class TestHandler(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。
import tornado.web import tornado.httpserver import tornado.ioloop 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 IndexHandle(RequestHandler): """主路由處理類""" def get(self): python_url = self.reverse_url("python_url") self.write('<a href="%s">index</a>' % python_url) class TestHandle(RequestHandler): """主路由處理類""" def initialize(self, subject): self.subject = subject def get(self): """對應http的get請求""" self.write(self.subject) if __name__ == '__main__': tornado.options.parse_command_line() # 輸出多值選項 # print(tornado.options.options.test) app = tornado.web.Application([ (r"/index", IndexHandle), url(r"/python", TestHandle, {"subject":"python"}, name="python_url") ], debug=True ) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(tornado.options.options.port) tornado.ioloop.IOLoop.current().start()
3.2 輸入
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同前。
從請求體中返回指定參數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同前。
說明
對於請求體中數據的要求同前。 這兩個方法最經常使用。
用代碼來看上述六中方法的使用:
import tornado.web import tornado.httpserver import tornado.ioloop 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 IndexHandle(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") # self.write(query_arg) self.write(str(query_args)) if __name__ == '__main__': tornado.options.parse_command_line() # 輸出多值選項 # print(tornado.options.options.test) app = tornado.web.Application([ (r"/index", IndexHandle) ], debug=True ) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(tornado.options.options.port) tornado.ioloop.IOLoop.current().start()
4. 關於請求的其餘信息
RequestHandler.request 對象存儲了關於請求的相關信息,具體屬性有:
method HTTP的請求方式,如GET或POST;
host 被請求的主機名;
uri 請求的完整資源標示,包括路徑和查詢字符串;
path 請求的路徑部分;
query 請求的查詢字符串部分;
version 使用的HTTP版本;
headers 請求的協議頭,是類字典型的對象,支持關鍵字索引的方式獲取特定協議頭信息,例如:request.headers["Content-Type"]
body 請求體數據; (獲取json數據:request.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:
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 tornado.") 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("./test", '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中對應請求方式的成員方法參數。若在正則表達式中定義了名字,則參數按名傳遞;若未定義名字,則參數按順序傳遞。提取出來的參數會做爲對應請求方式的成員方法的參數。
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 tornado.") 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()
3.3 輸出
1. write(chunk)
將chunk數據寫到輸出緩衝區。如咱們在以前的示例代碼中寫的:
class IndexHandle(RequestHandler): """主路由處理類""" def get(self): self.write("test1")
在同一個處理方法中屢次使用write方法
class IndexHandle(RequestHandler): def get(self): self.write("test1") self.write("test2") self.write("test3")
write方法是寫到緩衝區的,咱們能夠像寫文件同樣屢次使用write方法不斷追加響應內容,最終全部寫到緩衝區的內容一塊兒做爲本次請求的響應輸出。
利用write方法寫json數據
class IndexHandle(RequestHandler): """主路由處理類""" def get(self): data = { "name":"zhangsan", "age":24, "gender":1, } data_str = json.dumps(data) self.write(data_str)
實際上,咱們能夠不用本身手動去作json序列化,當write方法檢測到咱們傳入的chunk參數是字典類型後,會自動幫咱們轉換爲json字符串。
class IndexHandle(RequestHandler): def get(self): data = { "name":"zhangsan", "age":24, "gender":1, } # data_str = json.dumps(data) self.write(data)
兩種方式有什麼差別?
對比一下兩種方式的響應頭header中Content-Type字段,本身手動序列化時爲Content-Type:text/html; charset=UTF-8,而採用write方法時爲Content-Type:application/json; charset=UTF-8。
write方法除了幫咱們將字典轉換爲json字符串以外,還幫咱們將Content-Type設置爲application/json; charset=UTF-8。
2. set_header(name, value)
利用set_header(name, value)方法,能夠手動設置一個名爲name、值爲value的響應頭header字段。
用set_header方法來完成上面write所作的工做。
class IndexHandle(RequestHandler): def get(self): data = { "name":"zhangsan", "age":24, "gender":1, } data_str = json.dumps(data) self.write(data) self.set_header("Content-Type", "application/json; charset=UTF-8")
3. set_default_headers()
該方法會在進入HTTP處理方法前先被調用,能夠重寫此方法來預先設置默認的headers。注意:在HTTP處理方法中使用set_header()方法會覆蓋掉在set_default_headers()方法中設置的同名header。
import tornado.web import tornado.httpserver import tornado.ioloop import tornado.options from tornado.options import options, define from tornado.web import url, RequestHandler import json define("port", default=8000, type=int, help="run server on the given port.") class IndexHandle(RequestHandler): def set_default_headers(self): print("執行了set_default_headers()") # 設置get與post方式的默認響應體格式爲json self.set_header("Content-Type", "application/json; charset=UTF-8") # 設置一個名爲itcast、值爲python的header self.set_header("book", "python") def get(self): data = { "name":"zhangsan", "age":24, "gender":1, } data_str = json.dumps(data) self.write(data) self.set_header("name", "test") def post(self): print("執行了post()") data = { "name":"zhangsan", "age":24, "gender":1, } data_json = json.dumps(data) self.write(data_json) if __name__ == '__main__': tornado.options.parse_command_line() # 輸出多值選項 # print(tornado.options.options.test) app = tornado.web.Application([ (r"/index", IndexHandle) ], debug=True ) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(tornado.options.options.port) tornado.ioloop.IOLoop.current().start()
4. set_status(status_code, reason=None)
爲響應設置狀態碼。
參數說明:
status_code int類型,狀態碼,若reason爲None,則狀態碼必須爲下表中的。
reason string類型,描述狀態碼的詞組,若爲None,則會被自動填充爲下表中的內容。
Code | Enum Name | Details |
---|---|---|
100 | CONTINUE | HTTP/1.1 RFC 7231, Section 6.2.1 |
101 | SWITCHING_PROTOCOLS | HTTP/1.1 RFC 7231, Section 6.2.2 |
102 | PROCESSING | WebDAV RFC 2518, Section 10.1 |
200 | OK | HTTP/1.1 RFC 7231, Section 6.3.1 |
201 | CREATED | HTTP/1.1 RFC 7231, Section 6.3.2 |
202 | ACCEPTED | HTTP/1.1 RFC 7231, Section 6.3.3 |
203 | NON_AUTHORITATIVE_INFORMATION | HTTP/1.1 RFC 7231, Section 6.3.4 |
204 | NO_CONTENT | HTTP/1.1 RFC 7231, Section 6.3.5 |
205 | RESET_CONTENT | HTTP/1.1 RFC 7231, Section 6.3.6 |
206 | PARTIAL_CONTENT | HTTP/1.1 RFC 7233, Section 4.1 |
207 | MULTI_STATUS | WebDAV RFC 4918, Section 11.1 |
208 | ALREADY_REPORTED | WebDAV Binding Extensions RFC 5842, Section 7.1 (Experimental) |
226 | IM_USED | Delta Encoding in HTTP RFC 3229, Section 10.4.1 |
300 | MULTIPLE_CHOICES | HTTP/1.1 RFC 7231, Section 6.4.1 |
301 | MOVED_PERMANENTLY | HTTP/1.1 RFC 7231, Section 6.4.2 |
302 | FOUND | HTTP/1.1 RFC 7231, Section 6.4.3 |
303 | SEE_OTHER | HTTP/1.1 RFC 7231, Section 6.4.4 |
304 | NOT_MODIFIED | HTTP/1.1 RFC 7232, Section 4.1 |
305 | USE_PROXY | HTTP/1.1 RFC 7231, Section 6.4.5 |
307 | TEMPORARY_REDIRECT | HTTP/1.1 RFC 7231, Section 6.4.7 |
308 | PERMANENT_REDIRECT | Permanent Redirect RFC 7238, Section 3 (Experimental) |
400 | BAD_REQUEST | HTTP/1.1 RFC 7231, Section 6.5.1 |
401 | UNAUTHORIZED | HTTP/1.1 Authentication RFC 7235, Section 3.1 |
402 | PAYMENT_REQUIRED | HTTP/1.1 RFC 7231, Section 6.5.2 |
403 | FORBIDDEN | HTTP/1.1 RFC 7231, Section 6.5.3 |
404 | NOT_FOUND | HTTP/1.1 RFC 7231, Section 6.5.4 |
405 | METHOD_NOT_ALLOWED | HTTP/1.1 RFC 7231, Section 6.5.5 |
406 | NOT_ACCEPTABLE | HTTP/1.1 RFC 7231, Section 6.5.6 |
407 | PROXY_AUTHENTICATION_REQUIRED | HTTP/1.1 Authentication RFC 7235, Section 3.2 |
408 | REQUEST_TIMEOUT | HTTP/1.1 RFC 7231, Section 6.5.7 |
409 | CONFLICT | HTTP/1.1 RFC 7231, Section 6.5.8 |
410 | GONE | HTTP/1.1 RFC 7231, Section 6.5.9 |
411 | LENGTH_REQUIRED | HTTP/1.1 RFC 7231, Section 6.5.10 |
412 | PRECONDITION_FAILED | HTTP/1.1 RFC 7232, Section 4.2 |
413 | REQUEST_ENTITY_TOO_LARGE | HTTP/1.1 RFC 7231, Section 6.5.11 |
414 | REQUEST_URI_TOO_LONG | HTTP/1.1 RFC 7231, Section 6.5.12 |
415 | UNSUPPORTED_MEDIA_TYPE | HTTP/1.1 RFC 7231, Section 6.5.13 |
416 | REQUEST_RANGE_NOT_SATISFIABLE | HTTP/1.1 Range Requests RFC 7233, Section 4.4 |
417 | EXPECTATION_FAILED | HTTP/1.1 RFC 7231, Section 6.5.14 |
422 | UNPROCESSABLE_ENTITY | WebDAV RFC 4918, Section 11.2 |
423 | LOCKED | WebDAV RFC 4918, Section 11.3 |
424 | FAILED_DEPENDENCY | WebDAV RFC 4918, Section 11.4 |
426 | UPGRADE_REQUIRED | HTTP/1.1 RFC 7231, Section 6.5.15 |
428 | PRECONDITION_REQUIRED | Additional HTTP Status Codes RFC 6585 |
429 | TOO_MANY_REQUESTS | Additional HTTP Status Codes RFC 6585 |
431 | REQUEST_HEADER_FIELDS_TOO_LARGE Additional | HTTP Status Codes RFC 6585 |
500 | INTERNAL_SERVER_ERROR | HTTP/1.1 RFC 7231, Section 6.6.1 |
501 | NOT_IMPLEMENTED | HTTP/1.1 RFC 7231, Section 6.6.2 |
502 | BAD_GATEWAY | HTTP/1.1 RFC 7231, Section 6.6.3 |
503 | SERVICE_UNAVAILABLE | HTTP/1.1 RFC 7231, Section 6.6.4 |
504 | GATEWAY_TIMEOUT | HTTP/1.1 RFC 7231, Section 6.6.5 |
505 | HTTP_VERSION_NOT_SUPPORTED | HTTP/1.1 RFC 7231, Section 6.6.6 |
506 | VARIANT_ALSO_NEGOTIATES | Transparent Content Negotiation in HTTP RFC 2295, Section 8.1 (Experimental) |
507 | INSUFFICIENT_STORAGE | WebDAV RFC 4918, Section 11.5 |
508 | LOOP_DETECTED | WebDAV Binding Extensions RFC 5842, Section 7.2 (Experimental) |
510 | NOT_EXTENDED | An HTTP Extension Framework RFC 2774, Section 7 (Experimental) |
511 | NETWORK_AUTHENTICATION_REQUIRED | Additional HTTP Status Codes RFC 6585, Section 6 |
def get(self): data = { "name":"zhangsan", "age":24, "gender":1, } data_str = json.dumps(data) self.write(data) # self.set_header("name", "test") # self.set_status(404) # 標準狀態碼,不用設置reason # self.set_status(555,"666") # 非標準狀態碼,設置了reason self.set_status(211) # 非標準狀態碼,未設置reason,錯誤
5. redirect(url)
告知瀏覽器跳轉到url。
class IndexHandler(RequestHandler): """對應/""" def get(self): self.write("主頁") class LoginHandler(RequestHandler): """對應/login""" def get(self): self.write('<form method="post"><input type="submit" value="登錄"></form>') def post(self): self.redirect("/")
6. send_error(status_code=500, **kwargs)
拋出HTTP錯誤狀態碼status_code,默認爲500,kwargs爲可變命名參數。使用send_error拋出錯誤後tornado會調用write_error()方法進行處理,並返回給瀏覽器處理後的錯誤頁面。
class IndexHandler(RequestHandler): def get(self): self.write("主頁") self.send_error(404, content="出現404錯誤")
注意:默認的write\_error()方法不會處理send\_error拋出的kwargs參數,即上面的代碼中content="出現404錯誤"是沒有意義的。
嘗試下面的代碼會出現什麼問題?
class IndexHandler(RequestHandler): def get(self): self.write("主頁") self.send_error(404, content="出現404錯誤") self.write("結束") # 咱們在send_error再次向輸出緩衝區寫內容
注意:使用send_error()方法後就不要再向輸出緩衝區寫內容了!
7. write_error(status_code, **kwargs)
用來處理send_error拋出的錯誤信息並返回給瀏覽器錯誤信息頁面。能夠重寫此方法來定製本身的錯誤顯示頁面。
def write_error(self, status_code, **kwargs): self.write("<h1>出錯了!</h1>") self.write("<p>錯誤名:%s</p>" % kwargs.get("title","")) self.write("<p>錯誤詳情:%s</p>" % kwargs.get("content","")) def get(self): err_title = "not found" err_content = "未找到資源" self.send_error(404, title=err_title, content=err_content)
3.4 接口與調用順序
下面的接口方法是由tornado框架進行調用的,咱們能夠選擇性的重寫這些方法。
1. initialize()
對應每一個請求的處理類Handler在構造一個實例後首先執行initialize()方法。在講輸入時提到,路由映射中的第三個字典型參數會做爲該方法的命名參數傳遞,如:
class ProfileHandler(RequestHandler): def initialize(self, database): self.database = database def get(self): ... app = Application([ (r'/user/(.*)', ProfileHandler, dict(database=database)), ])
此方法一般用來初始化參數(對象屬性),不多使用。
2. prepare()
預處理,即在執行對應請求方式的HTTP方法(如get、post等)前先執行,注意:不論以何種HTTP方式請求,都會執行prepare()方法。
以預處理請求體中的json數據爲例:
import json class IndexHandler(RequestHandler): def prepare(self): if self.request.headers.get("Content-Type").startswith("application/json"): self.json_dict = json.loads(self.request.body) else: self.json_dict = None def post(self): if self.json_dict: for key, value in self.json_dict.items(): self.write("<h3>%s</h3><p>%s</p>" % (key, value)) def put(self): if self.json_dict: for key, value in self.json_dict.items(): self.write("<h3>%s</h3><p>%s</p>" % (key, value))
3. HTTP方法
方法 | 描述 |
---|---|
get | 請求指定的頁面信息,並返回實體主體。 |
head | 相似於get請求,只不過返回的響應中沒有具體的內容,用於獲取報頭 |
post | 向指定資源提交數據進行處理請求(例如提交表單或者上傳文件)。數據被包含在請求體中。POST請求可能會致使新的資源的創建和/或已有資源的修改。 |
delete | 請求服務器刪除指定的內容。 |
patch | 請求修改局部數據。 |
put | 從客戶端向服務器傳送的數據取代指定的文檔的內容。 |
options | 返回給定URL支持的全部HTTP方法。 |
4. on_finish()
在請求處理結束後調用,即在調用HTTP方法後調用。一般該方法用來進行資源清理釋放或處理日誌等。注意:請儘可能不要在此方法中進行響應輸出。
5. set_default_headers()
6. write_error()
7. 調用順序
咱們經過一段程序來看上面這些接口的調用順序。
class IndexHandler(RequestHandler): def initialize(self): print "調用了initialize()" def prepare(self): print "調用了prepare()" def set_default_headers(self): print "調用了set_default_headers()" def write_error(self, status_code, **kwargs): print "調用了write_error()" def get(self): print "調用了get()" def post(self): print "調用了post()" self.send_error(200) # 注意此出拋出了錯誤 def on_finish(self): print "調用了on_finish()"
在正常狀況未拋出錯誤時,調用順序爲:
在有錯誤拋出時,調用順序爲: