I'm trying to get a simple Django Web App with WebSockets going, and can't get the routing to work. I have a basic Consumer, ProgressConsumer
, that is used as base for two other Consumers.
class ProgressConsumer(WebsocketConsumer):
max_anims = None
def connect(self):
# self.set_max_anims()
self.log_cache = ""
print("Websocket connected.")
if (not self.max_anims):
raise NotSetError("max_anims not set")
self.room_group_name = "progress"
self.accept()
self.start_progress_update()
def start_progress_update(self):
cached_progress = 0
progress = 0
while progress != 100.0:
time.sleep(1)
if (updates := self.get_log_updates()) == "":
continue
if "Error" in updates:
self.send(json.dumps({"error": updates}))
self.clean_log()
self.close(3000)
return
if (progress := self.calculate_progress(updates)) == cached_progress:
continue
self.send(json.dumps({
'type': 'progress',
'message': progress,
}))
cached_progress = progress
def disconnect(self, close_code):
print("Websocket disconnected. Code: ", close_code)
def get_log_updates(self) -> str:
if self.log_cache == self.get_log_cache():
return ""
s1 = set(self.log_cache.split("\n"))
s2 = set(self.get_log_cache().split("\n"))
self.log_cache = self.get_log_cache()
return str(s1.symmetric_difference(s2))
def calculate_progress(self, difference: str) -> float:
if not "Animation" in difference:
return 0.0
animation_id = float(re.search("Animation (\d+)", difference).group(1))
return ((animation_id + 1) / self.max_anims) * 100.0
def clean_log(self):
raise NotImplementedError("clean_log not implemented")
def get_log_cache(self) -> str:
raise NotImplementedError("get_log_cache not implemented")
The two other consumers are:
class FunctionImagesProgressConsumer(ProgressConsumer):
max_anims = 4
def clean_log(self):
with open("function_images/project_files/logs/_ComplexFunctionScene.log", "w") as f:
f.write("")
def get_log_cache(self) -> str:
with open("function_images/project_files/logs/_ComplexFunctionScene.log", "r") as f:
return f.read()
class AnalysisProgressConsumer(ProgressConsumer):
max_anims = 80
def clean_log(self):
with open("analysis_generator/project_files/logs/_AnalysisScene.log", "w") as f:
f.write("")
def get_log_cache(self) -> str:
with open("analysis_generator/project_files/logs/_AnalysisScene.log", "r") as f:
return f.read()
These two consumers are used in different apps. One app is under the URL http://127.0.0.1:8000/function_images/
, the other one http://127.0.0.1:8000/analysis_generator/
.
Now I'm trying to route the correct consumer based on what site the user is on. This is my routing.py:
from django.urls import re_path
from function_images.consumers import FunctionImagesProgressConsumer
from analysis_generator.consumers import AnalysisProgressConsumer
websocket_urlpatterns = [
re_path(r"function_images/ws/progress", FunctionImagesProgressConsumer.as_asgi()),
re_path(r"analysis_generator/ws/progress", AnalysisProgressConsumer.as_asgi()),
]
and asgi.py
:
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from .routing import websocket_urlpatterns
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dq_playground.settings')
django_asgi_api = get_asgi_application()
application = ProtocolTypeRouter({
"http": django_asgi_api,
"websocket": AuthMiddlewareStack(
URLRouter(
websocket_urlpatterns
)
)
})
However, when I run this, and attempt to open the websocket, this error occurs:
File "C:\Users\damix\AppData\Local\Programs\Python\Python310\lib\site-packages\channels\staticfiles.py", line 44, in __call__
return await self.application(scope, receive, send)
File "C:\Users\damix\AppData\Local\Programs\Python\Python310\lib\site-packages\channels\routing.py", line 71, in __call__
return await application(scope, receive, send)
File "C:\Users\damix\AppData\Local\Programs\Python\Python310\lib\site-packages\channels\sessions.py", line 47, in __call__
return await self.inner(dict(scope, cookies=cookies), receive, send)
File "C:\Users\damix\AppData\Local\Programs\Python\Python310\lib\site-packages\channels\sessions.py", line 263, in __call__
return await self.inner(wrapper.scope, receive, wrapper.send)
File "C:\Users\damix\AppData\Local\Programs\Python\Python310\lib\site-packages\channels\auth.py", line 185, in __call__
return await super().__call__(scope, receive, send)
File "C:\Users\damix\AppData\Local\Programs\Python\Python310\lib\site-packages\channels\middleware.py", line 26, in __call__
return await self.inner(scope, receive, send)
File "C:\Users\damix\AppData\Local\Programs\Python\Python310\lib\site-packages\channels\routing.py", line 168, in __call__
raise ValueError("No route found for path %r." % path)
ValueError: No route found for path 'ws/progress/'.
And here's the frontend:
const establishSocketConnection = () => {
const socket_url = `ws://${window.location.host}/ws/progress/`;
let socket = new WebSocket(socket_url);
socket.onmessage = (event: MessageEvent<any>) => {
const data = JSON.parse(event.data);
if (data.type != "progress") {
return;
}
const progress: number = +data.message;
console.log(`Progress: ${progress}`);
updateProgressBar(progress);
}
socket.onclose = (): void => {
console.log("Socket connection closed");
}
socket.onerror = (error: Event): void => {
console.log("Socket error: " + error);
}
return socket;
}
I'm not sure how to do the routing differently, as this should work in my eyes.
In the frontend Javascript, you have to mention the django app name with full url like this. Simply you can call the ws request as below.
const socket_url_1 = `ws://${window.location.host}/analysis_generator/ws/progress/`;
const socket_url_2 = `ws://${window.location.host}/function_images/ws/progress/`;