aio-libs/aiohttp · error · InvalidURLError

{path}

Error message

{path}

What it means

Raised by HttpRequestParser.parse_message (aiohttp/http_parser.py:702) when the request-target does not match any allowed form (RFC 9112 section 3.2). If it does not start with '/' (origin-form), is not '*' with OPTIONS (asterisk-form), and yarl cannot parse it as an absolute URL (absolute-form for proxies), AND the method is not CONNECT (authority-form), InvalidURLError(path) is raised. Authority-form (host:port) is ONLY valid with CONNECT.

Source

Thrown at aiohttp/http_parser.py:702

            # NOTE: HTTP Request-Line input producing different
            # NOTE: `yarl.URL()` objects
            url = URL.build(
                path=path_part,
                query_string=qs_part,
                fragment=url_fragment,
                encoded=True,
            )
        elif path == "*" and method == "OPTIONS":
            # asterisk-form,
            url = URL(path, encoded=True)
        else:
            # absolute-form for proxy maybe,
            # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.2
            url = URL(path, encoded=True)
            if not url.absolute:
                # authority-form is only allowed with CONNECT
                # https://www.rfc-editor.org/info/rfc9112/#section-3.2.3-1
                raise InvalidURLError(
                    path.encode(errors="surrogateescape").decode("latin1")
                )

        # read headers
        (
            headers,
            raw_headers,
            close,
            compression,
            upgrade,
            chunked,
        ) = self.parse_headers(lines[1:])

        if version_o == HttpVersion11 and hdrs.HOST not in headers:
            raise BadHttpMessage("Missing 'Host' header in request.")

        if close is None:  # then the headers weren't set in the request
            if version_o <= HttpVersion10:  # HTTP 1.0 must asks to not close

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Use origin-form ('/path?query') for normal requests to an origin server.
  2. Use the literal '*' only with OPTIONS; use authority-form ('host:port') only with CONNECT.
  3. Use absolute-form ('http://host/path') only for forward-proxy requests.
  4. Let aiohttp's ClientSession format the request target from the URL you pass it.

Example fix

# before - authority-form with GET (invalid)
sock.send(b'GET example.com:80 HTTP/1.1\r\n\r\n')
# after - origin-form + Host header (normal origin request)
sock.send(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')
# or, for CONNECT, authority-form is correct:
sock.send(b'CONNECT example.com:80 HTTP/1.1\r\nHost: example.com:80\r\n\r\n')
Defensive patterns

Strategy: validation

Validate before calling

def valid_request_target(method: str, target: str) -> bool:
    from yarl import URL
    if target.startswith('/'):           # origin-form
        return True
    if target == '*' and method == 'OPTIONS':   # asterisk-form
        return True
    if method == 'CONNECT' and ':' in target:    # authority-form
        return True
    try:
        return URL(target, encoded=True).absolute  # absolute-form
    except Exception:
        return False
assert valid_request_target(method, target)

Prevention

When it happens

Trigger: A request like 'GET example.com:80 HTTP/1.1' (authority-form with a non-CONNECT method), 'OPTIONS foo HTTP/1.1' (asterisk-form requires a literal '*'), or a malformed target that is neither a path nor a valid absolute URL. Common when a client confuses proxy-style absolute URIs with origin-form paths.

Common situations: Proxies receiving absolute-form for non-proxy requests, clients sending authority-form by mistake, raw clients with the wrong path syntax, or HTTP/2 pseudo-headers leaking into HTTP/1.1.

Related errors


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