Nginx Issues with Daphne connecting to websocket

Hello,

I am facing issues using Django Channels with Daphne with Nginx. The below daphne command line works fine wherein I can connect to both HTTP and websocket route. However if I use nginx it works fine with HTTP, however websocket route shows connection failed.

daphne mysite.asgi:application -b 0.0.0.0 -p 8080

nginx.conf

    server {
        listen       80;
        listen       [::]:80;
        server_name  192.168.x.x;
        #root         /usr/share/nginx/html;
        root         /home/test/mysite;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        error_page 404 /404.html;
        location = /404.html {
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        }
    

location /static/ {
        alias /home/test/mysite/static/;
    }
 
   location / {
      include proxy_params;
      proxy_pass http://192.168.x.x:8080;
 }
 

 location /ws {

 proxy_pass http://192.168.x.x:8080;
 proxy_http_version 1.1;
 proxy_set_header Upgrade $http_upgrade;
 proxy_set_header Connection “upgrade”;
 proxy_redirect off;
 proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header X-Forwarded-Host $server_name;
 }
}

Another thing to note is that I am using Django User Model authentication and when I login with any user, I see the username displayed in the HTTP route, however when I switch to websocket route after login, the username does not show as HTTP user session information is lost. Any better solutions how to use both HTTP and Websocket together?

If you are using ws/ as your path for routing the websocket connection, then these lines should be:

location /ws/ {
   proxy_pass http://192.168.x.x:8080/ws/;

Also, and I don’t know if it’s just an artifact of how you copied your settings into the forum, but you’ve got what appears to be “smart quotes” around the word “upgrade” in the proxy_set_header statement.

Note: I hope you realize there is no value in obfuscating a private ip address. They can’t be directly reached from the internet. You’re not protecting anything doing it.

Hi @KenWhitesell ,

Thanks for your reply. I copied my settings from the official channels doc and looks like it got messed up. I have corrected it. The private address are just for testing and will be changed so they cannot be reached from internet as they are behined a FW.

I tried as below and even then it does not work.

proxy_pass http://192.168.x.x:8080/ws/;

Below are my config files.

JS client:
var mysocket = new WebSocket('ws://' + window.location.host + '/ws/myapp');

routing.py:

from django.urls import path
from . import consumers
      
websocket_urlpatterns = [
        path('ws/myapp/', consumers.WSConsumer.as_asgi()),
      ]

Your urls don’t match:

is not the same as:

Understood. The problem is that you are potentially hiding information that could be useful in debugging this by replacing the real information with the “.x.x”. For whatever reason you’re doing it, it’s potentially hindering our attempts to help you far more than whatever benefit you may think you’re getting by doing this.

Hi @KenWhitesell ,

You are correct ! The settings you provided for nginx as below worked. It was a typo from my end for the forward slash.

So below works and now I am seeing it works for both HTTP and Websocket route.

location /ws/ {

 proxy_pass http://192.168.122.176:8080/ws/;

I had another question that as I have a User login system where a user logs in and username is displayed on the page(say index.html). Now if the user clicks on a websocket route by clicking a url on the page, the page redirects to a HTML page which is a websocket route(where data is pushed from server every few seconds to display real time data) and this page uses template inheritance from index.html page. On this page, the logged in user’s name is now showing blank. Probably as the HTTP user session information is not passed to websocket? How can I make the username display on this websocket route(page) ?

How are you trying to access the user’s name in the consumer?

See the docs for Scope

Keep in mind, that there’s only one request sent for a websocket - on the initial connection. Subsequent websocket transmissions don’t include the request object - there’s no need for it.

I read the documentation however cannot find how to pass the HTTP user session information to websocket route. In the docs it does mention about using self.scope["user"], however looks like this is strictly for using authentication in channels itself. My use case is related to passing the already available user information from HTTP route to websocket route.

I’m not following what you’re asking for here.

That is not an accurate statement.

Quoting directly from that section in the docs:

In general, the scope is the place to get connection information and where middleware will put attributes it wants to let you access (in the same way that Django’s middleware adds things to request ).

From that same section:

If you enable things like Authentication, you’ll also be able to access the user object as scope["user"]

In my base.html, I use below for displaying the logged in user(username).

Welcome,{{user.username}}

This is my consumers.py:

class WSConsumer(AsyncWebsocketConsumer):
        async def connect(self):
                self.user = self.scope["user"]
                await self.accept()
                while True:
                        now = datetime.datetime.now()
                        time = (now.strftime("%Y-%m-%d %H:%M:%S"))
                        await asyncio.sleep(1)
                        await self.send(text_data=json.dumps({
                                'one': time,
                                }))

async def disconnect(self, close_code):
    pass

This is an excerpt from my JS file(client side):

{% extends “base.html” %}
{% block content %}
.
.
.
{% endblock %}

I basically want to pass the {{user.username}} to the JS file(client side) which is not happening at the moment.

I think you’re going to need to provide more specific details and code associated with what you’re trying to do here.

Unless you have a JavaScript templating library, that template fragment you’re showing is not going to get rendered on the “client side”. Rendering by Django occurs in the server, not in the client.

Finally got it working. I derived the username object(from the HTTP session) as below in consumers.py

user = self.scope["user"]
await self.accept()
while True:
             username_str = user.username
             await self.send(text_data=json.dumps({
                          'user': username_str,
                          }))

Then, I used the “onmessage” event in client side JS file to fetch the above ‘user’ attribute and display it which indeed is the logged in user :slight_smile:

I wrote a medium article on this:

https://medium.com/@matt_31770/authentication-for-django-channels-using-a-database-cluster-4800dbfcb07e

Even if you don’t want to use its codebase, reviewing the article will educate one on the numerous issues that need to be addressed.

First off, proxying of a websocket from nginx can SOMETIMES result in a leading slash getting stripped (whereas for urls passed for http processing, these appear to consistently remain intact…) It’s not clear to me if it’s nginx or daphne doing that. So for a URLRouter in asgi.py, it’s safer to use a “re_path” function than a “path” one, making a slash elective (using the regex expression “/?” to signify the leading slash…) You could do trial and error and see what is getting passed and hardcode it, but at some future date they might decide to change that convention and your app will break; better you use re_path.

If you consult the file on the article’s companion git:

https://gitlab.com/sourcesolver1/django_wss_auth_v1/-/blob/main/wss_auth/nginx.conf.fragment

you will see the parameters that should be set when you are proxying to Daphne.

I see a private address being used above on the proxy_pass directive; presuming the daphne server is on the same server as your nginx one, a local loopback address of 127.0.0.1 would be safer to use, in case that server ever gets its address changed. If it’s a hand-off to a different server on a private network, no problem.

If your nginx server handles certificates for the https and wss, there should be no need to be using http and ws in the context of the daphne server.