aio-libs/aiohttp · error · ValueError

{method} is not allowed HTTP method

Error message

{method} is not allowed HTTP method

What it means

Raised as ValueError by Route.__init__ (via add_route/add_get/add_post etc.) when the HTTP method string does not match HTTP_METHOD_RE (token chars defined by RFC 7230). The method is uppercased then matched against the allowed token grammar, so malformed methods, methods with spaces, or typos are rejected before route registration.

Source

Thrown at aiohttp/web_urldispatcher.py:166

class AbstractRoute(abc.ABC):
    def __init__(
        self,
        method: str,
        handler: Handler | type[AbstractView],
        *,
        expect_handler: _ExpectHandler | None = None,
        resource: AbstractResource | None = None,
    ) -> None:
        if expect_handler is None:
            expect_handler = _default_expect_handler

        assert inspect.iscoroutinefunction(expect_handler) or (
            sys.version_info < (3, 14) and asyncio.iscoroutinefunction(expect_handler)  # type: ignore[deprecated]
        ), f"Coroutine is expected, got {expect_handler!r}"

        method = method.upper()
        if not HTTP_METHOD_RE.match(method):
            raise ValueError(f"{method} is not allowed HTTP method")

        if inspect.iscoroutinefunction(handler) or (
            sys.version_info < (3, 14) and asyncio.iscoroutinefunction(handler)  # type: ignore[deprecated]
        ):
            pass
        elif isinstance(handler, type) and issubclass(handler, AbstractView):
            pass
        else:
            raise TypeError(
                f"Only async functions are allowed as web-handlers, got {handler!r}"
            )

        self._method = method
        self._handler = handler
        self._expect_handler = expect_handler
        self._resource = resource

    @property

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Use a single valid RFC 7230 method token: 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'.
  2. For matching any method use app.router.add_route('*', path, handler) which maps to METH_ANY.
  3. Strip/validate methods read from external config: method.strip().upper() before passing.
  4. Register separate routes for each method instead of comma/space-separated lists.

Example fix

# before
app.router.add_route('GET POST', '/items', handler)

# after
app.router.add_route('GET', '/items', handler)
app.router.add_route('POST', '/items', handler)
Defensive patterns

Strategy: validation

Validate before calling

import re
from aiohttp.web_urldispatcher import HTTP_METHOD_RE

def normalize_method(method: str) -> str:
    m = method.strip().upper()
    if not HTTP_METHOD_RE.match(m):
        raise ValueError(f"Invalid HTTP method: {method!r}")
    return m

Type guard

from aiohttp.web_urldispatcher import HTTP_METHOD_RE

def is_valid_method(method: str) -> bool:
    return isinstance(method, str) and bool(HTTP_METHOD_RE.match(method.strip().upper()))

Try / catch

try:
    app.router.add_route(method, path, handler)
except ValueError as e:
    raise ValueError(f"Rejected method {method!r}: {e}") from e

Prevention

When it happens

Trigger: Calling app.router.add_route('GETT', '/', handler), add_route('GET POST', ...), add_route('', ...), or passing a method with illegal characters. Also app.router.add_get with a typo like 'get ' (trailing space) after uppercasing yields 'GET ' which fails the regex.

Common situations: Typing the method name; passing a method read from config with whitespace/newline; concatenating methods; using lowercase is fine (uppercased internally) but embedded spaces or slashes are not; confusion with hdrs.METH_ANY which is handled separately for ANY routes.

Related errors


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