Avoid bytes to string to bytes conversion in websocket api (#108139)
This commit is contained in:
parent
ad35113e86
commit
60ab360fe7
16 changed files with 137 additions and 93 deletions
|
@ -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"
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue