aio-libs/aiohttp · error · ValueError

boundary missed for Content-Type

Error message

boundary missed for Content-Type: %s

What it means

MultipartReader.__init__ requires the Content-Type to be multipart/* and to carry a 'boundary' parameter. If parse_mimetype finds no boundary in the parameters, ValueError is raised because multipart framing cannot proceed without it.

Solutions

  1. Ensure the request/response Content-Type includes a valid boundary parameter, e.g. 'multipart/form-data; boundary=----xyz'.
  2. When building requests with MultipartWriter, let the writer generate the Content-Type (it embeds the boundary) rather than setting one manually.
  3. Before constructing MultipartReader, check parse_mimetype(content_type).parameters for 'boundary' and reject early with a clear 400.
  4. Forward the full Content-Type header through proxies without modification.

Example fix

// before
Content-Type: multipart/form-data

// after
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
Defensive patterns

Strategy: validation

Validate before calling

from aiohttp.hdrs import CONTENT_TYPE
from aiohttp.multipart import parse_mimetype

def has_boundary(content_type: str) -> bool:
    mt = parse_mimetype(content_type)
    return mt.type == 'multipart' and 'boundary' in mt.parameters

# at the request boundary
if not has_boundary(request.headers.get(CONTENT_TYPE, '')):
    return web.Response(status=400, text='multipart Content-Type missing boundary')

Type guard

def is_multipart_with_boundary(content_type: object) -> bool:
    if not isinstance(content_type, str):
        return False
    mt = parse_mimetype(content_type)
    return mt.type == 'multipart' and bool(mt.parameters.get('boundary'))

Try / catch

try:
    reader = MultipartReader(request.headers, request.content)
except ValueError as e:
    return web.Response(status=400, text=str(e))

Prevention

When it happens

Trigger: Constructing MultipartReader from headers whose Content-Type is 'multipart/form-data' (or mixed/related) with no ';boundary=...' token, or where the boundary parameter is malformed by the parser.

Common situations: A server/proxy stripping the boundary from Content-Type; a client forwarding multipart headers incompletely; malformed requests crafted to test error handling; Content-Type set manually without boundary.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/cf11ef0a13815650. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/multipart.py:699

    #: None points to type(self)
    multipart_reader_cls: type["MultipartReader"] | None = None
    #: Body part reader class for non multipart/* content types.
    part_reader_cls = BodyPartReader

    def __init__(
        self,
        headers: Mapping[str, str],
        content: StreamReader,
        *,
        client_max_size: int = sys.maxsize,
        max_field_size: int = 8190,
        max_headers: int = 128,
        max_size_error_cls: type[Exception] = ValueError,
    ) -> None:
        self._mimetype = parse_mimetype(headers[CONTENT_TYPE])
        assert self._mimetype.type == "multipart", "multipart/* content type expected"
        if "boundary" not in self._mimetype.parameters:
            raise ValueError(
                "boundary missed for Content-Type: %s" % headers[CONTENT_TYPE]
            )

        self.headers = headers
        self._boundary = ("--" + self._get_boundary()).encode()
        self._client_max_size = client_max_size
        self._content = content
        self._default_charset: str | None = None
        self._last_part: MultipartReader | BodyPartReader | None = None
        self._max_field_size = max_field_size
        self._max_headers = max_headers
        self._max_size_error_cls = max_size_error_cls
        self._at_eof = False
        self._at_bof = True
        self._unread: list[bytes] = []

    def __aiter__(self) -> Self:
        return self

View on GitHub (pinned to d041d4d0fd)