{"record":{"id":"f21cd9bf2b1f56c0","repo":"aio-libs/aiohttp","slug":"scheme-not-supported","errorCode":null,"errorMessage":"Scheme not supported","messagePattern":"Scheme not supported","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_urldispatcher.py","lineNumber":786,"sourceCode":"class Domain(AbstractRuleMatching):\n    re_part = re.compile(r\"(?!-)[a-z\\d-]{1,63}(?<!-)\")\n\n    def __init__(self, domain: str) -> None:\n        super().__init__()\n        self._domain = self.validation(domain)\n\n    @property\n    def canonical(self) -> str:\n        return self._domain\n\n    def validation(self, domain: str) -> str:\n        if not isinstance(domain, str):\n            raise TypeError(\"Domain must be str\")\n        domain = domain.rstrip(\".\").lower()\n        if not domain:\n            raise ValueError(\"Domain cannot be empty\")\n        elif \"://\" in domain:\n            raise ValueError(\"Scheme not supported\")\n        url = URL(\"http://\" + domain)\n        assert url.raw_host is not None\n        if not all(self.re_part.fullmatch(x) for x in url.raw_host.split(\".\")):\n            raise ValueError(\"Domain not valid\")\n        if url.port == 80:\n            return url.raw_host\n        return f\"{url.raw_host}:{url.port}\"\n\n    async def match(self, request: Request) -> bool:\n        host = request.headers.get(hdrs.HOST)\n        if not host:\n            return False\n        return self.match_domain(host)\n\n    def match_domain(self, host: str) -> bool:\n        return host.lower() == self._domain\n\n    def get_info(self) -> _InfoDict:","sourceCodeStart":768,"sourceCodeEnd":804,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_urldispatcher.py#L768-L804","documentation":"Thrown by Domain.validation() when the domain string passed to Application.add_domain() (or a Domain/MatchDomain rule) contains '://'. aiohttp attaches sub-applications by host, not by full URL, so it prepends 'http://' internally to parse the host; an embedded scheme breaks that logic and is rejected. Pass a bare hostname like 'example.com' or 'example.com:8080' instead of 'http://example.com'.","triggerScenarios":"Calling app.add_domain('https://api.example.com', sub_app) or constructing Domain('http://localhost:8080'). Any value containing '://' after rstrip('.').lower() hits line 786.","commonSituations":"Copy-pasting a full URL from a browser or config file into add_domain(); migrating from a framework that accepts full URLs; reading the host from an environment variable that includes the scheme (e.g. BASE_URL=https://api.example.com).","solutions":["Strip the scheme before passing the host: use URL(value).raw_host (and URL(value).port) instead of the raw string.","Pass only the host[:port] form, e.g. 'example.com' or 'example.com:8443'.","Sanitize env-derived config: host = value.split('://')[-1] before add_domain."],"exampleFix":"// before\napp.add_domain('https://api.example.com', sub_app)\n// after\nfrom yarl import URL\nu = URL('https://api.example.com')\napp.add_domain(f'{u.raw_host}:{u.port}' if u.port else u.raw_host, sub_app)","handlingStrategy":"validation","validationCode":"from yarl import URL\n\ndef normalize_domain(value: str) -> str:\n    if '://' in value:\n        u = URL(value)\n        host = u.raw_host\n        return f'{host}:{u.port}' if u.port and u.port != 80 else host\n    return value\n\n# usage:\napp.add_domain(normalize_domain(cfg_host), sub_app)","typeGuard":"import re\nDNS_LABEL = re.compile(r'(?!-)[a-z0-9*-]{1,63}(?<!-)', re.I)\n\ndef is_bare_host(value: str) -> bool:\n    return '://' not in value and all(\n        DNS_LABEL.fullmatch(p) for p in value.rstrip('.').lower().split('.') if p\n    )","tryCatchPattern":"try:\n    app.add_domain(host_str, sub_app)\nexcept ValueError as e:\n    if 'Scheme not supported' in str(e):\n        log.error('add_domain needs a bare host, got %r', host_str)\n    raise","preventionTips":["Always pass host[:port] to add_domain, never a full URL.","Derive the host from config via URL(raw).raw_host rather than string manipulation.","Unit-test add_domain inputs against a list of known-good and known-bad hosts."],"tags":["url-dispatcher","domain-routing","config","validation"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}