aio-libs/aiohttp · error · InvalidURLError
{path}
Error message
{path} What it means
Raised as InvalidURLError (a BadHttpMessage subclass, HTTP 400) when the request-line path is not in origin-form ('/...'), asterisk-form ('*' with OPTIONS), or authority-form (only legal with CONNECT), and is not an absolute URI. Per RFC 9112 §3.2.3, the authority-form (host:port) is allowed ONLY with the CONNECT method. Any other method using authority-form, or a malformed non-absolute path, trips this guard.
Solutions
- Send the request in origin-form (path beginning with '/') and put the host in the Host header, e.g. 'GET /path HTTP/1.1' with 'Host: example.com'.
- If you need authority-form, use the CONNECT method (it is the only method allowed to use it).
- If this is a proxy server, ensure the request-target is an absolute URI (http(s)://host/path) so url.absolute is True.
- For raw-socket tests, validate the request-line against RFC 9112 §3.2 before sending.
Example fix
// before (wrong target form) GET example.com:443 HTTP/1.1\r\n\r\n // after (origin-form + Host header) GET / HTTP/1.1\r\nHost: example.com\r\n\r\n // CONNECT is the only method allowed in authority-form CONNECT example.com:443 HTTP/1.1\r\nHost: example.com\r\n\r\n
Defensive patterns
Strategy: validation
Validate before calling
import re
from yarl import URL
def valid_request_target(method: str, path: str) -> bool:
# origin-form
if path.startswith('/'):
return True
# asterisk-form
if path == '*' and method.upper() == 'OPTIONS':
return True
# authority-form only with CONNECT
if method.upper() == 'CONNECT':
return bool(re.fullmatch(r'[A-Za-z0-9.\-]+:\d+', path))
# absolute-form (proxy)
try:
return URL(path, encoded=True).absolute
except Exception:
return False Try / catch
from aiohttp.http_exceptions import InvalidURLError, BadHttpMessage
try:
msg, payload, _ = parser.feed_data(raw)
except InvalidURLError as e:
# 400 Bad Request to the peer; log the malformed target
respond_400(f'Invalid request target: {e.args[0] if e.args else ""}')
except BadHttpMessage:
respond_400('Malformed request') Prevention
- Always send requests in origin-form ('/path') with a Host header unless using CONNECT.
- Reserve authority-form strictly for CONNECT.
- If acting as a proxy, accept and forward absolute-form URIs only.
- Validate request-targets in test harnesses against RFC 9112 §3.2.
When it happens
Trigger: A client sends a request like 'GET example.com:443 HTTP/1.1' (authority-form with a non-CONNECT method), or 'GET foo HTTP/1.1' where 'foo' is neither a path starting with '/', nor '*', nor a valid absolute URL. Constructed by HttpRequestParser.parse_message when URL(path, encoded=True).absolute is False and the method is not CONNECT.
Common situations: Misconfigured HTTP client sending proxy-style requests (absolute URI or authority) to a non-proxy server; a proxy or load balancer forwarding the wrong request-target form; a malformed test harness (e.g. raw socket test) sending an invalid request line; curl used with '--resolve' or proxy options against an origin server.
Related errors
- Request has duplicate `chunked` Transfer-Encoding
- Request has invalid `Transfer-Encoding`
- Bad HTTP method in status line
- Bad HTTP method in status line
- Bad status line
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/74c5144abf08e4d0.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/http_parser.py:704
# 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 d041d4d0fd)