{"record":{"id":"d69f2c035646e067","repo":"aio-libs/aiohttp","slug":"domain-not-valid","errorCode":null,"errorMessage":"Domain not valid","messagePattern":"Domain not valid","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_urldispatcher.py","lineNumber":790,"sourceCode":"        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:\n        return {\"domain\": self._domain}\n\n\nclass MaskDomain(Domain):","sourceCodeStart":772,"sourceCodeEnd":808,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_urldispatcher.py#L772-L808","documentation":"Thrown by Domain.validation() when, after stripping the scheme and lowercasing, the hostname's dot-separated labels each fail to match re_part (roughly a valid DNS label: alphanumerics and hyphens, max 63 chars, no leading/trailing hyphen). This guards add_domain() against malformed host patterns that could never match a real Host header. Each label must independently be a legal DNS label.","triggerScenarios":"Calling add_domain('exa mple.com') (whitespace inside), add_domain('ex_ample.com') (underscore), add_domain('a' * 64 + '.com') (label too long), or add_domain('..com') (empty label). MaskDomain allows '*' but otherwise the same rule applies.","commonSituations":"Using underscores in hostnames (common in some internal DNS but illegal per the regex); trailing malformed characters from config parsing; wildcard patterns with '*' in the wrong position (only MaskDomain supports '*').","solutions":["Use plain hyphen-and-alphanumeric labels only: 'api.example.com'.","For wildcards, switch to a Domain subclass that uses MaskDomain (app.add_domain('*.example.com') routes through MaskDomain automatically only if configured).","Validate each label with a regex like r'(?!-)[a-z0-9-]{1,63}(?<!-)' before calling add_domain."],"exampleFix":"// before\napp.add_domain('api_stage.example.com', sub_app)  # underscore rejected\n// after\napp.add_domain('api-stage.example.com', sub_app)","handlingStrategy":"validation","validationCode":"import re\nLABEL = re.compile(r'(?!-)[a-z0-9-]{1,63}(?<!-)')\n\ndef valid_host(host: str) -> bool:\n    host = host.rstrip('.').lower()\n    if '://' in host or not host:\n        return False\n    return all(LABEL.fullmatch(p) for p in host.split('.'))\n\nif not valid_host(cfg_host):\n    raise ValueError(f'invalid domain: {cfg_host!r}')","typeGuard":"def is_dns_label_string(value: str) -> bool:\n    return (\n        isinstance(value, str)\n        and '://' not in value\n        and bool(value)\n        and all(\n            part and len(part) <= 63 and not part.startswith('-') and not part.endswith('-')\n            and part.replace('-', '').isalnum()\n            for part in value.rstrip('.').lower().split('.')\n        )\n    )","tryCatchPattern":"try:\n    app.add_domain(host, sub_app)\nexcept ValueError as e:\n    if 'Domain not valid' in str(e):\n        # log and fall back to a default host or skip sub-app mount\n        log.warning('skipping sub-app for invalid host %r', host)\n    else:\n        raise","preventionTips":["Avoid underscores in hostnames; use hyphens.","Validate each DNS label against RFC 1035 rules before add_domain.","For wildcards, ensure you are using a rule class that supports them (MaskDomain)."],"tags":["url-dispatcher","domain-routing","validation","dns"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}