{"id":"465f3b65fdedbbc8","repo":"aio-libs/aiohttp","slug":"domain-cannot-be-empty","errorCode":null,"errorMessage":"Domain cannot be empty","messagePattern":"Domain cannot be empty","errorType":"validation","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/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/web_urldispatcher.py#L766-L802","documentation":"Raised as ValueError by Domain.validation when, after stripping trailing dots and lowercasing, the domain string is empty. A host rule with no hostname cannot match anything meaningful, so it is rejected. Triggered via Domain(''), Domain('.'), or Domain('   ') after normalization.","triggerScenarios":"Constructing aiohttp.web.Domain('') or Domain('.'), or app.add_domain('', sub_app). Also when a host variable from config is an empty string or only whitespace/ dots.","commonSituations":"Empty host config value; environment variable unset (defaulting to ''); building domain rules from a list that contained an empty element.","solutions":["Provide a real hostname: Domain('example.com').","Filter empty values from config lists before constructing Domain rules.","Guard config with: if host: app.add_domain(host, sub_app)."],"exampleFix":"# before\nhost = os.environ.get('HOST', '')\napp.add_domain(host, sub_app)  # '' raises\n\n# after\nhost = os.environ.get('HOST', '')\nif host:\n    app.add_domain(host, sub_app)","handlingStrategy":"validation","validationCode":"def clean_domain(domain: str) -> str:\n    d = domain.strip().rstrip('.').lower()\n    if not d:\n        raise ValueError('Domain cannot be empty')\n    return d","typeGuard":"def is_nonempty_domain(domain) -> bool:\n    return isinstance(domain, str) and bool(domain.strip().rstrip('.'))","tryCatchPattern":"try:\n    app.add_domain(host, sub_app)\nexcept ValueError as e:\n    if 'empty' in str(e):\n        log.warning('skipping empty host rule')\n    else:\n        raise","preventionTips":["Filter empty/whitespace entries from host config lists.","Guard add_domain calls with 'if host:' checks.","Validate domain rules in config-loading tests."],"tags":["aiohttp","domain-matching","value-error","validation","configuration"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}