aio-libs/aiohttp · error · ValueError

range not in acceptable format

Error message

range not in acceptable format

What it means

The http_range property parses the Range header with the strict regex ^bytes=(\d*)-(\d*)$. If the header is present but does not match (re.findall returns no capture group, raising IndexError), aiohttp raises ValueError('range not in acceptable format'). This indicates a syntactically invalid Range from the client.

Solutions

  1. Catch ValueError around access to request.http_range and respond 416 Range Not Satisfiable.
  2. On invalid range, fall back to serving the full file (ignore the header).
  3. Document to clients that only single-range bytes= syntax is accepted.

Example fix

# before
rng = request.http_range   # raises on bad header
# after
try:
    rng = request.http_range
except ValueError:
    raise web.HTTPRequestRangeNotSatisfiable()
Defensive patterns

Strategy: try-catch

Validate before calling

import re
rng = request.headers.get('Range')
if rng is not None and not re.match(r'^bytes=\d*-\d*$', rng):
    raise web.HTTPRequestRangeNotSatisfiable()

Try / catch

try:
    rng = request.http_range
except ValueError:
    raise web.HTTPRequestRangeNotSatisfiable()

Prevention

When it happens

Trigger: Range: items=0-100 (wrong unit), Range: bytes=abc-def (non-numeric), Range: 0-100 (missing 'bytes='), Range: bytes=1-2,3-4 (multiple ranges unsupported).

Common situations: Misbehaving crawlers/clients; custom download managers; CDNs forwarding non-standard Range syntax.

Related errors


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

Appendix: source

Thrown at aiohttp/web_request.py:613

        parsed = parse_cookie_header(self.headers.get(hdrs.COOKIE, ""))
        # Extract values from Morsel objects
        return MappingProxyType({name: morsel.value for name, morsel in parsed})

    @reify
    def http_range(self) -> "slice[int, int, int]":
        """The content of Range HTTP header.

        Return a slice instance.

        """
        rng = self._headers.get(hdrs.RANGE)
        start, end = None, None
        if rng is not None:
            try:
                pattern = r"^bytes=(\d*)-(\d*)$"
                start, end = re.findall(pattern, rng, re.ASCII)[0]
            except IndexError:  # pattern was not found in header
                raise ValueError("range not in acceptable format")

            end = int(end) if end else None
            start = int(start) if start else None

            if start is None and end is not None:
                # end with no start is to return tail of content
                start = -end
                end = None

            if start is not None and end is not None:
                # end is inclusive in range header, exclusive for slice
                end += 1

                if start >= end:
                    raise ValueError("start cannot be after end")

            if start is end is None:  # No valid range supplied
                raise ValueError("No start or end of range specified")

View on GitHub (pinned to d041d4d0fd)