aio-libs/aiohttp · warning · ValueError

No start or end of range specified

Error message

No start or end of range specified

What it means

Raised as ValueError by BaseRequest.http_range (aiohttp/web_request.py:631) when the Range header matched the byte syntax but both captured groups were empty - i.e. literally 'Range: bytes=-'. aiohttp has no start and no end to compute a slice from, so the parsed range is meaningless and it raises rather than return a degenerate slice(None, None, 1).

Source

Thrown at aiohttp/web_request.py:631

                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")

        return slice(start, end, 1)

    @reify
    def content(self) -> StreamReader:
        """Return raw payload stream."""
        return self._payload

    @property
    def can_read_body(self) -> bool:
        """Return True if request's HTTP BODY can be read, False otherwise."""
        return not self._payload.at_eof()

    @reify
    def body_exists(self) -> bool:
        """Return True if request has HTTP BODY, False otherwise."""
        return type(self._payload) is not EmptyStreamReader

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Wrap request.http_range access in try/except ValueError and treat it as 'no range requested' (serve full body).
  2. Return HTTPRequestedRangeNotSatisfiable (416) if you want strict semantics.
  3. Audit upstream clients / proxies that emit 'bytes=-' to confirm they actually want a partial response.

Example fix

// before
rng = request.http_range  # raises on 'bytes=-'

# after
try:
    rng = request.http_range
except ValueError:
    rng = slice(None, None, 1)  # treat as full body
Defensive patterns

Strategy: try-catch

Validate before calling

if request.headers.get("Range") == "bytes=-":
    rng = slice(None, None, 1)
else:
    rng = request.http_range

Type guard

def is_meaningful_range(value: str) -> bool:
    return value != "bytes=-"

Try / catch

try:
    rng = request.http_range
except ValueError:
    rng = slice(None, None, 1)  # treat as full body

Prevention

When it happens

Trigger: A client sending 'Range: bytes=-' (empty start and empty end) - typically a buggy download client or a probe. Because the regex '(\d*)-(\d*)' accepts empty captures, only the explicit None/None check on line 630 catches it.

Common situations: Misbehaving clients (some old Android download managers, curl edge cases); corrupted header from a proxy; manual tests that hardcode 'bytes=-'.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/7bf577f67e583a76.json. Report an issue: GitHub.