diff --git a/tutordash/server/app.py b/tutordash/server/app.py index 443d6fd..d042771 100644 --- a/tutordash/server/app.py +++ b/tutordash/server/app.py @@ -9,9 +9,9 @@ import typing as t import aiofiles from quart import ( Quart, + make_response, render_template, request, - websocket, redirect, url_for, ) @@ -118,10 +118,7 @@ class TutorCli: """ Sets the stop flag, whic is monitored by all subprocess.Popen commands. """ - app.logger.info( - "Stopping Tutor command: %s...", - self.command, - ) + app.logger.info("Stopping Tutor command: %s...", self.command) self._stop_flag.set() async def iter_logs(self) -> t.AsyncGenerator[str, None]: @@ -273,11 +270,9 @@ class TutorCliPool: replaced by another one, previous logs are not deleted. New ones are simply appended. """ - while True: - if cls.INSTANCE: - async for log in cls.INSTANCE.iter_logs(): - yield log - await asyncio.sleep(SHORT_SLEEP_SECONDS) + while cls.INSTANCE: + async for log in cls.INSTANCE.iter_logs(): + yield log app = Quart( @@ -341,27 +336,46 @@ async def tutor_cli() -> WerkzeugResponse: # ["config", "printvalue", "POUAC"], # ["local", "launch", "--non-interactive"], ) - return redirect(url_for("tutor_logs")) + return redirect(url_for("tutor_cli_logs")) @app.post("/tutor/cli/stop") async def tutor_cli_stop() -> WerkzeugResponse: TutorCliPool.stop() - return redirect(url_for("tutor_logs")) + return redirect(url_for("tutor_cli_logs")) @app.get("/tutor/logs") -async def tutor_logs() -> str: - return await render_template("tutor_logs.html", **shared_template_context()) +async def tutor_cli_logs() -> str: + return await render_template("tutor_cli_logs.html", **shared_template_context()) -@app.websocket("/tutor/logs/stream") -async def tutor_logs_stream() -> None: - async for content in TutorCliPool.iter_logs(): - try: - await websocket.send(content) - except asyncio.CancelledError: - return +@app.get("/tutor/cli/logs/stream") +async def tutor_cli_logs_stream() -> None: + # Websockets were not working for us in dev mode, we were unable to stop the server + # as long as there were open connection. We only need single-direction + # communication, so we use server-sent events + # https://github.com/pallets/quart/issues/333 + # https://quart.palletsprojects.com/en/latest/how_to_guides/server_sent_events.html + async def send_events(): + while True: + # TODO this is again causing the stream to never stop... + async for data in TutorCliPool.iter_logs(): + event = f"data: {data}\nevent: logs\n" + # TODO encode one way or another to be able to send EOL characters and other weird chars + yield event.encode() + await asyncio.sleep(SHORT_SLEEP_SECONDS) + + response = await make_response( + send_events(), + { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + "Transfer-Encoding": "chunked", + }, + ) + response.timeout = None + return response def shared_template_context() -> dict[str, t.Any]: diff --git a/tutordash/server/templates/index.html b/tutordash/server/templates/index.html index 6dbd7d3..4b3877d 100644 --- a/tutordash/server/templates/index.html +++ b/tutordash/server/templates/index.html @@ -17,8 +17,8 @@ - - + + @@ -31,7 +31,7 @@
diff --git a/tutordash/server/templates/tutor_cli_logs.html b/tutordash/server/templates/tutor_cli_logs.html new file mode 100644 index 0000000..ab6db07 --- /dev/null +++ b/tutordash/server/templates/tutor_cli_logs.html @@ -0,0 +1,22 @@ +{% extends 'index.html' %} + +{% block workspace_header %}Tutor command logs{% endblock %} + +{% block workspace_content %} +

+{% endblock %}
+
+{% block scripts %}
+
+{% endblock %}
diff --git a/tutordash/server/templates/tutor_logs.html b/tutordash/server/templates/tutor_logs.html
deleted file mode 100644
index fe8dc29..0000000
--- a/tutordash/server/templates/tutor_logs.html
+++ /dev/null
@@ -1,20 +0,0 @@
-{% extends 'index.html' %}
-
-{% block workspace_header %}Tutor command logs{% endblock %}
-
-{% block workspace_content %}
-

-{% endblock %}
-
-{% block scripts %}
-
-{% endblock %}