Avoid bytes to string to bytes conversion in websocket api (#108139)

This commit is contained in:
J. Nick Koston 2024-01-16 10:37:34 -10:00 committed by GitHub
parent ad35113e86
commit 60ab360fe7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 137 additions and 93 deletions

View file

@ -16,7 +16,11 @@ from homeassistant.const import (
)
from homeassistant.core import Event, State
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.json import JSON_DUMP, find_paths_unserializable_data
from homeassistant.helpers.json import (
JSON_DUMP,
find_paths_unserializable_data,
json_bytes,
)
from homeassistant.util.json import format_unserializable_data
from . import const
@ -44,7 +48,7 @@ BASE_ERROR_MESSAGE = {
"success": False,
}
INVALID_JSON_PARTIAL_MESSAGE = JSON_DUMP(
INVALID_JSON_PARTIAL_MESSAGE = json_bytes(
{
**BASE_ERROR_MESSAGE,
"error": {
@ -60,9 +64,17 @@ def result_message(iden: int, result: Any = None) -> dict[str, Any]:
return {"id": iden, "type": const.TYPE_RESULT, "success": True, "result": result}
def construct_result_message(iden: int, payload: str) -> str:
def construct_result_message(iden: int, payload: bytes) -> bytes:
"""Construct a success result message JSON."""
return f'{{"id":{iden},"type":"result","success":true,"result":{payload}}}'
return b"".join(
(
b'{"id":',
str(iden).encode(),
b',"type":"result","success":true,"result":',
payload,
b"}",
)
)
def error_message(
@ -96,7 +108,7 @@ def event_message(iden: int, event: Any) -> dict[str, Any]:
return {"id": iden, "type": "event", "event": event}
def cached_event_message(iden: int, event: Event) -> str:
def cached_event_message(iden: int, event: Event) -> bytes:
"""Return an event message.
Serialize to json once per message.
@ -105,23 +117,30 @@ def cached_event_message(iden: int, event: Event) -> str:
all getting many of the same events (mostly state changed)
we can avoid serializing the same data for each connection.
"""
return f'{_partial_cached_event_message(event)[:-1]},"id":{iden}}}'
return b"".join(
(
_partial_cached_event_message(event)[:-1],
b',"id":',
str(iden).encode(),
b"}",
)
)
@lru_cache(maxsize=128)
def _partial_cached_event_message(event: Event) -> str:
def _partial_cached_event_message(event: Event) -> bytes:
"""Cache and serialize the event to json.
The message is constructed without the id which appended
in cached_event_message.
"""
return (
_message_to_json_or_none({"type": "event", "event": event.json_fragment})
_message_to_json_bytes_or_none({"type": "event", "event": event.json_fragment})
or INVALID_JSON_PARTIAL_MESSAGE
)
def cached_state_diff_message(iden: int, event: Event) -> str:
def cached_state_diff_message(iden: int, event: Event) -> bytes:
"""Return an event message.
Serialize to json once per message.
@ -130,18 +149,27 @@ def cached_state_diff_message(iden: int, event: Event) -> str:
all getting many of the same events (mostly state changed)
we can avoid serializing the same data for each connection.
"""
return f'{_partial_cached_state_diff_message(event)[:-1]},"id":{iden}}}'
return b"".join(
(
_partial_cached_state_diff_message(event)[:-1],
b',"id":',
str(iden).encode(),
b"}",
)
)
@lru_cache(maxsize=128)
def _partial_cached_state_diff_message(event: Event) -> str:
def _partial_cached_state_diff_message(event: Event) -> bytes:
"""Cache and serialize the event to json.
The message is constructed without the id which
will be appended in cached_state_diff_message
"""
return (
_message_to_json_or_none({"type": "event", "event": _state_diff_event(event)})
_message_to_json_bytes_or_none(
{"type": "event", "event": _state_diff_event(event)}
)
or INVALID_JSON_PARTIAL_MESSAGE
)
@ -212,10 +240,10 @@ def _state_diff(
return {ENTITY_EVENT_CHANGE: {new_state.entity_id: diff}}
def _message_to_json_or_none(message: dict[str, Any]) -> str | None:
def _message_to_json_bytes_or_none(message: dict[str, Any]) -> bytes | None:
"""Serialize a websocket message to json or return None."""
try:
return JSON_DUMP(message)
return json_bytes(message)
except (ValueError, TypeError):
_LOGGER.error(
"Unable to serialize to JSON. Bad data found at %s",
@ -226,9 +254,9 @@ def _message_to_json_or_none(message: dict[str, Any]) -> str | None:
return None
def message_to_json(message: dict[str, Any]) -> str:
def message_to_json_bytes(message: dict[str, Any]) -> bytes:
"""Serialize a websocket message to json or return an error."""
return _message_to_json_or_none(message) or JSON_DUMP(
return _message_to_json_bytes_or_none(message) or json_bytes(
error_message(
message["id"], const.ERR_UNKNOWN_ERROR, "Invalid JSON in response"
)