Search code examples
pythondjangogitherokudjango-channels

django channels works locally but fails on server


Django channels fails to connect in production

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


Solution

  • 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.