120 lines
3.9 KiB
Python
120 lines
3.9 KiB
Python
"""
|
|
method to create, serialize and deserialize DataResponse message
|
|
"""
|
|
|
|
import base64
|
|
import re
|
|
from io import BytesIO
|
|
from amqp.model.model import DataResponse
|
|
from amqp.model.snowflake_id import SnowflakeId
|
|
from amqp.adapter.serializer import parse_map_string, map_as_string
|
|
|
|
|
|
class DataResponseFactory:
|
|
MAGIC_BYTE_HTTP_REQUEST = "A"
|
|
|
|
@staticmethod
|
|
def create_async_response_message(_id: SnowflakeId) -> DataResponse:
|
|
"""
|
|
Helper method to create standard OK response.
|
|
:param _id: ID
|
|
:return: Response message
|
|
"""
|
|
return DataResponse(
|
|
str(_id), 200, "application/text", "OK".encode("utf-8"), "", "", {}
|
|
)
|
|
|
|
@staticmethod
|
|
def of(id: SnowflakeId, response_code: int, content_type: str, body: bytes, trace_info: dict[str,str]) -> DataResponse:
|
|
"""
|
|
Create the DataResponse message supplying all key values
|
|
:param id: SnowflakeID
|
|
:param response_code: HTTP response code
|
|
:param content_type: MIME content type
|
|
:param body: payload
|
|
:param trace_info: Jaeger trace info (id)
|
|
:return: DataResponseMessage object
|
|
"""
|
|
return DataResponse(
|
|
id=id.as_string(),
|
|
response_code=response_code,
|
|
content_type=content_type,
|
|
response=body,
|
|
error=None,
|
|
error_cause=None,
|
|
trace_info=trace_info
|
|
)
|
|
|
|
@staticmethod
|
|
def serialize(response: DataResponse) -> bytes:
|
|
"""
|
|
Serialize the message to byte array (to be sent over RabbitMQ)
|
|
:param response: Object to serialize
|
|
:return: byte array as serialized message
|
|
"""
|
|
id_bytes = response.id.encode('utf-8')
|
|
prolog = (
|
|
f"|r={response.response_code}|c={response.content_type}|e={response.error}|f={response.error_cause}|t={{{map_as_string(response.trace_info)}}}|b="
|
|
).encode('utf-8')
|
|
header_length = 1 + len(id_bytes) + len(prolog)
|
|
prolog_len = header_length.to_bytes(2, byteorder='big')
|
|
msg_length = 2 + header_length + len(response.response)
|
|
with BytesIO(bytearray(msg_length)) as baos:
|
|
baos.write(prolog_len)
|
|
baos.write(DataResponseFactory.MAGIC_BYTE_HTTP_REQUEST.encode('utf-8'))
|
|
baos.write(id_bytes)
|
|
baos.write(prolog)
|
|
baos.write(base64.b64encode(response.response))
|
|
return baos.getvalue()
|
|
|
|
@staticmethod
|
|
def from_bytes(input_bytes: bytes) -> DataResponse:
|
|
"""
|
|
Buil;ds the DataResponse object from its serialized form
|
|
:param input_bytes: byte array - serialized response
|
|
:return: DataResponse object
|
|
"""
|
|
prolog_len = (input_bytes[0] << 8) + input_bytes[1]
|
|
metadata = input_bytes[2:prolog_len + 2].decode()
|
|
|
|
pattern = re.compile(
|
|
f"{DataResponseFactory.MAGIC_BYTE_HTTP_REQUEST}"
|
|
r"([0-9A-Fa-f]+)\|"
|
|
r"r=([^|]*)\|"
|
|
r"c=([^|]*)\|"
|
|
r"e=([^|]*)\|"
|
|
r"f=([^|]*)\|"
|
|
r"t=(\{[^}]*\})\|"
|
|
r"b="
|
|
)
|
|
match = pattern.match(metadata)
|
|
if not match:
|
|
raise ValueError(
|
|
f"DataResponse format does not match expected format. "
|
|
f"Input string [{metadata}] does not match pattern: {pattern.pattern}"
|
|
)
|
|
|
|
id_str = match.group(1)
|
|
response_code = int(match.group(2))
|
|
content_type = match.group(3)
|
|
error = match.group(4)
|
|
error_cause = match.group(5)
|
|
trace_info_str = match.group(6)
|
|
body_b64 = input_bytes[prolog_len + 2:]
|
|
|
|
trace_info = parse_map_string(trace_info_str)
|
|
|
|
return DataResponse(
|
|
id_str,
|
|
response_code,
|
|
content_type,
|
|
base64.b64decode(body_b64),
|
|
error,
|
|
error_cause,
|
|
trace_info,
|
|
)
|
|
|
|
|
|
# Ensure backward compatibility
|
|
AMQResponseFactory = DataResponseFactory
|