Requests
Starlette includes a Request
class that gives you a nicer interface onto
the incoming request, rather than accessing the ASGI scope and receive channel directly.
Request
Signature: Request(scope, receive=None)
from starlette.requests import Request
from starlette.responses import Response
async def app(scope, receive, send):
assert scope['type'] == 'http'
request = Request(scope, receive)
content = '%s %s' % (request.method, request.url.path)
response = Response(content, media_type='text/plain')
await response(scope, receive, send)
Requests present a mapping interface, so you can use them in the same
way as a scope
.
For instance: request['path']
will return the ASGI path.
If you don't need to access the request body you can instantiate a request
without providing an argument to receive
.
Method
The request method is accessed as request.method
.
URL
The request URL is accessed as request.url
.
The property is a string-like object that exposes all the components that can be parsed out of the URL.
For example: request.url.path
, request.url.port
, request.url.scheme
.
Headers
Headers are exposed as an immutable, case-insensitive, multi-dict.
For example: request.headers['content-type']
Query Parameters
Query parameters are exposed as an immutable multi-dict.
For example: request.query_params['search']
Path Parameters
Router path parameters are exposed as a dictionary interface.
For example: request.path_params['username']
Client Address
The client's remote address is exposed as a named two-tuple request.client
(or None
).
The hostname or IP address: request.client.host
The port number from which the client is connecting: request.client.port
Cookies
Cookies are exposed as a regular dictionary interface.
For example: request.cookies.get('mycookie')
Cookies are ignored in case of an invalid cookie. (RFC2109)
Body
There are a few different interfaces for returning the body of the request:
The request body as bytes: await request.body()
The request body, parsed as form data or multipart: async with request.form() as form:
The request body, parsed as JSON: await request.json()
You can also access the request body as a stream, using the async for
syntax:
from starlette.requests import Request
from starlette.responses import Response
async def app(scope, receive, send):
assert scope['type'] == 'http'
request = Request(scope, receive)
body = b''
async for chunk in request.stream():
body += chunk
response = Response(body, media_type='text/plain')
await response(scope, receive, send)
If you access .stream()
then the byte chunks are provided without storing
the entire body to memory. Any subsequent calls to .body()
, .form()
, or .json()
will raise an error.
In some cases such as long-polling, or streaming responses you might need to
determine if the client has dropped the connection. You can determine this
state with disconnected = await request.is_disconnected()
.
Request Files
Request files are normally sent as multipart form data (multipart/form-data
).
Signature: request.form(max_files=1000, max_fields=1000)
You can configure the number of maximum fields or files with the parameters max_files
and max_fields
:
async with request.form(max_files=1000, max_fields=1000):
...
Info
These limits are for security reasons, allowing an unlimited number of fields or files could lead to a denial of service attack by consuming a lot of CPU and memory parsing too many empty fields.
When you call async with request.form() as form
you receive a starlette.datastructures.FormData
which is an immutable
multidict, containing both file uploads and text input. File upload items are represented as instances of starlette.datastructures.UploadFile
.
UploadFile
has the following attributes:
filename
: Anstr
with the original file name that was uploaded orNone
if its not available (e.g.myimage.jpg
).content_type
: Anstr
with the content type (MIME type / media type) orNone
if it's not available (e.g.image/jpeg
).file
: ASpooledTemporaryFile
(a file-like object). This is the actual Python file that you can pass directly to other functions or libraries that expect a "file-like" object.headers
: AHeaders
object. Often this will only be theContent-Type
header, but if additional headers were included in the multipart field they will be included here. Note that these headers have no relationship with the headers inRequest.headers
.size
: Anint
with uploaded file's size in bytes. This value is calculated from request's contents, making it better choice to find uploaded file's size thanContent-Length
header.None
if not set.
UploadFile
has the following async
methods. They all call the corresponding file methods underneath (using the internal SpooledTemporaryFile
).
async write(data)
: Writesdata
(bytes
) to the file.async read(size)
: Readssize
(int
) bytes of the file.async seek(offset)
: Goes to the byte positionoffset
(int
) in the file.- E.g.,
await myfile.seek(0)
would go to the start of the file.
- E.g.,
async close()
: Closes the file.
As all these methods are async
methods, you need to "await" them.
For example, you can get the file name and the contents with:
async with request.form() as form:
filename = form["upload_file"].filename
contents = await form["upload_file"].read()
Info
As settled in RFC-7578: 4.2, form-data content part that contains file
assumed to have name
and filename
fields in Content-Disposition
header: Content-Disposition: form-data;
name="user"; filename="somefile"
. Though filename
field is optional according to RFC-7578, it helps
Starlette to differentiate which data should be treated as file. If filename
field was supplied, UploadFile
object will be created to access underlying file, otherwise form-data part will be parsed and available as a raw
string.
Application
The originating Starlette application can be accessed via request.app
.
Other state
If you want to store additional information on the request you can do so
using request.state
.
For example:
request.state.time_started = time.time()