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
- Ensure the request/response Content-Type includes a valid boundary parameter, e.g. 'multipart/form-data; boundary=----xyz'.
- When building requests with MultipartWriter, let the writer generate the Content-Type (it embeds the boundary) rather than setting one manually.
- Before constructing MultipartReader, check parse_mimetype(content_type).parameters for 'boundary' and reject early with a clear 400.
- 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
- Always set a boundary when constructing multipart Content-Type headers manually.
- Let MultipartWriter generate the Content-Type so the boundary is always present.
- Forward Content-Type through proxies unchanged.
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
- boundary value contains invalid characters
- boundary %r is too long (70 chars max)
- boundary should contain ASCII only chars
- content_type must be an instance of str. Got
- Could not find starting boundary
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 selfView on GitHub (pinned to d041d4d0fd)