tornado 系列講解之二(web 框架之RequestHandler和Application)

tornado.web

tornado.web提供了一個(gè)具有異步特征的web框架蹄衷,能支持很多連接菌瘫,支持長輪詢歌焦。

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

if __name__ == "__main__":
    application = tornado.web.Application([
        (r"/", MainHandler),
    ])
    application.listen(8888)
    tornado.ioloop.IOLoop.current().start()

線程安全說明

一般來講,RequestHandler提供的方法和tornado其他地方的方法都不是線程安全的郁油,特別對(duì)于write,finish攀痊,和flush來講桐腌,必須在主線程調(diào)用。如果使用多線程苟径,請(qǐng)求完成前最好使用 IOLoop.add_callback將控制權(quán)交給主線程案站,或者限制其他線程在IOLoop.run_in_executor,保證回調(diào)運(yùn)行在executor棘街,且不會(huì)引用到tornado對(duì)象蟆盐。

Request handlers

tornado.web.RequestHandler(...)
是所有http請(qǐng)求的基類,子類必須實(shí)現(xiàn)至少一個(gè)http方法遭殉,子類不要覆蓋__init__方法石挂,可以使用 initialize方法代替。

Entry points

  • RequestHandler.initialize()
    子類初始化方法险污,每個(gè)request都會(huì)被調(diào)用誊稚,URLSpec的第三個(gè)參數(shù),是dict的話罗心,會(huì)傳到initialize() 方法里
class ProfileHandler(RequestHandler):
    def initialize(self, database):
        self.database = database

    def get(self, username):
        ...

app = Application([
    (r'/user/(.*)', ProfileHandler, dict(database=database)),
    ])
  • RequestHandler.prepare()
    在調(diào)用HTTP方法之前調(diào)用
  • RequestHandler.on_finish()
    request結(jié)束后調(diào)用里伯,重載這個(gè)方法以便進(jìn)行一些清理操作

Input

獲取參數(shù)方法支持HTML表單形式,如果希望使用其他格式的參數(shù)渤闷,比如json疾瓮,可以:

def prepare(self):
    if self.request.headers['Content-Type'] == 'application/x-json':
        self.args = json_decode(self.request.body)
    # Access self.args directly instead of using self.get_argument.
  • RequestHandler.get_argumentt(name: str, default: Union[None, str, RAISE] = RAISE, strip: bool = True)
    獲取指定name的query和body的參數(shù)。
  • RequestHandler.get_arguments(name: str, strip: bool = True) → List[str]
    返回指定name的參數(shù)列表飒箭,包括query和body
  • RequestHandler.get_query_argument(name: str, default: Union[None, str, RAISE] = RAISE, strip: bool = True) → Optional[str]
    獲取指定name的query參數(shù)
  • RequestHandler.get_query_arguments(name: str, strip: bool = True) → List[str]
    獲取指定name的query參數(shù)列表
  • RequestHandler.get_body_argument(name: str, default: Union[None, str, RAISE] = RAISE, strip: bool = True) → Optional[str]
    獲取指定name的body參數(shù)
  • RequestHandler.get_body_arguments(name: str, strip: bool = True) → List[str]
    獲取指定name的body參數(shù)列表
  • RequestHandler.decode_argument(value: bytes, name: str = None) → str
    對(duì)參數(shù)解碼
  • RequestHandler.request
    tornado.httputil.HTTPServerRequest對(duì)象包含了請(qǐng)求數(shù)據(jù)和header

Output

  • RequestHandler.set_status(status_code: int, reason: str = None) → None
    為response設(shè)置狀態(tài)碼
  • RequestHandler.set_header(name: str, value: Union[bytes, str, int, numbers.Integral, datetime.datetime]) → None
    為response設(shè)置header
  • RequestHandler.add_header
    為response添加header
  • RequestHandler.clear_header
  • RequestHandler.set_default_headers() → None
    request到來前設(shè)置header
  • RequestHandler.write(chunk: Union[str, bytes, dict]) → None
    如果chunk是dict狼电,會(huì)被轉(zhuǎn)成json蜒灰,并且設(shè)置 Content-Type為application/json
  • RequestHandler.flush(include_footers: bool = False) → Future[None]
    把當(dāng)前輸出緩存到網(wǎng)絡(luò)中
  • RequestHandler.finish(chunk: Union[str, bytes, dict] = None) → Future[None]
    結(jié)束掉http請(qǐng)求,接受的參數(shù)和write一樣肩碟。
  • RequestHandler.render(template_name: str, **kwargs) → Future[None]
    以給定參數(shù)呈現(xiàn)給模板强窖,這個(gè)方法會(huì)調(diào)用finish,因此這個(gè)方法調(diào)用后削祈,后面再調(diào)用其他方法也不會(huì)有output了
  • RequestHandler.render_string(template_name: str, **kwargs) → bytes
    用給定的參數(shù)生成模板翅溺。
  • RequestHandler.redirect(url: str, permanent: bool = False, status: int = None) → None
    重定向到給定的url
  • RequestHandler.send_error(status_code: int = 500, **kwargs) → None
    發(fā)送指定錯(cuò)誤碼給瀏覽器
  • RequestHandler.clear() → None
    為這個(gè)response重置所有的headers和content

cookie

  • RequestHandler.cookies
    self.request.cookies的別名
  • RequestHandler.get_cookie(name: str, default: str = None) → Optional[str]
    返回指定那么的cookie
  • RequestHandler.set_cookie(name: str, value: Union[str, bytes], domain: str = None, expires: Union[float, Tuple, datetime.datetime] = None, path: str = '/', expires_days: int = None, **kwargs) → None
    設(shè)置cookie
  • RequestHandler.clear_cookie(name: str, path: str = '/', domain: str = None) → None
    清除指定name的cookie
  • RequestHandler.clear_all_cookies(path: str = '/', domain: str = None) → None
    清除這個(gè)用戶的所有cookie
  • RequestHandler.get_secure_cookie
    如果有效,返回簽名cookie
  • RequestHandler.set_secure_cookie
    為cookie簽名髓抑,防止篡改
    ...

Other

  • RequestHandler.application
    Application對(duì)象咙崎。
  • RequestHandler.current_user
    當(dāng)前已認(rèn)證用戶
    這個(gè)可以被兩種方式改寫:
  1. 重新get_current_user方法
def get_current_user(self):
    user_cookie = self.get_secure_cookie("user")
    if user_cookie:
        return json.loads(user_cookie)
    return None
  1. 重載prepare():
@gen.coroutine
def prepare(self):
    user_id_cookie = self.get_secure_cookie("user_id")
    if user_id_cookie:
        self.current_user = yield load_user(user_id_cookie)
  • RequestHandler.get_login_url() → str
    重載這個(gè)方法可以自定義login UR,默認(rèn)情況下吨拍,使用的是application中settings的login_url褪猛。
  • RequestHandler.on_connection_close() → None
    如果客戶端關(guān)閉連接,會(huì)在異步handler調(diào)用羹饰。重載這個(gè)方法用于長連接的清理工作伊滋。
  • RequestHandler.reverse_url(name: str, *args) → str
    Application.reverse_url的別名
  • RequestHandler.settings
    self.application.settings的別名
  • RequestHandler.static_url(path: str, include_host: bool = None, **kwargs) → str
    返回指定的相對(duì)路徑的靜態(tài)文件的url,使用這個(gè)方法需要先設(shè)置static_path队秩,

Application configuration

  • 類tornado.web.Application(handlers: List[Union[Rule, Tuple]] = None, default_host: str = None, transforms: List[Type[OutputTransform]] = None, **settings)
    這個(gè)類的實(shí)例能直接通過httpserver去啟動(dòng):
application = web.Application([
    (r"/", MainPageHandler),
])
http_server = httpserver.HTTPServer(application)
http_server.listen(8080)
ioloop.IOLoop.current().start()

Application的參數(shù)是list新啼,我們按順序遍歷列表,并實(shí)例化第一個(gè)匹配到的requestHandler刹碾。也可在這配置靜態(tài)文件位置:

application = web.Application([
    (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
])

使用add_handlers也可以添加虛擬主機(jī):

application.add_handlers(r"www\.myhost\.com", [
    (r"/article/([0-9]+)", ArticleHandler),
])

可以使用add_handlers綁定多個(gè)域名燥撞。

  • Application.reverse_url(name: str, *args) → str
    返回指定handler名的url。

裝飾器(Decorators)

  • tornado.web.authenticated(method: Callable[[...], Optional[Awaitable[None]]]) → Callable[[...], Optional[Awaitable[None]]]
    一個(gè)用于控制登錄的裝飾器迷帜,沒有登錄的話物舒,會(huì)跳轉(zhuǎn)到配置的login_url
  • tornado.web.addslash(method: Callable[[...], Optional[Awaitable[None]]]) → Callable[[...], Optional[Awaitable[None]]]
    使用此裝飾器向請(qǐng)求路徑添加缺少的尾隨斜杠。比如一個(gè)/foo的請(qǐng)求將會(huì)跳轉(zhuǎn)到/foo/戏锹,路由規(guī)則中需要與這樣配置r'/foo/?'配合使用
  • tornado.web.removeslash(method: Callable[[...], Optional[Awaitable[None]]]) → Callable[[...], Optional[Awaitable[None]]]
    使用此裝飾器從請(qǐng)求路徑中刪除尾隨斜杠.比如:
    使用此裝飾器向請(qǐng)求路徑添加缺少的尾隨斜杠冠胯。比如一個(gè)/foo/的請(qǐng)求將會(huì)跳轉(zhuǎn)到/foo,路由規(guī)則中需要與這樣配置r'/foo/*'配合使用
  • tornado.web.stream_request_body
    支持streaming body锦针,和文件上傳有關(guān)荠察,具體參考tornado.web.stream_request_body
    文件上傳
#!/usr/bin/env python

"""Usage: python file_uploader.py [--put] file1.txt file2.png ...
Demonstrates uploading files to a server, without concurrency. It can either
POST a multipart-form-encoded request containing one or more files, or PUT a
single file without encoding.
See also file_receiver.py in this directory, a server that receives uploads.
"""

import mimetypes
import os
import sys
from functools import partial
from uuid import uuid4

try:
    from urllib.parse import quote
except ImportError:
    # Python 2.
    from urllib import quote

from tornado import gen, httpclient, ioloop
from tornado.options import define, options


# Using HTTP POST, upload one or more files in a single multipart-form-encoded
# request.
@gen.coroutine
def multipart_producer(boundary, filenames, write):
    boundary_bytes = boundary.encode()

    for filename in filenames:
        filename_bytes = filename.encode()
        mtype = mimetypes.guess_type(filename)[0] or "application/octet-stream"
        buf = (
            (b"--%s\r\n" % boundary_bytes)
            + (
                b'Content-Disposition: form-data; name="%s"; filename="%s"\r\n'
                % (filename_bytes, filename_bytes)
            )
            + (b"Content-Type: %s\r\n" % mtype.encode())
            + b"\r\n"
        )
        yield write(buf)
        with open(filename, "rb") as f:
            while True:
                # 16k at a time.
                chunk = f.read(16 * 1024)
                if not chunk:
                    break
                yield write(chunk)

        yield write(b"\r\n")

    yield write(b"--%s--\r\n" % (boundary_bytes,))


# Using HTTP PUT, upload one raw file. This is preferred for large files since
# the server can stream the data instead of buffering it entirely in memory.
@gen.coroutine
def post(filenames):
    client = httpclient.AsyncHTTPClient()
    boundary = uuid4().hex
    headers = {"Content-Type": "multipart/form-data; boundary=%s" % boundary}
    producer = partial(multipart_producer, boundary, filenames)
    response = yield client.fetch(
        "http://localhost:8888/post",
        method="POST",
        headers=headers,
        body_producer=producer,
    )

    print(response)


@gen.coroutine
def raw_producer(filename, write):
    with open(filename, "rb") as f:
        while True:
            # 16K at a time.
            chunk = f.read(16 * 1024)
            if not chunk:
                # Complete.
                break

            yield write(chunk)


@gen.coroutine
def put(filenames):
    client = httpclient.AsyncHTTPClient()
    for filename in filenames:
        mtype = mimetypes.guess_type(filename)[0] or "application/octet-stream"
        headers = {"Content-Type": mtype}
        producer = partial(raw_producer, filename)
        url_path = quote(os.path.basename(filename))
        response = yield client.fetch(
            "http://localhost:8888/%s" % url_path,
            method="PUT",
            headers=headers,
            body_producer=producer,
        )
        print(response)


if __name__ == "__main__":
    define("put", type=bool, help="Use PUT instead of POST", group="file uploader")

    # Tornado configures logging from command line opts and returns remaining args.
    filenames = options.parse_command_line()
    if not filenames:
        print("Provide a list of filenames to upload.", file=sys.stderr)
        sys.exit(1)

    method = put if options.put else post
    ioloop.IOLoop.current().run_sync(lambda: method(filenames))

文件接收

#!/usr/bin/env python

"""Usage: python file_receiver.py
Demonstrates a server that receives a multipart-form-encoded set of files in an
HTTP POST, or streams in the raw data of a single file in an HTTP PUT.
See file_uploader.py in this directory for code that uploads files in this format.
"""

import logging

try:
    from urllib.parse import unquote
except ImportError:
    # Python 2.
    from urllib import unquote

import tornado.ioloop
import tornado.web
from tornado import options


class POSTHandler(tornado.web.RequestHandler):
    def post(self):
        for field_name, files in self.request.files.items():
            for info in files:
                filename, content_type = info["filename"], info["content_type"]
                body = info["body"]
                logging.info(
                    'POST "%s" "%s" %d bytes', filename, content_type, len(body)
                )

        self.write("OK")


@tornado.web.stream_request_body
class PUTHandler(tornado.web.RequestHandler):
    def initialize(self):
        self.bytes_read = 0

    def data_received(self, chunk):
        self.bytes_read += len(chunk)

    def put(self, filename):
        filename = unquote(filename)
        mtype = self.request.headers.get("Content-Type")
        logging.info('PUT "%s" "%s" %d bytes', filename, mtype, self.bytes_read)
        self.write("OK")


def make_app():
    return tornado.web.Application([(r"/post", POSTHandler), (r"/(.*)", PUTHandler)])


if __name__ == "__main__":
    # Tornado configures logging.
    options.parse_command_line()
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市奈搜,隨后出現(xiàn)的幾起案子悉盆,更是在濱河造成了極大的恐慌,老刑警劉巖馋吗,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件焕盟,死亡現(xiàn)場離奇詭異,居然都是意外死亡宏粤,警方通過查閱死者的電腦和手機(jī)脚翘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門灼卢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人来农,你說我怎么就攤上這事鞋真。” “怎么了沃于?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵涩咖,是天一觀的道長。 經(jīng)常有香客問我揽涮,道長,這世上最難降的妖魔是什么饿肺? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任蒋困,我火速辦了婚禮,結(jié)果婚禮上敬辣,老公的妹妹穿的比我還像新娘雪标。我一直安慰自己,他們只是感情好溉跃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布村刨。 她就那樣靜靜地躺著,像睡著了一般撰茎。 火紅的嫁衣襯著肌膚如雪嵌牺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天龄糊,我揣著相機(jī)與錄音逆粹,去河邊找鬼。 笑死炫惩,一個(gè)胖子當(dāng)著我的面吹牛僻弹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播他嚷,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蹋绽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了筋蓖?” 一聲冷哼從身側(cè)響起卸耘,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎粘咖,沒想到半個(gè)月后鹊奖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涂炎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年忠聚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了设哗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡两蟀,死狀恐怖网梢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赂毯,我是刑警寧澤战虏,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站党涕,受9級(jí)特大地震影響烦感,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜膛堤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一手趣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肥荔,春花似錦绿渣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至誉帅,卻和暖如春淀散,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蚜锨。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國打工吧凉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人踏志。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓阀捅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親针余。 傳聞我的和親對(duì)象是個(gè)殘疾皇子饲鄙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容