i'm working on a chat application using django Channels
and reconnecting-web-socket the app works fine locally but when deployed to heroku
the site works fine but the web socket closes before connection is established and sometimes it works fine.
it tries to connect and says websocket open and established
2020-11-21T11:45:14.483101+00:00 app[web.1]: 10.38.231.42:12792 - - [21/Nov/2020:11:45:14] "WSCONNECTING /ws/chat/" - -
2020-11-21T11:45:14.483267+00:00 app[web.1]: 2020-11-21 11:45:14,483 DEBUG Upgraded connection ['10.38.231.42', 12792] to WebSocket
2020-11-21T11:45:14.486434+00:00 app[web.1]: 2020-11-21 11:45:14.486 UTC [24] LOG C-0x5564d6f000a0: db1/kutxkggzwbkjsp@127.0.0.1:35034 login attempt: db=db1 user=kutxkggzwbkjsp tls=no
2020-11-21T11:45:14.493134+00:00 app[web.1]: 2020-11-21 11:45:14,492 DEBUG WebSocket ['10.38.231.42', 12792] open and established
2020-11-21T11:45:14.493228+00:00 app[web.1]: 10.38.231.42:12792 - - [21/Nov/2020:11:45:14] "WSCONNECT /ws/chat/" - -
2020-11-21T11:45:14.493405+00:00 app[web.1]: 2020-11-21 11:45:14,493 DEBUG WebSocket ['10.38.231.42', 12792] accepted by application
then it tries to connect to the redis db
2020-11-21T11:45:14.494880+00:00 app[web.1]: 2020-11-21 11:45:14,494 DEBUG Parsed Redis URI ('redis-10596.c8.us-east-1-2.ec2.cloud.redislabs.com', 10596)
2020-11-21T11:45:14.495020+00:00 app[web.1]: 2020-11-21 11:45:14,494 DEBUG Creating tcp connection to ('redis-10596.c8.us-east-1-2.ec2.cloud.redislabs.com', 10596)
but right after that the socket closes
2020-11-21T11:45:17.133433+00:00 heroku[router]: at=info method=GET path="/ws/chat/" host=tolk-project.herokuapp.com request_id=41ed7690-5f91-4238-9792-01f52c5f65a1 fwd="102.143.218.204" dyno=web.1 connect=0ms service=2654ms status=101 bytes=145 protocol=http
2020-11-21T11:45:17.134597+00:00 app[web.1]: 2020-11-21 11:45:17,134 DEBUG WebSocket closed for ['10.38.231.42', 12792]
2020-11-21T11:45:17.135119+00:00 app[web.1]: 10.38.231.42:12792 - - [21/Nov/2020:11:45:17] "WSDISCONNECT /ws/chat/" - -
2020-11-21T11:45:17.419219+00:00 app[web.1]: 2020-11-21 11:45:17,419 DEBUG Cancelling waiter (<Future cancelled>, [None, None])
2020-11-21T11:45:17.419377+00:00 app[web.1]: 2020-11-21 11:45:17,419 DEBUG Waiter future is already done <Future cancelled>
what is causing the connection to close immediately ?
my requirements.txt file:
dj-config-url
django==2.2.7
pillow==5.2.0
twisted==19.7.0
asgiref==3.2.2
channels==2.2.0
channels-redis==2.4.0
daphne==2.3.0
django-heroku==0.3.1
django-redis==4.10.0
django-rest-framework==0.1.0
django-storages==1.9.1
djangorestframework==3.10.3
dropbox==9.4.0
psycopg2==2.8.5
setuptools==40.8.0
whitenoise==4.1.2
twisted==19.7.0
my asgi.py file:
import os
import django
from channels.routing import get_default_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Tolk.settings")
os.environ['ASGI_THREADS'] = "19"
django.setup()
application = get_default_application()
my procfile:
web: bin/start-pgbouncer-stunnel daphne Tolk.asgi:application --port $PORT --bind 0.0.0.0 -v2
my settings.py file:
"""
Django settings for Tolk project.
Generated by 'django-admin startproject' using Django 2.2.1.
For more information on this file, see
https://docs.djangoproject.com/en/2.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import os
import dj_database_url
import django_heroku
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
# hosts allowed for use with this application
ALLOWED_HOSTS = [
'http://tolk-project.herokuapp.com/',
]
# Application definition
INSTALLED_APPS = [
# built-in
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# third party
'channels',
'rest_framework',
# local
'chat.apps.ChatConfig',
'accounts.apps.AccountsConfig',
'Authentication.apps.AuthenticationConfig',
'web_interface.apps.WebInterfaceConfig',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'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',
'whitenoise.middleware.WhiteNoiseMiddleware',
]
ROOT_URLCONF = 'Tolk.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'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',
],
},
},
]
WSGI_APPLICATION = 'Tolk.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {'default': dj_database_url.config(ssl_require=False)}
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files settings
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_URL = '/static/'
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Extra places for collectstatic to find static files.
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'assets'),
)
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
# Authentication Settings
# authentication user model
# https://docs.djangoproject.com/en/2.2/topics/auth/customizing.html
AUTH_USER_MODEL = 'accounts.User'
# Password validation
# https://docs.djangoproject.com/en/2.2/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',
},
]
# *****Channels settings******
# root application
ASGI_APPLICATION = "Tolk.routing.application"
# channel layers backends
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [
f"redis://:{os.environ.get('REDIS_PASSWORD_1')}@{os.environ.get('REDIS_HOST_1')}",
],
},
},
}
# Redis Cache
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": f"redis://{os.environ.get('REDIS_HOST_2')}",
'TIMEOUT': 60 * 60 * 24,
"OPTIONS": {
'PASSWORD': os.environ.get('REDIS_PASSWORD_2'),
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
"KEY_PREFIX": "chat"
},
}
# rest framework
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
'rest_framework.renderers.AdminRenderer',
]
}
# **** heroku settings ****
# Activate Django-Heroku.
django_heroku.settings(locals())
# delete ssl mode
del DATABASES['default']['OPTIONS']['sslmode']
# **** dropbox settings ****
DEFAULT_FILE_STORAGE = 'storages.backends.dropbox.DropBoxStorage'
DROPBOX_OAUTH2_TOKEN = os.environ.get('DROPBOX_OAUTH2_TOKEN')
my connect function in consumers.py
# used to receive, send messages and keep
# track of online and offline users
class ChatConsumer(AsyncJsonWebsocketConsumer):
connected_user = None
async def connect(self) -> None:
"""
check id user is authenticated if user is
authenticated the connect to channel and sets up
a cache db
"""
if self.scope['user'].is_authenticated:
# accept user
await self.accept()
# to keep track of user
self.connected_user = self.scope['user']
cache = caches['default']
cache.set(f"{self.connected_user.email}", "active") # cache the user status
# add your self to your own friend list
await self.channel_layer.group_add(
f'{self.connected_user.id}_friends',
self.channel_name
)
# loop the connected user's friends list and
# add yourself to the each friend's friend list
for friend in self.connected_user.contact.friends.all():
group_name = f'{friend.user.id}_friends'
await self.channel_layer.group_add(
group_name,
self.channel_name
)
# create a new dict that contains the conversation the user joins
# every time they connected
joined_conversations = dict()
cache.set(f"{self.connected_user.email}_conversations", joined_conversations)
# close cache
cache.close()
# send all notifications that the user has
await self.send_notifications()
else:
await self.close() # reject connection
Note: i also tested this across different browsers
the problem was in reconnecting-web-socket.min.js changing
let socket = new ReconnectingWebSocket(ws_path)
to
let socket = new WebSocket(ws_path)
fixed the problem.