Skip compressing HTTP JSON responses below 1 KiB (#175560)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Paulus Schoutsen
2026-07-03 23:02:32 +02:00
committed by GitHub
co-authored by Claude
parent 2293f51828
commit 631179dffe
4 changed files with 78 additions and 3 deletions
+5 -2
View File
@@ -46,6 +46,7 @@ from homeassistant.exceptions import (
Unauthorized,
)
from homeassistant.helpers import config_validation as cv, recorder, template
from homeassistant.helpers.http import MIN_COMPRESSED_RESPONSE_SIZE
from homeassistant.helpers.json import json_dumps, json_fragment
from homeassistant.helpers.service import async_get_all_descriptions
from homeassistant.helpers.typing import ConfigType
@@ -223,12 +224,14 @@ class APIStatesView(HomeAssistantView):
for state in hass.states.async_all()
if entity_perm(state.entity_id, POLICY_READ)
)
body = b"".join((b"[", b",".join(states), b"]"))
response = web.Response(
body=b"".join((b"[", b",".join(states), b"]")),
body=body,
content_type=CONTENT_TYPE_JSON,
zlib_executor_size=32768,
)
response.enable_compression()
if len(body) > MIN_COMPRESSED_RESPONSE_SIZE:
response.enable_compression()
return response
+6 -1
View File
@@ -27,6 +27,10 @@ from .json import find_paths_unserializable_data, json_bytes, json_dumps
_LOGGER = logging.getLogger(__name__)
# Responses smaller than this fit within a single network packet, so
# compressing them wastes event-loop CPU without reducing round-trips.
MIN_COMPRESSED_RESPONSE_SIZE: Final = 1024
type AllowCorsType = Callable[[AbstractRoute | AbstractResource], None]
KEY_AUTHENTICATED: Final = "ha_authenticated"
@@ -160,7 +164,8 @@ class HomeAssistantView:
headers=headers,
zlib_executor_size=32768,
)
response.enable_compression()
if len(msg) > MIN_COMPRESSED_RESPONSE_SIZE:
response.enable_compression()
return response
def json_message(
+27
View File
@@ -51,6 +51,33 @@ async def test_api_list_state_entities(
assert remote_data == local_data
@pytest.mark.parametrize(
("entity_count", "expect_compression"),
[
pytest.param(1, False, id="small-body-not-compressed"),
pytest.param(50, True, id="large-body-compressed"),
],
)
async def test_api_states_compression_threshold(
hass: HomeAssistant,
mock_api_client: TestClient,
entity_count: int,
expect_compression: bool,
) -> None:
"""Test that only state list responses above the size threshold are compressed."""
for i in range(entity_count):
hass.states.async_set(
f"test.entity_{i}", "on", {"friendly_name": f"Entity {i}"}
)
resp = await mock_api_client.get(
const.URL_API_STATES, headers={"Accept-Encoding": "gzip, deflate"}
)
assert resp.status == HTTPStatus.OK
assert ("Content-Encoding" in resp.headers) is expect_compression
async def test_api_get_state(hass: HomeAssistant, mock_api_client: TestClient) -> None:
"""Test if the debug interface allows us to get a state."""
hass.states.async_set("hello.world", "nice", {"attr": 1})
+40
View File
@@ -0,0 +1,40 @@
"""Tests for the HTTP helpers."""
from http import HTTPStatus
from aiohttp import web
import pytest
from homeassistant.helpers.http import MIN_COMPRESSED_RESPONSE_SIZE, HomeAssistantView
from tests.typing import ClientSessionGenerator
@pytest.mark.parametrize(
("body_size", "expect_compression"),
[
pytest.param(8, False, id="small-body-not-compressed"),
pytest.param(
MIN_COMPRESSED_RESPONSE_SIZE * 2, True, id="large-body-compressed"
),
],
)
@pytest.mark.usefixtures("socket_enabled")
async def test_json_response_compression_threshold(
aiohttp_client: ClientSessionGenerator,
body_size: int,
expect_compression: bool,
) -> None:
"""Test HomeAssistantView.json only compresses bodies above the threshold."""
async def handler(request: web.Request) -> web.Response:
return HomeAssistantView.json({"data": "x" * body_size})
app = web.Application()
app.router.add_get("/", handler)
client = await aiohttp_client(app)
resp = await client.get("/", headers={"Accept-Encoding": "gzip, deflate"})
assert resp.status == HTTPStatus.OK
assert ("Content-Encoding" in resp.headers) is expect_compression