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è)可以被兩種方式改寫:
- 重新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
- 重載
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()