tornado.web
tornado.web提供了一个具有异步特征的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其他地方的方法都不是线程安全的,特别对于write,finish,和flush来讲,必须在主线程调用。如果使用多线程,请求完成前最好使用 IOLoop.add_callback
将控制权交给主线程,或者限制其他线程在IOLoop.run_in_executor
,保证回调运行在executor,且不会引用到tornado对象。
Request handlers
tornado.web.RequestHandler(...)
是所有http请求的基类,子类必须实现至少一个http方法,子类不要覆盖__init__
方法,可以使用 initialize
方法代替。
Entry points
-
RequestHandler.initialize()
子类初始化方法,每个request都会被调用,URLSpec的第三个参数,是dict的话,会传到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()
在调用HTTP方法之前调用 -
RequestHandler.on_finish()
request结束后调用,重载这个方法以便进行一些清理操作
Input
获取参数方法支持HTML表单形式,如果希望使用其他格式的参数,比如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的参数。 -
RequestHandler.get_arguments(name: str, strip: bool = True) → List[str]
返回指定name的参数列表,包括query和body -
RequestHandler.get_query_argument(name: str, default: Union[None, str, RAISE] = RAISE, strip: bool = True) → Optional[str]
获取指定name的query参数 -
RequestHandler.get_query_arguments(name: str, strip: bool = True) → List[str]
获取指定name的query参数列表 -
RequestHandler.get_body_argument(name: str, default: Union[None, str, RAISE] = RAISE, strip: bool = True) → Optional[str]
获取指定name的body参数 -
RequestHandler.get_body_arguments(name: str, strip: bool = True) → List[str]
获取指定name的body参数列表 -
RequestHandler.decode_argument(value: bytes, name: str = None) → str
对参数解码 -
RequestHandler.request
tornado.httputil.HTTPServerRequest
对象包含了请求数据和header
Output
-
RequestHandler.set_status(status_code: int, reason: str = None) → None
为response设置状态码 -
RequestHandler.set_header(name: str, value: Union[bytes, str, int, numbers.Integral, datetime.datetime]) → None
为response设置header -
RequestHandler.add_header
为response添加header RequestHandler.clear_header
-
RequestHandler.set_default_headers() → None
request到来前设置header -
RequestHandler.write(chunk: Union[str, bytes, dict]) → None
如果chunk是dict,会被转成json,并且设置 Content-Type为application/json -
RequestHandler.flush(include_footers: bool = False) → Future[None]
把当前输出缓存到网络中 -
RequestHandler.finish(chunk: Union[str, bytes, dict] = None) → Future[None]
结束掉http请求,接受的参数和write一样。 -
RequestHandler.render(template_name: str, **kwargs) → Future[None]
以给定参数呈现给模板,这个方法会调用finish,因此这个方法调用后,后面再调用其他方法也不会有output了 -
RequestHandler.render_string(template_name: str, **kwargs) → bytes
用给定的参数生成模板。 -
RequestHandler.redirect(url: str, permanent: bool = False, status: int = None) → None
重定向到给定的url -
RequestHandler.send_error(status_code: int = 500, **kwargs) → None
发送指定错误码给浏览器 -
RequestHandler.clear() → None
为这个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
设置cookie -
RequestHandler.clear_cookie(name: str, path: str = '/', domain: str = None) → None
清除指定name的cookie -
RequestHandler.clear_all_cookies(path: str = '/', domain: str = None) → None
清除这个用户的所有cookie -
RequestHandler.get_secure_cookie
如果有效,返回签名cookie -
RequestHandler.set_secure_cookie
为cookie签名,防止篡改
...
Other
-
RequestHandler.application
Application
对象。 -
RequestHandler.current_user
当前已认证用户
这个可以被两种方式改写:
- 重新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
重载这个方法可以自定义login UR,默认情况下,使用的是application中settings的login_url。 -
RequestHandler.on_connection_close() → None
如果客户端关闭连接,会在异步handler调用。重载这个方法用于长连接的清理工作。 -
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
返回指定的相对路径的静态文件的url,使用这个方法需要先设置static_path,
Application configuration
-
类tornado.web.Application(handlers: List[Union[Rule, Tuple]] = None, default_host: str = None, transforms: List[Type[OutputTransform]] = None, **settings)
这个类的实例能直接通过httpserver去启动:
application = web.Application([
(r"/", MainPageHandler),
])
http_server = httpserver.HTTPServer(application)
http_server.listen(8080)
ioloop.IOLoop.current().start()
Application的参数是list,我们按顺序遍历列表,并实例化第一个匹配到的requestHandler。也可在这配置静态文件位置:
application = web.Application([
(r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
])
使用add_handlers也可以添加虚拟主机:
application.add_handlers(r"www\.myhost\.com", [
(r"/article/([0-9]+)", ArticleHandler),
])
可以使用add_handlers绑定多个域名。
-
Application.reverse_url(name: str, *args) → str
返回指定handler名的url。
装饰器(Decorators)
-
tornado.web.authenticated(method: Callable[[...], Optional[Awaitable[None]]]) → Callable[[...], Optional[Awaitable[None]]]
一个用于控制登录的装饰器,没有登录的话,会跳转到配置的login_url -
tornado.web.addslash(method: Callable[[...], Optional[Awaitable[None]]]) → Callable[[...], Optional[Awaitable[None]]]
使用此装饰器向请求路径添加缺少的尾随斜杠。比如一个/foo
的请求将会跳转到/foo/
,路由规则中需要与这样配置r'/foo/?'
配合使用 -
tornado.web.removeslash(method: Callable[[...], Optional[Awaitable[None]]]) → Callable[[...], Optional[Awaitable[None]]]
使用此装饰器从请求路径中删除尾随斜杠.比如:
使用此装饰器向请求路径添加缺少的尾随斜杠。比如一个/foo/
的请求将会跳转到/foo
,路由规则中需要与这样配置r'/foo/*'
配合使用 -
tornado.web.stream_request_body
支持streaming body,和文件上传有关,具体参考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()