{"record":{"id":"465f3b65fdedbbc8","repo":"aio-libs/aiohttp","slug":"domain-cannot-be-empty","errorCode":null,"errorMessage":"Domain cannot be empty","messagePattern":"Domain cannot be empty","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_urldispatcher.py","lineNumber":784,"sourceCode":"\n\nclass 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","sourceCodeStart":766,"sourceCodeEnd":802,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_urldispatcher.py#L766-L802","documentation":"After confirming the domain is a str, DomainValidator strips trailing dots and lowercases; if the result is empty (e.g. the input was '' or only dots like '...'), it raises ValueError. An empty domain cannot match any Host header, so the rule would be a no-op — aiohttp rejects it at construction to surface the misconfiguration immediately.","triggerScenarios":"Constructing a Domain rule with Domain(''), Domain('.'), Domain('...'), or a string that becomes empty after stripping trailing dots. Typically arises from misconfigured host-based routing where the domain comes from an empty config value.","commonSituations":"Environment variable for the domain is unset (defaults to ''); config file with a blank domain field; dynamic construction that yields an empty string when a lookup fails; trailing-dot-only strings from malformed input.","solutions":["Provide a non-empty domain string after stripping dots.","Validate config at startup — fail loudly if the domain env var is empty rather than passing it through to Domain().","Default to a concrete hostname (e.g. 'localhost' for dev) when the config value is missing."],"exampleFix":"// before\nimport os\nrule = Domain(os.environ.get('DOMAIN', ''))  # '' if unset → ValueError\n// after\nimport os\ndomain = os.environ.get('DOMAIN')\nif not domain:\n    raise SystemExit('DOMAIN env var must be set')\nrule = Domain(domain)","handlingStrategy":"validation","validationCode":"def non_empty_domain(domain: str) -> str:\n    cleaned = domain.rstrip('.').lower()\n    if not cleaned:\n        raise ValueError('Domain is empty after stripping dots')\n    return cleaned","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate config-derived domain strings at startup before passing to Domain().","Default to a concrete hostname (e.g. 'localhost') when the env var is unset.","Fail fast with a clear message rather than letting an empty string reach the router."],"tags":["routing","domain","validation","configuration"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}