Jaime
Jaime

Reputation: 129

Running simple async chatroom. Works fine in dev. Fails in Heroku

I've made an async chatroom app for a django website based on the excellent tutorial provided by django on django channels with daphne and redis.

https://channels.readthedocs.io/en/stable/tutorial/index.html

The app works very nicely in dev with redis running on a container in docker.

The challenge arises when I try to run it after deploying to Heroku!

I installed Redis Cloud using their free subscription. It's active and as far as I can see working fine. There are no databases connecting to it.

I've pinged the redis process successfully from the heroku.cli.

I have used standard django logic to allow it to connect to channels via a setting that points to a local .env variable for dev and the config var set up automatically by Redis Cloud for production.

REDIS_URL = os.getenv("REDISCLOUD_URL", "redis://localhost:6379")

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [REDIS_URL],
        },
    },
}

URLs are controlled as they are in the tutorial example from a simple routing file:

from django.urls import re_path

from . import consumers

websocket_urlpatterns = [
    re_path(r"^ws/chat/(?P<room_name>\w+)/$", consumers.ChatConsumer.as_asgi()),
]

Of course, as soon as the user sends an async message, it fails, leaving a 404 message in the Heroku --tail that looks like this:

2025-02-16T19:51:41.383369+00:00 app[web.1]: 10.1.19.114 - - [16/Feb/2025:19:51:41 +0000] "GET /ws/chat/kitchen/ HTTP/1.1" 404 11248 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
2025-02-16T19:51:41.383718+00:00 heroku[router]: at=info method=GET path="/ws/chat/kitchen/" host=sparksync-test-baedaeaf485c.herokuapp.com request_id=98f3ad28-61d1-4926-b125-ebc67fa78cb4 fwd="79.210.183.19" dyno=web.1 connect=0ms service=2ms status=404 bytes=11552 protocol=https

In dev, the app runs using the usual python manage.py runserver command, but wsgi hands over immediately to asgi to run the app.

The procfile line is the usual

web: gunicorn dating_app.wsgi:application

Could the issue that gunicorn doesn't hand over to daphne? Even though python manage.py runserver works fine in dev?

If this could be the cause my problem in the deployed environment, how can I fix it, given that the standard heroku daphne call shown above crashes immediately.

Even if you don't have any answers to these questions, any tips on troubleshooting would be gratefully accepted.

Upvotes: 0

Views: 33

Answers (1)

Jaime
Jaime

Reputation: 129

As it turned out, my guess that gunicorn wasn't handing over to daphne in the deployed environment turned out to be true!

The tutorial (https://channels.readthedocs.io/en/latest/tutorial/part_1.html) tells us that, with the configuration it suggests (i.e. with Daphne and channels_redis both installed and the setting ASGI_APPLICATION = "[app_name].asgi.application" in place in your project, if you start up the app using the python manage.py runserver command, wsgi hands over to asgi, which can use daphne (which can handle async websockets) instead of gunicorn (which can't).

My understanding now it that when you deploy your functioning app on heroku putting the standard startup string in your project's Heroku Procfile (´´web: gunicorn [app_name].wsgi:application``) no handover to Daphne takes place (naturally enough, in retrospect).

The Procfile string I ended up using was web: daphne -b 0.0.0.0 -p $PORT [app_name].asgi:application, which did the trick. There are probably lots of other possible strings (I've just checked and web: daphne [app_name].asgi:application works for me too, for example).

A note on Redis:

The tutorial gives you pretty full instructions on how to get the dev version to work asynchronously with websockets: it recommends you install Docker and then create a container on it running an instance of Redis.

Heroku apparently has docker support too, but the way I resolved the need to provide Daphne with a Redis instance in Heroku-hosted production was to install the Redis Cloud addon (the free plan does the trick nicely) on Heroku's Resources tab and put it to work. Redis Cloud automatically creates and populates a config var called REDISCLOUD_URL. Once you have Redis Cloud up and running, you'll see the config var on your Heroku Settings tab.

Adjust your project settings so that CHANNEL_LAYERS is configured to make its default host point to REDISCLOUD_URL and you're all set!

My CHANNEL_LAYERS logic looks like this:

REDIS_URL = os.getenv("REDISCLOUD_URL", "redis://localhost:6379")

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [REDIS_URL],
        },
    },
}

In dev, the above setting points the app to my .env value for REDISCLOUD_URL (redis://localhost:6379) if there is an .env file present, and to the same redis://localhost:6379 string anyway if .env isn't there. In production, it picks up Heroku's automatically created config var.

Upvotes: 0

Related Questions