Request
简介#
按照惯例,Request
作为参数被命名为 request
,但你可以随意命名。参数的名称并不重要。以下两个处理程序都是有效的。
@app.get("/foo")
async def typical_use_case(request):
return text("I said foo!")
@app.get("/foo")
async def atypical_use_case(req):
return text("I said foo!")
注解请求对象非常简单。
from sanic.request import Request
from sanic.response import text
@app.get("/typed")
async def typed_handler(request: Request):
return text("Done.")
请求体#
Request
对象允许你以几种不同的方式访问请求体的内容。
$ curl localhost:8000 -d '{"foo": "bar"}'
>>> print(request.json)
{'foo': 'bar'}
$ curl localhost:8000 -d '{"foo": "bar"}'
>>> print(request.body)
b'{"foo": "bar"}'
小技巧
request.form
对象是几种类型中的一种,它是一个字典,每个值都是一个列表。这是因为 HTTP 允许使用单个键来发送多个值。
大多数情况下,你会想要使用 get()
方法来访问第一个元素而不是一个列表。如果你确实需要所有项目的列表,你可以使用 getlist()
。
$ curl localhost:8000 -d 'foo=bar'
>>> print(request.body)
b'foo=bar'
>>> print(request.form)
{'foo': ['bar']}
>>> print(request.form.get("foo"))
bar
>>> print(request.form.getlist("foo"))
['bar']
小技巧
request.files
对象是几种类型中的一种,它是一个字典,每个值都是一个列表。这是因为 HTTP 允许使用单个键来发送多个值。
大多数情况下,你会想要使用 get()
方法来访问第一个元素而不是一个列表。如果你确实需要所有项目的列表,你可以使用 getlist()
。
$ curl -F 'my_file=@/path/to/TEST' http://lo
>>> print(request.body)
b'--------------------------cb566ad845ad02d3\r\nContent-Disposition: form-data; name="my_file"; filename="TEST"\r\nContent-Type: application/octet-stream\r\n\r\nhello\n\r\n--------------------------cb566ad845ad02d3--\r\n'
>>> print(request.files)
{'my_file': [File(type='application/octet-stream', body=b'hello\n', name='TEST')]}
>>> print(request.files.get("my_file"))
File(type='application/octet-stream', body=b'hello\n', name='TEST')
>>> print(request.files.getlist("my_file"))
[File(type='application/octet-stream', body=b'hello\n', name='TEST')]
Context
请求上下文#
request.ctx
#
request.ctx
对象是你的游乐场,用于存储你需要的关于请求的任何信息。它只存在于请求的持续时间内,并且对请求是唯一的。
这与 app.ctx
对象形成对比,后者在所有请求之间共享。小心不要将它们混淆!
默认情况下,request.ctx
对象是 SimpleNamespace
对象,允许你在其上设置任意属性。Sanic 不会将此对象用于任何其他目的,因此你可以自由地使用它,而不必担心名称冲突。
这通常用于存储诸如已验证用户详细信息之类的项目:
@app.on_request
async def run_before_handler(request):
request.ctx.user = await fetch_user_by_token(request.token)
@app.route('/hi')
async def hi_my_name_is(request):
if not request.ctx.user:
return text("Hmm... I don't know you")
return text(f"Hi, my name is {request.ctx.user.name}")
如你所见,request.ctx
对象是存储你需要在多个处理程序中访问的信息的好地方,这使得你的代码更 DRY(不重复)且更易于维护。但是,正如我们在后面中间件部分将要学到的,你还可以使用它来存储中间件中的信息,以便在另一个中间件中使用。
连接请求上下文#
通常你的 API 需要向同一个客户端提供多个并发(或连续的)请求。例如,这在需要查询多个端点以获取数据的进步型 Web 应用程序中非常常见。
HTTP 协议通过使用 keep alive 头来减轻由连接引起的开销时间。
@app.on_request
async def increment_foo(request):
if not hasattr(request.conn_info.ctx, "foo"):
request.conn_info.ctx.foo = 0
request.conn_info.ctx.foo += 1
@app.get("/")
async def count_foo(request):
return text(f"request.conn_info.ctx.foo={request.conn_info.ctx.foo}")
$ curl localhost:8000 localhost:8000 localhost:8000
request.conn_info.ctx.foo=1
request.conn_info.ctx.foo=2
request.conn_info.ctx.foo=3
警告
虽然这看起来像是一个方便的地方来存储单个 HTTP 连接的请求之间的信息,但不要假设单个连接上的所有请求都来自单个最终用户。这是因为 HTTP 代理和负载均衡器可以将多个连接复用到服务器的单个连接中。
不要使用这个来存储关于单个用户的信息。对于那个,请使用 request.ctx
对象。
请求的参数#
@app.route('/tag/<tag>')
async def tag_handler(request, tag):
return text("Tag - {}".format(tag))
# or, explicitly as keyword arguments
@app.route('/tag/<tag>')
async def tag_handler(request, *, tag):
return text("Tag - {}".format(tag))
在请求实例上有两个属性来获取查询参数: request.args
和 request.query_args
。这允许您从请求路径(?
后面的部分)访问查询参数。
在大多数用例中,您都希望使用请求对象来访问查询参数。这将是解析后的查询字符串作为字典。
这是目前为止最常见的模式。
考虑这个例子,我们有一个带有 q
参数的 /search
端点,我们想用它来搜索一些东西。
@app.get("/search")
async def search(request):
query = request.args.get("q")
if not query:
return text("No query string provided")
return text(f"Searching for: {query}")
有时,你可能希望以原始字符串或元组列表的形式访问查询字符串。为此,你可以使用 request.query_string
和 request.query_args
属性。
还应该注意的是,HTTP 允许单个键有多个值。尽管 request.args
似乎像是一个普通的字典,但它实际上是一个特殊类型,允许单个键有多个值。你可以通过使用 request.args.getlist()
方法来访问这个。
request.query_string
- 原始查询字符串request.query_args
- 解析后的查询字符串作为元组列表request.args
- 解析后的查询字符串作为特殊字典request.args.get()
- 获取键的第一个值(类似于普通字典)request.args.getlist()
- 获取键的所有值
curl "http://localhost:8000?key1=val1&key2=val2&key1=val3"
>>> print(request.args)
{'key1': ['val1', 'val3'], 'key2': ['val2']}
>>> print(request.args.get("key1"))
val1
>>> print(request.args.getlist("key1"))
['val1', 'val3']
>>> print(request.query_args)
[('key1', 'val1'), ('key2', 'val2'), ('key1', 'val3')]
>>> print(request.query_string)
key1=val1&key2=val2&key1=val3
当前请求 getter#
有时,您可能会发现需要在应用程序中无法访问的位置访问当前请求。一个典型的例子可能是日志格式。您可以使用 request.get_current
来获取当前请求(如果有的话)。
请记住,请求对象仅限于单个 asyncio。正在运行处理程序的任务。如果您不在该任务中,则没有请求对象。
import logging
from sanic import Request, Sanic, json
from sanic.exceptions import SanicException
from sanic.log import LOGGING_CONFIG_DEFAULTS
LOGGING_FORMAT = (
"%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: "
"%(request_id)s %(request)s %(message)s %(status)d %(byte)d"
)
old_factory = logging.getLogRecordFactory()
def record_factory(*args, **kwargs):
record = old_factory(*args, **kwargs)
record.request_id = ""
try:
request = Request.get_current()
except SanicException:
...
else:
record.request_id = str(request.id)
return record
logging.setLogRecordFactory(record_factory)
LOGGING_CONFIG_DEFAULTS["formatters"]["access"]["format"] = LOGGING_FORMAT
app = Sanic("Example", log_config=LOGGING_CONFIG_DEFAULTS)
在这个例子中,我们将 request.id
添加到每个访问日志消息中。