aio-libs/aiohttp · error · ValueError
Scheme not supported
Error message
Scheme not supported
What it means
Raised as ValueError by Domain.validation when the domain string contains '://' — indicating a scheme (e.g. 'https://example.com') was passed where only a bare host is expected. Domain matches on the Host header, which never includes a scheme, so a schemed input is rejected.
Source
Thrown at aiohttp/web_urldispatcher.py:786
class Domain(AbstractRuleMatching):
re_part = re.compile(r"(?!-)[a-z\d-]{1,63}(?<!-)")
def __init__(self, domain: str) -> None:
super().__init__()
self._domain = self.validation(domain)
@property
def canonical(self) -> str:
return self._domain
def validation(self, domain: str) -> str:
if not isinstance(domain, str):
raise TypeError("Domain must be str")
domain = domain.rstrip(".").lower()
if not domain:
raise ValueError("Domain cannot be empty")
elif "://" in domain:
raise ValueError("Scheme not supported")
url = URL("http://" + domain)
assert url.raw_host is not None
if not all(self.re_part.fullmatch(x) for x in url.raw_host.split(".")):
raise ValueError("Domain not valid")
if url.port == 80:
return url.raw_host
return f"{url.raw_host}:{url.port}"
async def match(self, request: Request) -> bool:
host = request.headers.get(hdrs.HOST)
if not host:
return False
return self.match_domain(host)
def match_domain(self, host: str) -> bool:
return host.lower() == self._domain
def get_info(self) -> _InfoDict:View on GitHub (pinned to c0ef574e29)
Solutions
- Pass only the host (optionally with port): Domain('example.com') or Domain('example.com:8080').
- Strip the scheme before constructing: extract host via yarl: URL(url).raw_host.
- If you meant path-based mounting, use app.add_sub_app(prefix, sub_app) instead of add_domain.
Example fix
# before
app.add_domain('https://api.example.com', sub_app)
# after
from yarl import URL
host = URL('https://api.example.com').raw_host
app.add_domain(host, sub_app) Defensive patterns
Strategy: validation
Validate before calling
from yarl import URL
def host_from_url(value: str) -> str:
if '://' in value:
u = URL(value)
host = u.raw_host
if u.port and u.port != 80:
host = f'{host}:{u.port}'
return host
return value Type guard
def is_bare_host(domain) -> bool:
return isinstance(domain, str) and '://' not in domain and bool(domain.strip()) Try / catch
try:
app.add_domain(host, sub_app)
except ValueError as e:
if 'Scheme not supported' in str(e):
from yarl import URL
app.add_domain(URL(host).raw_host, sub_app)
else:
raise Prevention
- Pass bare hosts to add_domain, never full URLs.
- Normalize config base URLs to hosts with yarl before building Domain rules.
- Distinguish host-based (add_domain) from path-based (add_sub_app) mounting.
When it happens
Trigger: Constructing aiohttp.web.Domain('https://example.com'), Domain('http://api.example.com:8080'), or app.add_domain('https://example.com', sub_app). Passing a full URL instead of just the host.
Common situations: Copy-pasting a full URL from a browser/address bar into domain config; reading base URLs from config and passing them whole; confusing add_domain (host-based) with prefix-based sub-app mounting.
Related errors
- Domain cannot be empty
- '{directory}' is not a directory
- Domain must be str
- base_url must have a trailing '/'
- {url}
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/f21cd9bf2b1f56c0.json.
Report an issue: GitHub.