Source code for litestar_vite.ipc._tcp

"""HTTP transport for communicating with Vite's development SSR middleware."""

from typing import Any, cast

import anyio
import httpx2

from litestar_vite.ipc._base import BaseIPCTransport, IPCError, IPCTimeoutError

__all__ = ("TCPStreamIPCTransport",)


[docs] class TCPStreamIPCTransport(BaseIPCTransport): """Send development SSR RPC requests through a pooled HTTP client.""" __slots__ = ("_client", "_host", "_path", "_port", "_scheme", "_url")
[docs] def __init__( self, host: str = "127.0.0.1", port: int = 5173, path: str = "/__litestar_ssr__", *, scheme: str = "http" ) -> None: """Configure the Vite SSR endpoint, including HTTPS when enabled.""" if scheme not in {"http", "https"}: msg = "SSR transport scheme must be http or https" raise ValueError(msg) self._host = host self._port = port self._path = path if path.startswith("/") else f"/{path}" self._scheme = scheme self._url = httpx2.URL(scheme=scheme, host=host.strip("[]"), port=port, path=self._path) self._client: httpx2.AsyncClient | None = None
@property def host(self) -> str: """Return the target hostname or IP address.""" return self._host @property def port(self) -> int: """Return the target TCP port.""" return self._port @property def path(self) -> str: """Return the configured HTTP endpoint path.""" return self._path @property def scheme(self) -> str: """Return the endpoint protocol.""" return self._scheme @property def is_running(self) -> bool: """Return True because connections are established lazily on the next request.""" return True
[docs] async def start(self) -> None: """Initialize the client connection pool if needed.""" if self._client is None or self._client.is_closed: self._client = httpx2.AsyncClient(trust_env=False)
[docs] async def close(self) -> None: """Release pooled connections.""" if self._client is not None: await self._client.aclose() self._client = None
[docs] async def send_request(self, payload: dict[str, Any], timeout: float = 10.0) -> dict[str, Any]: """Send an RPC request within a total deadline and validate its response. Raises: IPCTimeoutError: The request exceeded its total deadline. IPCError: The connection failed or the server returned an invalid response. """ try: with anyio.fail_after(timeout): await self.start() client = cast("httpx2.AsyncClient", self._client) response = await client.post(self._url, json=payload, timeout=timeout) if response.status_code != 200: msg = f"Upstream SSR server returned HTTP {response.status_code}: {response.text}" raise IPCError(msg) try: result = response.json() except ValueError as exc: msg = "SSR server returned invalid JSON" raise IPCError(msg) from exc if not isinstance(result, dict) or ("result" not in result and "error" not in result): msg = "SSR server response must be an object containing result or error" raise IPCError(msg) return cast("dict[str, Any]", result) except (TimeoutError, httpx2.TimeoutException) as exc: msg = f"TCP request to {self._host}:{self._port} timed out after {timeout} seconds" raise IPCTimeoutError(msg) from exc except httpx2.HTTPError as exc: msg = f"TCP connection error to {self._host}:{self._port}: {exc}" raise IPCError(msg) from exc