1. 程式人生 > >Tornado官方文件翻譯--持續更新

Tornado官方文件翻譯--持續更新

簡介

Tornado是一個Python的網路框架和非同步網路庫,最初由FriendFeed開發。通過使用非阻塞的網路I/O,Tornado可以吞吐數以萬計的開放連線,這使它得以完美應用於長輪詢和WebSockets網路協議,以及其他需要彼此進行長連線的應用。

Tornado可以被大致分成四個主要部分:

網路框架(包括作為基類的RequestHandler, RequestHandler用來建立網路應用,以及各種支援類)。
執行HTTP協議(HTTPServer和AsyncHTTPClient)的客戶端與伺服器端。
非同步網路庫,包括類IOLoop和IOStream,作為HTTP元件的構建模組,也可以被用來執行其他協議。
協程庫(tornado.gen),允許非同步程式碼用一種比鏈式呼叫更直接的方式來寫。

Tornado網路框架和HTTP伺服器一起提供了一個除了WSGI以外的其他選擇。儘管可以在一個WSGI容器中使用Tornado網路框架,或者Tornado HTTP伺服器作為其他WSGI框架的容器,但是這些組合每一個都有其侷限,為了利用好Tornado,你需要將Tornado的網路框架和HTTP伺服器結合起來使用。

非同步和非阻塞I/O

實時的網路特點需要每個使用者有一個長連線並且還要有足夠的效能,在一個傳統的非同步網路伺服器中,這意味著要為每一個使用者分配一個執行緒,這樣做代價是非常昂貴的。

為了減小併發連線的代價,Tornado使用單執行緒的event loop。這意味著所有應用的程式碼都應該是非同步和非阻塞的,因為一次只能有一個操作是活躍的。

非同步和非阻塞兩個術語是緊密關聯的,它們在使用的時候經常不加區分,但是它們並不是同一個概念。

阻塞

如果程式在return之前需要等待另一個事件發生,那麼它就會阻塞。程式阻塞的原因很多:網路I/O,磁碟I/O, 鎖等等。事實上,每個函式當它在執行和使用CPU的時候都會阻塞,至少也是一小部分(舉一個極端的例子來論證為什麼CPU阻塞必須和其他型別的阻塞一樣要嚴肅對待,想一想密碼雜湊函式比如bcrypt,它故意佔用幾百毫秒的CPU事件,遠超過一個典型的網路或磁碟訪問事件)

一個函式可能在一些方面是阻塞的而其他方面是非阻塞的。例如:tornado.httpclient在預設配置下會在DNS解析上阻塞,但是其他網路請求中不會阻塞(可以使用有正確配置libcurl的ThreadedResolver或tornado.curl_httpclient模組減輕這個影響)。

非同步

非同步函式在它結束之前return,並且在應用中,通常會在觸發一些後續操作之前導致某些後臺工作發生(這是相對於正常的非同步函式來說,正常的非同步函式會在return之前做完它們要做的所有事情)。有很多種不同的非同步介面:

引數回撥(Callback argument)
返回佔位符(Future,Promise,Deferred)
送入佇列
註冊回撥(Callback registry)

不管使用何種型別的介面,從定義上來說這些非同步函式和他們的呼叫者之間的互動是不同的;沒有任何一種方式可以使一個非同步函式用一種相對它的呼叫者來說透明的方式來實現非同步(像gevent這樣的系統使用輕量級的執行緒來提供和非同步系統相近的效能,但是它們實際上並沒有實現非同步)。

例子

下面是一個非同步函式的例子

from tornado.httpclient import HTTPClient

def synchronous_fetch(url):
	http_client = HTTPClient()
	response = http_client.fetch(url)
	return response.body

這裡用引數呼叫方式重寫這個函式變成非同步:

from tornado.httpclient import AsyncHTTPClient
def asyncchronous_fetch(url, callback):
	http_client = AsyncHTTPClient()
	def handle_response(response):
		callback(response.body)
	http_client.fetch(url, callback=handle_response)

再用Future改寫:

from tornado.concurrent import Future

def async_fetch_future(url):
	http_client = AsyncHTTPClient()
	my_future = Future()
	fetch_future = http_client.fetch(url)
	fetch_future.add_done_callback(
		lambda f: my_future.set_result(f.result())
	)
	return my_future

原生的Future版本更加複雜,但是儘管如此仍然推薦在Tornado中進行練習Futures,因為它們有兩個主要優勢。錯誤處理更加連貫,因為Future.result方法可以簡單丟擲一個exception(相對於callback-oriented介面中普遍的ad-hoc錯誤處理來說),並且Futures非常適合用協程。多執行緒會再此手冊的下一部分深入討論。下面是我們的樣例函式的協程版,和最初的非同步版本非常相似。

from tornado import gen

@gen.coroutine
def fetch_coroutine(url):
	http_client = AsyncHTTPClient()
	response = yield http_client.fetch(url)
	raise gen.Return(response.body)

宣告raise gen.Return(response.body)是python2的一個神器,在這個裡面生成器是不允許返回值的。為了克服這個問題,Tornado執行緒丟擲一種特殊的異常稱作Return。協程會捕捉到這個異常然後把它當成一個返回值來對待。在python3.3以及更高的版本中,return response.body也能獲得同樣的結果。

Coroutines

在Tornado中推薦使用協程的方式來寫非同步程式碼。協程使用Python的yield關鍵字來暫停和恢復執行而不是鏈式回撥(類似gevent一樣的框架中的合作輕量級執行緒有時候也被稱為協程,但是在Tornado中所有的協程都使用明確的上下文轉換,稱為非同步函式)

協程幾乎和非同步程式碼一樣簡單,但是沒有執行緒的代價。它們還通過減少上下文切換可能發生的位置的數量來使併發更容易推理。

例子:

from tornado import gen

@gen.coroutine
def fetch_coroutine(url):
	http_client = AsyncHTTPClient()
	response = yield http_client.fetch(url)
	# In Python versions prior to 3.3,returing a value from
	# a generator is not allowed and you must use 
	# raise gen.Return(response.body)
	#  instead
	return response.body

Python 3.5: async and await

Python3.5引進了async和await關鍵字(使用這些關鍵字的函式也被稱為原生協程(native coroutines))。從Tornado4.3開始,你可以使用它們來代替基於yield的協程(檢視接下來的段落中它的侷限)。很簡單地使用async def foo()的函式定義來代替@gen.coroutine裝飾器,然後await代替yield。為了相容低版本的Python此文件的剩餘部分仍然使用yield的方式,但是在能夠使用的情況下async和await執行速度更快:

async def fetch_coroutine(url):
	http_client = AsyncHTTPClient()
	response = await http_client.fetch(url)
	return response.body

await關鍵字沒有yield功能多。例如,在一個基於yield的協程中,你可以yield一個Futures物件的列表,而在原生協程中則必須用tornado.gen.multi來打包這個列表。這也消除了與concurrent.futures的整合。你可以使用tornado.gen.convert_yielded把任何能用yield起作用的程式碼轉換成await方式:

async def f():
	executor = concurrent.futures.ThreadPoolExecutor()
	await tornado.gen.convert_yielded(executor.submit(g))

	---g是任務函式(譯者注)

儘管原生協程並沒有很明顯的和某個特定框架繫結在一起(即:它們沒有使用像tornado.gen.coroutine或asyncio.coroutine一樣的裝飾器),不是所有的協程都彼此相容。有一個協程執行程式是被第一個呼叫的協程選中的,然後被所有通過await呼叫的協程所共享。Tornado的協程執行程式被設計成多用途,並且接受任何框架的可await的物件;其他協程可能可能會更加侷限(例如,asyncio協程執行程式不接受來自其他框架的協程)。因此,在需要結合多個框架的的應用中推薦使用Tornado的協程執行程式。如果想要在一個已經使用asyncio執行程式的協程中呼叫Tornado的協程執行程式,使用tornado.platform.asyncio.to_asyncio_future介面卡即可。

工作原理

包含yield的函式就是一個生成器。所有的生成器都是非同步的;當被呼叫的時候它們會返回一個生成器物件,而不是執行結束。@gen.coroutine裝飾器通過yield表示式和生成器溝通,通過返回一個Future物件來和協程呼叫者溝通。

下面是一個簡單版的協程裝飾器內部迴圈:

# Simplified inner loop of tornado.gen.Runner
def run(self):
	future = self.gen.send(self.next)
	def callback(f):
		self.next = f.result()
		self.run()
	future.add_done_callback(callback)

裝飾器從生成器中接受一個Future物件,等待(非阻塞)Future結束,然後解包Future物件,並且把結果返回給生成器作為yield表示式的結果。除了把非同步函式返回的Future物件立即傳遞給yield表示式以外,大多數非同步程式碼從不會直接觸碰到Future類。

怎樣呼叫協程

協程不會用正常的方式來丟擲異常:它們丟擲的任何異常在它被yield之前都會被困在FUture物件中。這意味著用正確的方式呼叫協程是非常重要的,否則你可能會忽略一些錯誤:

@gen.coroutine
def divide(x, y):
	return x / y

def bad_call():
	# This should raise a ZeroDivisionError, but it won't bacause the coroutine is called incorrectly.
	divide(1, 0)

在幾乎所有情形下,任何一個呼叫協程的函式本身必須也是協程,並且在呼叫中使用yield關鍵字。當你重寫父類中定義的方法時,最好查閱文件看是否允許協程(文件應該說明此方法“是一個協程”或“返回Future物件”):

@gen.coroutine
def good_call():
	# yield will unwrap the Future returned by divide() and raise
	# the exception
	yield divide(1, 0)

有事你可能想不需要等待結果直接“Fire and forget” 一個協程。這種情況下推薦使用IOLoop.spawn_callback,這可以使IOLoop對呼叫負責。如果失敗IOLoop會記錄堆疊路徑:

# The IOLoop will catch the exception and print a stack trace in 
# the logs.Note that this doesn't look like a normal call ,since
# we pass the function object to be called by the IOLoop.
IOLoop.current().spawn_callback(divide, 1, 0)

在使用@gen.coroutine的函式中推薦用這種方式使用IOLoop.spawn_callback,但是它需要函式使用async def(否則執行緒執行程式不會開始)。

最後,在專案的頂層,如果IOLoop還沒有開始執行,你可以開始IOLoop,執行協程,然後用IOLoop.run_sync方法停止IOLoop。這經常被用來開始面向批量(batch-oriented)的專案的主函式:

# run_sync() doesn't take arguments, so we must wrap the
# call in a lambda.
IOLoop.current().run_sync(lambda: divide(1, 0))

協程模式

回撥互動(Interaction with callbacks)

為了和使用回撥而不是Future的非同步程式碼互動,要在Task中打包此呼叫。這將會為你增加回調引數,並返回一個你可以yield的Future物件:

@gen.coroutine
def call_task():
	# Note that there are no paterns on some_funxtion.
	# This will be translated by Task into
	#   some_function(other_args, callback=callback)
	yield gen.Task(some_function, other_args)

呼叫阻塞函式(Calling blocking functions)

從協程中呼叫一個阻塞函式的最簡單的方法就是使用ThreadPoolExecutor,返回一個其他協程相容的Futures物件:

thread_pool = ThreadPoolExecutor()

@gen.coroutine
def call_blocking():
	yield thread_pool.submit(blocking_func, args)

並行(Parallelism)

協程裝飾器能夠識別列表和字典,因為它們的值是Futures物件,並且並行等待所有的這些Futures:

@gen.coroutine
def parallel_fetch(url1, url2):
	resp1, resp2 = yield [http_client.fetch(url1),
						  http_client.frtch(url2)]

@gen.coroutine
def parallel_fetch_many(urls):
	reponse = yield [http_client.fetch(url) for url in urls]
	# response is a list of HTTPResponse in the same order

@gen.coroutine
def parallel_fetch_dict(urls):
	response = yield {url: http_client.fetch(url) for url in urls}
	# response is a dict {url: HTTPResponse}

交叉存取

有時儲存一個Future物件比立即yield它會更有用,以便你可以在等待之前開始開始另一個操作:

@gen.coroutine
def get(self):
    fetch_future = self.fetch_next_chunk()
    while True:
        chunk = yield fetch_future
        if chunk is None: break
        self.write(chunk)
        fetch_future = self.fetch_next_chunk()
        yield self.flush()

這種模式用@gen.coroutine是最有用的。如果fetch_next_chunk()使用async def,那麼為了開啟後臺程序,它一定會被當作fetch_future = tornado.gen.convert_yielded(self.fetch_next_chunk())來呼叫。

迴圈

對協程來說迴圈是很複雜的,原因是python中在for或者while迴圈中對每一次迭代都yield並且捕捉到yield的結果是不可能的。相反,你需要根據不同的結果區分迴圈條件,如下面的例子:

import motor
db = motor.MotorClient().test

@gen.coroutine
def loop_example(collection):
	cursor = db.collection.find()
	while (yield cursor.fetch_next):
	doc = cursor.next_object()

後臺執行

使用協程後PeriodicCallback便不能正常使用。相反,協程可以包含一個while True迴圈,然後使用tornado.gen.sleep:

@gen.coroutine
def minute_loop():
	while True:
		yield do_something()
		yield gen.sleep(60)

# Coroutines that loop forever are generally started with
# spawn_callback().
IOLoop.current().spawn_callback(minute_loop)

有時一個更加複雜的迴圈可能是明智的。例如,之前的迴圈每60+N秒迴圈一次,N是do_something()的執行時間。為了精確地沒60秒執行一次,使用上面的交叉儲存方法:

@gen.coroutine
def minute_loop2():
	while True:
		nxt = gen.sleep(60)  # start the clock.
		yield do_something() # Run while the clock is ticking.
		yield nxt            # Wait for the timer to run out.

Queue例子 - 併發網路爬蟲

Tornado的Tornado.queues模組為協程補充了一個生產者和消費者模式,類似Python標準庫中的queue模組為執行緒補充的模型。

一個yield Queue.get的協程直到佇列中有元素之前都會暫停。如果佇列設定了最大容量,一個yield Queue.put的協程在佇列有空間之前都會暫停。

佇列中包含了許多未結束的任務,並且佇列是從0開始的。put增加數量;task_done減少。

在這個網路爬蟲的例子中,佇列開始只包含一個base_url。當worker獲取到一個頁面後,它會分析連線,然後把新的連線放進佇列中,然後呼叫一次task_done來減少數量。最終,worker獲取到一個頁面,這個頁面的URL在之前的頁面中已經全部用過了,然後佇列中就沒有work剩餘了。因此worker對task_done的呼叫會把計數器減少至0.正在等待join的主要協程會取消暫停然後結束。

# !/usr/bin/env python

import time
from datetime import timedelta

try:
	from HTMLParser import HTMLParser
	from urlparser import urljoin, urldefrag
except ImportError:
	from html.parser import HTMLParser
	from urllib.parse import urljoin, urldefrag

from tornado import httpclient, gen, ioloop, queues

base_url = 'http://www.tornadoweb.org/en/stable/'
concurrency = 10

@gen.coroutine
def get_links_from_url(url):
	"""Download the page at `url` and parse it for links.

	Returned links have had the fragment after `#` removed, and have been made
	absolute so, e.g. the URL 'gen.html#tornado.gen.coroutine' becomes
	'http://www.tornadoweb.org/en/stable/gen.html'.
	"""
	try:
		response = yield httpclient.AsyncHTTPClient().fetch(url)
		print('fetched %s' %url)

		html = response.body if isinstance(response.body, str) \
		    else response.body.decode()
		urls = [urljoin(url, remove_fragment(new_url))
				for new_url in get_links(html)]
	except Exception as e:
		print("Exception: %s %s" % (e, url))
		raise gen.Return([])

	raise gen.Return(urls)


def remove_fragment(url):
	pure_url, frag = urldefrag(url)
	return pure_url


def get_links(html):
	class URLSeeker(HTMLParser):
		def __init__(self):
			HTMLPrser.__init__(self)
			self.urls = []

		def handle_starttag(self, tag, attrs):
			href = dict(attrs).get("href")
			if href and tag == 'a':
				self.urls.append(href)

		url_seeker = URLSeeker()
		url_seeker.feed(html)
		return url_seeker.urls


@gen.coroutine
def main():
	q = queues.Queue()
	start = time.time()
	fetching, fetched = set(), set()

	@gen.coroutine
	def fetch_url():
		current_url = yield q.get()
		try:
			if current_url in fetching:
			return

			print('fetching %s' % current_url)
			fetched.add(current_url)

			for new_url in urls:
				# Only follow links beneath the base URL
				if new_url.startwith(base_url):
					yield q.put(new_url)

		finally:
			q.task_done()

	@gen.coroutine
	def worker():
		while True:
			yield fetch_url()

	q.put(base_url)

	# Start workers, then wait for the worker queue to be empty.
	for _ in rage(concurrency):
		worker()
	yield q.join(timeout=timedalta(seconds=300))
	assert fetching == fetched
	print('Done in %d seconds, fetched %s URLs.' %(
	time.time() - start, len(fetched)))


if __name__ == "__main__":
	import logging
	logging.basicConfig()
	io_loop = ioloop.IOLoop.current()
	io_loop.run_sync(main)

一個Tornado網路應用的結構

一個Tornado網路應用通常是由一個或多個RequestHandler的子類組成的,Application將接收到的請求傳遞給處理器(handler),然後main()函式開啟服務。

下面是一個最簡單的"hello world"例子:

import tornado.ioloop
improt tornado.web

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

def make_app():
	return tornado.web.Application([
		(r"/", MainHandler),
	])

if __name__ == "__main__":
	app = make_app()
	app.listen(8888)
	tornado.ioloop.IOLoop.current().start()

Application物件

Application物件負責全域性配置,包括對映請求到處理器的路徑表。

路徑表是一個URLSpec物件的列表(或元組),每一個都包含(至少)一個正則表示式和一個處理器類。順序很重要;第一個匹配到的會起作用。如果正則表示式包含匹配分組,這些分組會作為路徑引數傳遞給處理器的HTTP方法。如果一個字典作為URLSpec的第三方元素被傳遞進來,它支援initialization引數,這個引數會傳遞給RequestHandler.initializa方法。最後,URLSpec可能還會有一個名字,這將使它能夠被RequestHandler.reverse_url方法使用。

例如,在下面的程式碼段中,根路徑/對映到MainHandler,/story/後跟一個數字類的URL會對映到StoryHandler。數字會作為字串傳遞給StoryHandler.get方法。

class MainHandler(RequestHandler):
	def get(self):
		self.write('<a href="%s">link to story 1</a>' %
		self.reverse_url("story", "1"))

class StoryHandler(RequestHandler):
	def initialize(self, db):
		self.db = db

	def get(self, story_id):
		self.write("this is story %s" % story_id)

app = Application([
	url(r"/", MainHandler),
	url(r"/story/([0-9]+)", StoryHandler, dict(db=db), name="story")
	])

Application構造器接受許多能用來指定應用行為的關鍵字引數,並且允許可選特性;完整列表請參看Application.settings。

基類化RequestHandler(Subclassing RequestHandler)

Tornado網路應用的絕大部分工作都是在RequestHandler的子類中完成的。處理器子類的主要入口是HTTP方法:get(), post() 等等。每一個處理器可能會定義一個或多個這些方法來處理不同的HTTP動作。正如上面所描述的,這些方法將會加上路徑中匹配到的引數來被呼叫。

在一個處理器中,呼叫一些例如RequestHandler.render或者RequestHandler.write的方法來產生響應。render()方法通過名字載入了Template類,然後加上引數進行提交。write()用來在非模板環境下使用;它接受strings,bytes和dictionaries(字典必須被編碼成json格式)。

在RequestHandler中的很多方法都被設計為可在子類中重寫,並且可以在整個應用中使用。定義一個BaseHandler,並重寫一些方法例如write_error和get_current_user,然後把你的BaseHandler代替RequestHandler作為所有處理器的基類,這種做法是非常常見的。

處理請求輸入(Handling request input)

請求處理器可以通過self.request來獲取代表當前請求的物件。檢視HTTPServerRequest的定義來獲取完整的屬性列表

method, uri, path, query(The query portion of uri), 
version(HTTP version specified in request,e.g. "HATTP/1.1"), headers, body, remote_ip, protocol, host等等,詳細介紹請點選屬性列表。

---譯者注

HTML表單提交的請求資料將會為你分析好,並且可以在一些方法像get_query_argument和get_body_argument中可用。

class MyFormHandler(tornado.web.RequestHandler):
	def get(self):
		self.write('<html><body><form action="/myform" method="POST">'
                   '<input type="text" name="message">'
                   '<input type="submit" value="Submit">'
                   '</form></body></html>')
					)

	def post(self):
		self.set_header("Content-Type", "text/plain")
		self.write("You wrote " + self.get_body_argument("message"))

因為HTML表單編碼對於一個引數是單值還是列表是模糊不清的,RequestHandler對方法進行了區分,使得應用能夠表明是否希望獲得列表,對列表來說,使用get_query_arguments和get_body_arguments代替單值的方法。

通過表單上傳檔案可以通過self.request.files來獲得,對映名字(input的name屬性)到檔案列表。每個檔案都是{“filename”:…, “content_type”:…, “body”:…}格式的字典。files物件檔案僅在使用表單包裝器上傳檔案時才存在(例如multipart/form-data Content-Type);如果沒有使用這種格式,原生的上傳資料會存放在self.request.boby中。預設情況下上傳的檔案是全部快取在記憶體中的;如果你需要處理一些很大的檔案,不方便放在記憶體中,檢視stream_request_body裝飾器。

在demos資料夾中(tornado原始碼中有一個demos資料夾,存放了幾個小的例子),file_reciver.py展示了這兩種方法的來接收上傳檔案。

由於HTML表單編碼的問題(單值和多值的模糊性),Tornado並不打算用其他型別的輸入來統一表單引數。尤其我們不會去分析JSON請求體。希望使用JSON來代替form-encoding的應用可能會重寫prepare方法來解析它們的請求:

def prepare(self):
	if self.request.headers["Content-Type"].startwith("application/json"):
		self.json_args = json.loads(self.request.body)
	else:
		self.json_args = None

重寫RequestHandler中的方法

除了get和post方法外,在RequestHandler還有一些其他方法在必要時也可以被子類重寫。在每個請求中,丟回發生以下一系列的呼叫:

1.每個請求中都會新建一個RequestHandler物件
2.如果有從Application配置中獲得初始引數的話initialize()函式會被呼叫,initialize函式應該只儲存傳遞給成員變數的引數;它可能不會產生任何輸出或呼叫類似send_error一樣的方法。
3.呼叫prepare方法。這在一個基類中是最有用的,基類由你的處理器子類所共享,因為不論使用哪種HTTP方法prepare函式都會被呼叫。prepare可能會產生輸出;如果它呼叫了finish(或者redirect方法等),程序就會在此結束。
4.某一個HTTP方法被呼叫:get,post,put等等。如果URL正則表示式包含了捕捉分組引數(capturing groups),這些引數也會傳遞到此方法中。
5.當請求結束時,on_finish會被呼叫,對非同步處理器來說,這一步在get(或其他方法)return後就會立即發生;對非同步處理器來說,它發生在呼叫finish之後。

正如在RequestHandler文件中註釋的一樣,所有方法都是可以重寫的。最常用扽一些可以被重寫的方法包括:
write_error -輸出html的錯誤頁面。
on_connection_close -當客戶端斷開連線的時候呼叫;應用可能會選擇檢測這種情況然後停止更深層的處理,注意,不能保證一個已關閉的連線也能被及時檢測到。
get_current_user -檢視User authentication
get_user_locale 為當前使用者返回一個locale物件
set_default_headers -可能會被用來在響應中設定另外的響應頭(例如一個custom Server響應頭)

錯誤處理

如果處理器丟擲一個異常,tornado會掉用RequestHandler.write_error來生成一個錯誤頁面。tornado.web.HTTPError可以被用來產生一個特定的狀態碼;其他所有異常都返回500狀態碼。

預設的錯誤頁面包括一個堆疊路徑(debug模式下)另外還有一個錯誤的線上描述(move brand_model.txt to project).為了生成一個自己的錯誤頁面,重寫RequestHandler.write_error(可能是在一個由你的所有處理器所共享的基類中)這個方法可以用過write和render等方法產生正常的輸出。如果錯誤是由一個異常導致的,一個exc_info會作為一個關鍵字引數被傳遞進來(注意,此異常不保證是sys.exc_info中當前的異常,因此write_error必須使用traceback.format_exception來代替traceback.format_exc)。

通過呼叫set_status,寫一個響應或return等方法從常規的處理器方法(而不是write_error)中產生一個錯誤頁面也是可能的。tornado.web.Finish這個特殊的異常可能會被丟擲以終止處理器,在簡單地返回不方便的情況下並不呼叫write_error方法。

對於404錯誤來說,使用default_handler_class Application setting。這個處理器用改重寫prepare方法,而不是更詳細的如get方法,以便在任何HTTP方法中都能使用。它應該產生一個如上所述的錯誤頁面:或者通過丟擲一個HTTPError(404)並重寫為write_error,或者呼叫self.set_status(404並在prepare()中直接產生響應。

重定向

在Tornado中有兩種方式可以重定一個請求:RequestHandler.redirect和RedirectHandler。

你可以在一個RequestHandler中使用self.redirect()。有一個可選的引數permanent,你可以使用它來表明該重定向永久的。permanent的預設值是False,可以生成一個302 Found的HTTP響應碼,它適合於類似成功post請求後重定向使用者這類情況。如果permanent是true,301 Moved PermanentlyHTTP響應碼會被使用,在下面這中情況下是有用的:重定向到一個權威的URL來採用一種相對搜尋引擎友好的方式獲取頁面。

RedirectHandler可以讓你在你的Application路由表中直接配置重定向。例如,配置一個靜態重定向:

app = tornado.web.Application([
	url(r"/app", tornado.web.RedirectHandler,
		dict(url="http://itunes.apple.com/my-app-id")),
	
	])

下面的規則把所有以/pictures/開頭的請求重定向到字首是/photos/

app = tornado.web.Application([
	url(r"/photos/(.*)", MyPhotoHandler),
	url(r"/pictures/(.*)", tornado.web.RedirectHandler, 
		dict(url=r"/photos/{0}")),
	])

不像RequestHandler.redirect,RedirectHandler預設使用永久重定向。這是因為路由表在執行過程中不會改變並且是永久的,儘管在處理器中的重定向很可能是其他可能回改變的邏輯所導致的。如果想發起一個臨時重定向,只需要把permanent=False引數加到RedirectHandler的初始化引數中。

非同步處理器

Tornado的處理器預設是同步的:當get()/post()方法返回值的時候,請求會被問為結束,然後響應會被返回。因為在一個處理器執行的時候其他所有請求都會阻塞,所以任何長時間執行的處理器都應該做成非同步方式以便用一種非阻塞的方式呼叫它的後續操作。這個話題在 Asynchronous and non-Blocking I/O章節中會重新講解;這部分是關於非同步技術在RequestHandler中的細節的。