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 closeView on GitHub (pinned to c0ef574e29)
Solutions
- Use origin-form ('/path?query') for normal requests to an origin server.
- Use the literal '*' only with OPTIONS; use authority-form ('host:port') only with CONNECT.
- Use absolute-form ('http://host/path') only for forward-proxy requests.
- 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
- Use origin-form (/path) for regular requests
- Reserve CONNECT for authority-form and OPTIONS for the literal '*'
- Let ClientSession format the target from a yarl URL
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
- Bad HTTP method in status line {line!r}
- Bad status line {line!r}
- Missing 'Host' header in request.
- base_url must have a trailing '/'
- Method cannot contain non-token characters {method!r} (found
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/74c5144abf08e4d0.json.
Report an issue: GitHub.