'Django Cookiecutter Channels3 - connection opens, doesn't send
I started a a project with Django Cookiecutter w/ Docker: https://cookiecutter-django.readthedocs.io/en/latest/
I'm trying to add Channels and follow the tutorial in their docs: https://channels.readthedocs.io/en/stable/tutorial
I added Channels 3.0.4 to requirements.txt, rebuilt the docker container.
I added channels to settings/base.py, and this:
WSGI_APPLICATION = "config.wsgi.application"
ASGI_APPLICATION = "config.asgi.application"
I updated my config/asgi.py file:
import os
import sys
from pathlib import Path
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from the_pub.chat import routing
ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent
sys.path.append(str(ROOT_DIR / "the_pub"))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
django_application = get_asgi_application()
from config.websocket import websocket_application # noqa isort:skip
application = ProtocolTypeRouter({
"https": django_application,
"websocket": AuthMiddlewareStack(
URLRouter(
routing.websocket_urlpatterns
)
),
})
async def application(scope, receive, send):
if scope["type"] == "http":
await django_application(scope, receive, send)
elif scope["type"] == "websocket":
await websocket_application(scope, receive, send)
else:
raise NotImplementedError(f"Unknown scope type {scope['type']}")
created a config/websocket.io file
async def websocket_application(scope, receive, send):
while True:
event = await receive()
if event["type"] == "websocket.connect":
await send({"type": "websocket.accept"})
if event["type"] == "websocket.disconnect":
break
if event["type"] == "websocket.receive":
if event["text"] == "ping":
await send({"type": "websocket.send", "text": "pong!"})
views:
# chat/views.py
from django.shortcuts import render
def index(request):
return render(request, 'chat/index.html')
def index(request):
return render(request, 'chat/index.html', {})
def room(request, room_name):
return render(request, 'chat/room.html', {
'room_name': room_name
})
routing:
# chat/routing.py
from django.urls import re_path
from the_pub.chat import consumers
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
]
chat/urls:
# chat/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('<str:room_name>/', views.room, name='room'),
]
consumer:
# chat/consumers.py
import json
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.accept()
def disconnect(self, close_code):
pass
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
self.send(text_data=json.dumps({
'message': message
}))
app.py
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class ChatConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'the_pub.chat'
verbose_name= _("Chat")
def ready(self):
try:
import the_pub.users.signals # noqa F401
except ImportError:
pass
template:
<!-- chat/templates/chat/room.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Chat Room</title>
</head>
<body>
<textarea id="chat-log" cols="100" rows="20"></textarea><br>
<input id="chat-message-input" type="text" size="100"><br>
<input id="chat-message-submit" type="button" value="Send">
{{ room_name|json_script:"room-name" }}
<script>
const roomName = JSON.parse(document.getElementById('room-name').textContent);
const chatSocket = new WebSocket(
'ws://'
+ window.location.host
+ '/ws/chat/'
+ roomName
+ '/'
);
chatSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
document.querySelector('#chat-log').value += (data.message + '\n');
console.log(data);
};
chatSocket.onclose = function(e) {
console.error('Chat socket closed unexpectedly');
};
document.querySelector('#chat-message-input').focus();
document.querySelector('#chat-message-input').onkeyup = function(e) {
if (e.keyCode === 13) { // enter, return
document.querySelector('#chat-message-submit').click();
}
};
document.querySelector('#chat-message-submit').onclick = function(e) {
const messageInputDom = document.querySelector('#chat-message-input');
const message = messageInputDom.value;
chatSocket.send(JSON.stringify({
'message': message
}));
messageInputDom.value = '';
};
</script>
</body>
</html>
When I test the websocket in a chrome plugin I can send messages and it logs. When I hit send on the form it does nothing. no console alerts, nothing in docker logs. All it does is clear the text in the text box. I didn't think a third party could check the socket because I wrapped it in an authentication layer, but it's the opposite, my app acts like the javascript to send the message to the socket doesn't exist.
when you install Channels, it says to do 'pip -m install -U channels'. I added channels to the requirements.txt base file and let django cookiecutter run install with the rest of the libraries. did this break it?
Also, I'm running this project has it was set up by cookiecutter, which I guess is wsgi. Is it even possible to use both wsgi and asgi like this or should I be looking at how to run the whole site on asgi?
I get an error in the console "DevTools failed to load source map: Could not load content for /requestProvider.js.map. I normally ignore these errors but this seams suspiciously related to the socket.send() function on triggering an .onmessage.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
