diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 0d86ab57d3f982..5b68f91e494807 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -22,7 +22,6 @@ from aiohttp.typedefs import JSONDecoder, StrOrURL from aiohttp.web_exceptions import HTTPMovedPermanently, HTTPRedirection from aiohttp.web_protocol import RequestHandler -from aiohttp_fast_url_dispatcher import FastUrlDispatcher, attach_fast_url_dispatcher from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa @@ -335,10 +334,6 @@ def __init__( "max_field_size": MAX_LINE_SIZE, }, ) - # By default aiohttp does a linear search for routing rules, - # we have a lot of routes, so use a dict lookup with a fallback - # to the linear search. - attach_fast_url_dispatcher(self.app, FastUrlDispatcher()) self.hass = hass self.ssl_certificate = ssl_certificate self.ssl_peer_certificate = ssl_peer_certificate diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index 0291c6926a5bfa..6f52569c38c0ee 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -14,6 +14,7 @@ import aiohttp from aiohttp import web from aiohttp.hdrs import CONTENT_TYPE, USER_AGENT +from aiohttp.resolver import AsyncResolver from aiohttp.web_exceptions import HTTPBadGateway, HTTPGatewayTimeout from homeassistant import config_entries @@ -24,7 +25,6 @@ from homeassistant.util.hass_dict import HassKey from homeassistant.util.json import json_loads -from .backports.aiohttp_resolver import AsyncResolver from .frame import warn_use from .json import json_dumps diff --git a/homeassistant/helpers/backports/__init__.py b/homeassistant/helpers/backports/__init__.py deleted file mode 100644 index e672fe1d3d2e97..00000000000000 --- a/homeassistant/helpers/backports/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Backports for helpers.""" diff --git a/homeassistant/helpers/backports/aiohttp_resolver.py b/homeassistant/helpers/backports/aiohttp_resolver.py deleted file mode 100644 index efa4ba4bb85934..00000000000000 --- a/homeassistant/helpers/backports/aiohttp_resolver.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Backport of aiohttp's AsyncResolver for Home Assistant. - -This is a backport of the AsyncResolver class from aiohttp 3.10. - -Before aiohttp 3.10, on system with IPv6 support, AsyncResolver would not fallback -to providing A records when AAAA records were not available. - -Additionally, unlike the ThreadedResolver, AsyncResolver -did not handle link-local addresses correctly. -""" - -from __future__ import annotations - -import asyncio -import socket -import sys -from typing import Any, TypedDict - -import aiodns -from aiohttp.abc import AbstractResolver - -# This is a backport of https://github.com/aio-libs/aiohttp/pull/8270 -# This can be removed once aiohttp 3.10 is the minimum supported version. - -_NUMERIC_SOCKET_FLAGS = socket.AI_NUMERICHOST | socket.AI_NUMERICSERV -_SUPPORTS_SCOPE_ID = sys.version_info >= (3, 9, 0) - - -class ResolveResult(TypedDict): - """Resolve result. - - This is the result returned from an AbstractResolver's - resolve method. - - :param hostname: The hostname that was provided. - :param host: The IP address that was resolved. - :param port: The port that was resolved. - :param family: The address family that was resolved. - :param proto: The protocol that was resolved. - :param flags: The flags that were resolved. - """ - - hostname: str - host: str - port: int - family: int - proto: int - flags: int - - -class AsyncResolver(AbstractResolver): - """Use the `aiodns` package to make asynchronous DNS lookups.""" - - def __init__(self, *args: Any, **kwargs: Any) -> None: - """Initialize the resolver.""" - if aiodns is None: - raise RuntimeError("Resolver requires aiodns library") - - self._loop = asyncio.get_running_loop() - self._resolver = aiodns.DNSResolver(*args, loop=self._loop, **kwargs) # type: ignore[misc] - - async def resolve( # type: ignore[override] - self, host: str, port: int = 0, family: int = socket.AF_INET - ) -> list[ResolveResult]: - """Resolve a host name to an IP address.""" - try: - resp = await self._resolver.getaddrinfo( - host, - port=port, - type=socket.SOCK_STREAM, - family=family, # type: ignore[arg-type] - flags=socket.AI_ADDRCONFIG, - ) - except aiodns.error.DNSError as exc: - msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed" - raise OSError(msg) from exc - hosts: list[ResolveResult] = [] - for node in resp.nodes: - address: tuple[bytes, int] | tuple[bytes, int, int, int] = node.addr - family = node.family - if family == socket.AF_INET6: - if len(address) > 3 and address[3] and _SUPPORTS_SCOPE_ID: - # This is essential for link-local IPv6 addresses. - # LL IPv6 is a VERY rare case. Strictly speaking, we should use - # getnameinfo() unconditionally, but performance makes sense. - result = await self._resolver.getnameinfo( - (address[0].decode("ascii"), *address[1:]), - _NUMERIC_SOCKET_FLAGS, - ) - resolved_host = result.node - else: - resolved_host = address[0].decode("ascii") - port = address[1] - else: # IPv4 - assert family == socket.AF_INET - resolved_host = address[0].decode("ascii") - port = address[1] - hosts.append( - ResolveResult( - hostname=host, - host=resolved_host, - port=port, - family=family, - proto=0, - flags=_NUMERIC_SOCKET_FLAGS, - ) - ) - - if not hosts: - raise OSError("DNS lookup failed") - - return hosts - - async def close(self) -> None: - """Close the resolver.""" - self._resolver.cancel() diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c013415c79426b..80eaa3bc31d20f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -3,9 +3,8 @@ aiodhcpwatcher==1.0.2 aiodiscover==2.1.0 aiodns==3.2.0 -aiohttp-fast-url-dispatcher==0.3.0 aiohttp-fast-zlib==0.1.1 -aiohttp==3.9.5 +aiohttp==3.10.0b1 aiohttp_cors==0.7.0 aiozoneinfo==0.2.1 astral==2.2 diff --git a/pyproject.toml b/pyproject.toml index 5d788393bca738..c3a9b864df987b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,9 +24,8 @@ classifiers = [ requires-python = ">=3.12.0" dependencies = [ "aiodns==3.2.0", - "aiohttp==3.9.5", + "aiohttp==3.10.0b1", "aiohttp_cors==0.7.0", - "aiohttp-fast-url-dispatcher==0.3.0", "aiohttp-fast-zlib==0.1.1", "aiozoneinfo==0.2.1", "astral==2.2", diff --git a/requirements.txt b/requirements.txt index a729f09472bc6a..6f6a11b03a1467 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,9 +4,8 @@ # Home Assistant Core aiodns==3.2.0 -aiohttp==3.9.5 +aiohttp==3.10.0b1 aiohttp_cors==0.7.0 -aiohttp-fast-url-dispatcher==0.3.0 aiohttp-fast-zlib==0.1.1 aiozoneinfo==0.2.1 astral==2.2 diff --git a/script/licenses.py b/script/licenses.py index 52e8fd0f3e7f88..358e0e03791544 100644 --- a/script/licenses.py +++ b/script/licenses.py @@ -125,7 +125,6 @@ def from_dict(cls, data: dict[str, str]) -> PackageDefinition: "aiocomelit", # https://github.com/chemelli74/aiocomelit/pull/138 "aioecowitt", # https://github.com/home-assistant-libs/aioecowitt/pull/180 "aiohappyeyeballs", # PSF-2.0 license - "aiohttp-fast-url-dispatcher", # https://github.com/bdraco/aiohttp-fast-url-dispatcher/pull/10 "aioopenexchangerates", # https://github.com/MartinHjelmare/aioopenexchangerates/pull/94 "aiooui", # https://github.com/Bluetooth-Devices/aiooui/pull/8 "aioruuvigateway", # https://github.com/akx/aioruuvigateway/pull/6 diff --git a/tests/components/websocket_api/test_http.py b/tests/components/websocket_api/test_http.py index 794dd410661db4..11665da11b46e3 100644 --- a/tests/components/websocket_api/test_http.py +++ b/tests/components/websocket_api/test_http.py @@ -5,7 +5,7 @@ from typing import Any, cast from unittest.mock import patch -from aiohttp import ServerDisconnectedError, WSMsgType, web +from aiohttp import WSMsgType, WSServerHandshakeError, web import pytest from homeassistant.components.websocket_api import ( @@ -374,7 +374,7 @@ async def test_prepare_fail( "homeassistant.components.websocket_api.http.web.WebSocketResponse.prepare", side_effect=(TimeoutError, web.WebSocketResponse.prepare), ), - pytest.raises(ServerDisconnectedError), + pytest.raises(WSServerHandshakeError), ): await hass_ws_client(hass)