Tornado進階

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)
View Code

對於路由中的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()
View Code

 

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同前。

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同前。

說明

對於請求體中數據的要求同前。 這兩個方法最經常使用。

用代碼來看上述六中方法的使用:

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()
View Code

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()
View Code

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()
View Code

 

3.3 輸出

1. write(chunk)

將chunk數據寫到輸出緩衝區。如咱們在以前的示例代碼中寫的:

class IndexHandle(RequestHandler):
    """主路由處理類"""

    def get(self):
        self.write("test1")
View Code

在同一個處理方法中屢次使用write方法

class IndexHandle(RequestHandler):

    def get(self):
        self.write("test1")
        self.write("test2")
        self.write("test3")
View Code

  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)
View Code

 實際上,咱們能夠不用本身手動去作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)
View Code

兩種方式有什麼差別?

對比一下兩種方式的響應頭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")
View Code

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()
View Code

 

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,錯誤
View Code

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("/")
View Code

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錯誤")
View Code

注意:默認的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再次向輸出緩衝區寫內容
View Code

注意:使用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)
View Code

 

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)),
    ])
View Code

此方法一般用來初始化參數(對象屬性),不多使用。

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))
View Code

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()"
View Code

在正常狀況未拋出錯誤時,調用順序爲:

  1. set_defautl_headers()
  2. initialize()
  3. prepare()
  4. HTTP方法
  5. on_finish()

在有錯誤拋出時,調用順序爲:

  1. set_default_headers()
  2. initialize()
  3. prepare()
  4. HTTP方法
  5. set_default_headers()
  6. write_error()
  7. on_finish()
相關文章
相關標籤/搜索