Перейти к содержанию

Multipart Form Data

Чтение тела запроса как multipart/form-data.

Описание

Form Data (MIME-type: multipart/form-data) — один из наиболее часто используемых типов содержимого для передачи двоичных данных на сервер.

Формат multipart означает, что данные отправляются на сервер отдельными частями. Каждая часть может иметь свой тип содержимого, имя файла и сами данные. Разделение данных происходит с помощью граничной строки.

from pydantic import BaseModel, ConfigDict
from rapidy.http import post, Body, ContentType, FileField

class UserData(BaseModel):
    model_config = ConfigDict(arbitrary_types_allowed=True)

    username: str
    password: str
    image: FileField

@post('/')
async def handler(
    user_data: UserData = Body(content_type=ContentType.m_part_form_data),
    # or
    user_data: UserData = Body(content_type='multipart/form-data'),
) -> ...:

Пример данных

POST /  HTTP/1.1
Host: localhost:8080
Content-Type: multipart/form-data; boundary=---WD9146A
Content-Length: ...

---WD9146A
Content-Disposition: form-data; name="username"

User
---WD9146A
Content-Disposition: form-data; name="password"

myAwesomePass
---WD9146A
Content-Disposition: form-data; name="image"; filename="image.png";
Content-Type: image/png

<... binary data ...>
---WD9146A
Отправка с помощью curl
curl -X POST \
-H "Content-Type: multipart/form-data" \
-F username=User \
-F password=myAwesomePass \
http://127.0.0.1:8080

Извлечение без валидации

Отключение валидации не рекомендуется.

Если валидация отключена, параметр будет содержать базовую структуру aiohttp:

  • Body(content_type=ContentType.m_part_form_data) → MultiDictProxy[Union[str, bytes, FileField]]

Способы отключения валидации

Явное отключение

from pydantic import BaseModel
from rapidy.http import post, Body, ContentType

class BodyData(BaseModel):
    ...

@post('/')
async def handler(
    data: BodyData = Body(validate=False, content_type=ContentType.m_part_form_data),
) -> ...:

Использование Any

@post('/')
async def handler(
    data: Any = Body(content_type=ContentType.m_part_form_data),
) -> ...:

Отсутствие аннотации типа

Если не указывать тип, по умолчанию будет установлен Any.

@post('/')
async def handler(
    data=Body(content_type=ContentType.m_part_form_data),
) -> ...:


Значения по умолчанию

Если HTTP-запрос не содержит тела, параметр получит указанное значение по умолчанию (если оно задано).

Примеры использования

Указано значение по умолчанию

from pydantic import BaseModel
from rapidy.http import post, Body, ContentType

class BodyData(BaseModel):
    ...

@post('/')
async def handler(
    data: BodyData = Body('some_data', content_type=ContentType.m_part_form_data),
    # or
    data: BodyData = Body(default_factory=lambda: 'some_data', content_type=ContentType.m_part_form_data),
) -> ...:

Опциональное тело запроса

from pydantic import BaseModel
from rapidy.http import post, Body, ContentType

class BodyData(BaseModel):
    ...

@post('/')
async def handler(
    data: BodyData | None = Body(content_type=ContentType.m_part_form_data),
    # or
    data: Optional[BodyData] = Body(content_type=ContentType.m_part_form_data),
    # or
    data: Union[BodyData, None] = Body(content_type=ContentType.m_part_form_data),
) -> ...:

Извлечение сырых данных

Rapidy использует метод post объекта Request для получения данных и передает их в Pydantic для валидации.

Как происходит извлечение внутри Rapidy
async def extract_post_data(request: Request) -> Optional[MultiDictProxy[Union[str, bytes, FileField]]]:
    if not request.body_exists:
        return None

    return await request.post()

Rapidy использует встроенные механизмы aiohttp

Подробнее об объекте aiohttp.Request и методах извлечения данных можно узнать здесь.

x-www-form-urlencoded и multipart/form-data обрабатываются одинаково.

Оба этих типа данных извлекаются через метод post объекта Request. Это особенность реализации aiohttp.

Однако если параметр аннотирован как bytes или StreamReader, данные извлекаются иначе.

Подробнее об объекте StreamReader можно узнать здесь.

bytes

from rapidy.http import post, Body, ContentType

@post('/')
async def handler(
    user_data: bytes = Body(content_type=ContentType.m_part_form_data),
    # also you can use pydantic validation
    user_data: bytes = Body(content_type=ContentType.m_part_form_data, min_length=1),
) -> ...:
Внутренний код Rapidy
async def extract_body_bytes(request: Request) -> Optional[bytes]:
    if not request.body_exists:
        return None

    return await request.read()

StreamReader

from rapidy import StreamReader
from rapidy.http import post, Body, ContentType

@post('/')
async def handler(
    user_data: StreamReader = Body(content_type=ContentType.m_part_form_data),
) -> ...:
Внутренний код Rapidy
async def extract_body_stream(request: Request) -> Optional[StreamReader]:
    if not request.body_exists:
        return None

    return request.content

Валидация Pydantic для StreamReader не поддерживается.

Значение по умолчанию для StreamReader задать нельзя.

Если попытаться установить значение по умолчанию для Body с аннотацией StreamReader через default или default_factory, будет вызвана ошибка ParameterCannotUseDefaultError.

from rapidy import StreamReader
from rapidy.http import post, Body, ContentType

@post('/')
async def handler(
    user_data: StreamReader = Body(default='SomeDefault', content_type=ContentType.m_part_form_data),
) -> ...:
------------------------------
Handler attribute with Type `Body` cannot have a default value.

Handler path: `<full_path>/main.py`
Handler name: `handler`
Attribute name: `data`
------------------------------