I am trying to deploy my app to heroku. The app has a simple chatting system that uses Websockets and django channels.
When I test my app using python manage.py runserver the app behaves just as intended.
I tried deploying the app and all features work except for the chatting system.
Here is the error message I am getting in the Chrome Console:
layout.js:108 Mixed Content: The page at 'https://desolate-lowlands-74512.herokuapp.com/index' was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint 'ws://desolate-lowlands-74512.herokuapp.com/ws/chat/19/'. This request has been blocked; this endpoint must be available over WSS.
This is what I tried to fix it: I went from ws to wss I changed this:
const chatSocket = new WebSocket(
'ws://'
+ window.location.host
+ '/ws/chat/'
+ friendship_id
+ '/'
);
console.log(chatSocket)
to this:
const chatSocket = new WebSocket(
'wss://'
+ window.location.host
+ '/ws/chat/'
+ friendship_id
+ '/'
);
console.log(chatSocket)
with this change the websocket loads but the chatting system still doesn't work. The messages still don't get sent or received
This is the error message I get when opening the Chatbox:
layout.js:108 WebSocket connection to 'wss://desolate-lowlands-74512.herokuapp.com/ws/chat/19/' failed: Error during WebSocket handshake: Unexpected response code: 404
and this is the error message I get when I try to send the message:
WebSocket is already in CLOSING or CLOSED state.
Here is the code: This is my asgi.py file:
"""
ASGI config for DBSF project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
import social.routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DBSF.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(
social.routing.websocket_urlpatterns
)
),
})
here is routing.py
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<friendship_id>\w+)/$', consumers.ChatConsumer.as_asgi()),
]
Here's consumers.py
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
from .models import Message, Friendship, User
import datetime
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_name = self.scope['url_route']['kwargs']['friendship_id']
self.room_group_name = 'chat_%s' % self.room_name
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name,
self.channel_name
)
# Receive message from WebSocket
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
sender = text_data_json['sender']
receiver = text_data_json['receiver']
friendship_id = self.scope['url_route']['kwargs']['friendship_id']
message_to_save = Message(conversation=Friendship.objects.get(id=friendship_id), sender=User.objects.get(username=sender), receiver=User.objects.get(username=receiver), text=message, date_sent=datetime.datetime.now())
message_to_save.save()
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
'sender': sender,
'receiver': receiver,
'id': message_to_save.id
}
)
# Receive message from room group
def chat_message(self, event):
message = event['message']
sender = event['sender']
receiver = event['receiver']
id = event['id']
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message,
'sender': sender,
'receiver': receiver,
'id': id,
}))
Here is settings.py
"""
Django settings for DBSF project.
Generated by 'django-admin startproject' using Django 3.1.2.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""
from pathlib import Path
import django_heroku
import os
from dotenv import load_dotenv
load_dotenv()
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ['SECRET_KEY']
AUTH_USER_MODEL = 'social.User'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['desolate-lowlands-74512.herokuapp.com', 'localhost', '127.0.0.1']
# Application definition
INSTALLED_APPS = [
'channels',
'social',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'DBSF.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}
WSGI_APPLICATION = 'DBSF.wsgi.application'
ASGI_APPLICATION = 'DBSF.asgi.application'
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'EST'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_URL = '/static/'
MEDIA_ROOT= os.path.join(BASE_DIR, 'media/')
MEDIA_URL= "/media/"
here's wsgi.py
"""
WSGI config for DBSF project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DBSF.settings')
application = get_wsgi_application()
I assume the once I change from ws to wss the consumer can't connect. I think this will be an easy fix but I can't figure out what code i need to change. I suspect that it is something in asgy.py or routing.py
Please let me know if it is unclear what I am asking or if I need to show any other files
Hello, here is some steps that I find to myself to deploy similar mysite
app for heroku server. I used this libraries:
django==3.2.6
channels==3.0.4
python==3.9.6
You can read how to work with it at Tutorial . First of all, follow the installation and tutorial.
In your Procfile
you need to use daphne
which is installed with channels
library by default:
web: daphne -b 0.0.0.0 -p $PORT mysite.asgi:application
You don't need any workers if you use latest version of channels.
At heroku environment $PORT
replace with some port that it provide. You can provide it locally by using .env
file.
The last thing you need to replace ws://
with wss://
(see Errors and solutions) at room.html
.
settings
( mysite.settings
) and use asgi
application:INSTALLED_APPS = [
'channels',
'chat',
...
]
...
ASGI_APPLICATION = "mysite.asgi.application"
mysite.settings
).CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels.layers.InMemoryChannelLayer',
},
}
According Documentation you should use database for production, but for local environment you may use
channels.layers.InMemoryChannelLayer
.
asgi
server (not wsgi
) because you need asynchronous behaviour. Also, for deployment you should use daphne
instead of gunicorn
. daphne
is included in channels
library by default, so you don't need to install it manually. Basic run server will look like (Terminal):daphne -b 0.0.0.0 -p $PORT mysite.asgi:application
where $PORT
is specific port (for UNIX system is 5000). (That format used for heroku application, you can change it manually).
You may think about to use your server via wss protocol: replace ws://...
with wss://...
or use following template in your html ( chat/templates/chat/room.html
):
(window.location.protocol === 'https:' ? 'wss' : 'ws') + '://'
Hope this answer is useful for channels
with Django
.
It sounds like you are not properly setting the Procfile for your project. Because Channels apps need both a HTTP/WebSocket server and a backend channel consumer, the Procfile needs to define both of these types:
release: python manage.py migrate
web: daphne therapist_portal.asgi:application --port $PORT --bind 0.0.0.0
worker: python manage.py runworker channel_layer
After you do your initial deploy, you'll need to make sure both process types are running (Heroku will only start the web dyno by default, you can also change the dynos tiers according to your needs):
heroku ps:scale web=1:free worker=1:free
If you are still running into problems, you might want to change your asgy.py file to something like:
import os
import channels
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "therapist_portal.settings")
django.setup()
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
import social.routing
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(
social.routing.websocket_urlpatterns
)
),
})
This will ensure that Daphne will start up properly.
NOTE 1: I'm assuming you are using Django 3.*
and Channels 3.*
NOTE 2: Part of this answer came from the somewhat outdated docs from Heroku.
If you are using channels >=2, then you don't need any worker. All you need is to set your Procfile and settings properly.
release: python manage.py migrate
web: daphne <your-app>.asgi:application --port $PORT --bind 0.0.0.0 -v2
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [os.environ.get('REDIS_URL')],
},
},
}
Notice here that we don't have the extra 6379
port in the hosts above.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.