async def stream_response(self, send) -> None: await send({ "type": "http.response.start", "status": self.status_code, "headers": self.raw_headers, }) self._ping_task = self._loop.create_task( self._ping(send)) # type: ignore async for data in self.body_iterator: if isinstance(data, dict): chunk = ServerSentEvent(**data).encode() else: chunk = ServerSentEvent(str(data), sep=self.sep).encode() _log.debug(f"chunk: {chunk.decode()}") await send({ "type": "http.response.body", "body": chunk, "more_body": True }) await send({ "type": "http.response.body", "body": b"", "more_body": False })
async def wait(self) -> None: """EventSourceResponse object is used for streaming data to the client, this method returns future, so we can wait until connection will be closed or other task explicitly call ``stop_streaming`` method. """ if self._ping_task is None: raise RuntimeError("Response is not started") with contextlib.suppress(asyncio.CancelledError): await self._ping_task _log.debug(f"SSE ping stopped.") # pragma: no cover
async def _ping(self, send: Send) -> None: # Legacy proxy servers are known to, in certain cases, drop HTTP connections after a short timeout. # To protect against such proxy servers, authors can send a custom (ping) event # every 15 seconds or so. # Alternatively one can send periodically a comment line # (one starting with a ':' character) while self.active: await asyncio.sleep(self._ping_interval) ping = ServerSentEvent(datetime.utcnow(), event="ping").encode() _log.debug(f"ping: {ping.decode()}") await send({"type": "http.response.body", "body": ping, "more_body": True})
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: await run_until_first_complete( (self.stream_response, {"send": send}), (self.listen_for_disconnect, {"receive": receive}), ) self.stop_streaming() await self.wait() _log.debug(f"streaming stopped.") if self.background is not None: # pragma: no cover, tested in StreamResponse await self.background()
async def listen_for_disconnect(receive: Receive) -> None: while True: message = await receive() if message["type"] == "http.disconnect": _log.debug(f"Got event: http.disconnect. Stop streaming.") break
from uvicorn.config import logger as _log # TODO: remove from uvicorn.main import Server original_handler = Server.handle_exit Server.handle_exit = AppStatus.handle_exit def unpatch_uvicorn_signal_handler(): """restores original signal-handler and rolls back monkey-patching. Normally this should not be necessary. """ Server.handle_exit = original_handler except ModuleNotFoundError as e: _log = logging.getLogger(__name__) # logging.basicConfig(level=logging.INFO) _log.debug(f"Uvicorn not used, falling back to python standard logging.") class SseState(enum.Enum): CONNECTING = 0 OPENED = 1 CLOSED = 2 class ServerSentEvent: def __init__( self, data: Any, *, event: Optional[str] = None, id: Optional[int] = None,