Setting up Django Channels - django

I have been trying to integrate Django channels into my existing Django application.
Here's my routing.py:
from channels.routing import route
channel_routing = [
route('websocket.receive', 'chat.consumers.ws_echo', path=r'^/chat/$'),
]
Here's my consumers.py:
def ws_echo(message):
message.reply_channel.send({
'text': message.content['text'],
})
I am trying to create a socket by doing this:
ws = new WebSocket('ws://' + window.location.host + '/chat/');
ws.onmessage = function(message) {
alert(message.data);
};
ws.onopen = function() {
ws.send('Hello, world');
};
When I run this code, I get the following error in my console:
WebSocket connection to 'ws://localhost:8000/chat/' failed: Error during WebSocket handshake: Unexpected response code: 404
On my server, I get the following error:
HTTP GET /chat/ 404
Based on the error, I think that Django is giving a http connection rather than a ws connection.
Any help on this issue is greatly appreciated.

The issue with my setup was with my nginx configuration. All I had to do was add the forwarding lines and it fixed the problem.
location /chat { # Django Channels
proxy_pass http://0.0.0.0:8001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}

Related

Issue connecting to secure websocket using Django/Nginx/Daphne

Having an issue using secure websocket (wss) with django, Nginx, Gunicorn, and daphne. My site is hosted through cloudflare which provides the SSL/TLS certificate. I'm using a linux socket in /run/daphne/daphne.sock, where I gave the user 'ubuntu' ownership of the daphne folder.
The websockets work fine locally when it is not secured. When I tried hosting on my EC2 instance, I get the error-
sockets.js:16 WebSocket connection to 'wss://www.mywebsite.com/ws/sheet/FPIXX8/' failed:
Then it keeps trying to reconnect and fail again. It never sets up an initial connection since I don't get a ping.
Here are a few relevant files and snippets of code-
settings.py
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer', # asgi_redis.RedisChannelLayer ?
'CONFIG': {
'hosts': [('localhost', 6379)],
},
}
}
I think there may be an issue with the 'hosts' but I haven't been able to figure out through tutorials/googling, nor am I sure exactly what it does besides set a port. Since I'm using sockets, I imagine this would need to be different (or maybe it's ignored?) in deployment.
Routing.py (in main project dir)
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import app.routing
application = ProtocolTypeRouter({
# (http->django views is added by default)
'websocket': AuthMiddlewareStack(
URLRouter(
app.routing.websocket_urlpatterns
)
),
})
Routing.py (in app)
from django.urls import re_path
from django.conf.urls import url
from app import consumers
websocket_urlpatterns = [
re_path(r'ws/sheet/(?P<gameID>\w+)/$', consumers.GameConsumer.as_asgi()),
]
I'm pretty sure neither routing.py files don't cause an issue because I still get redirected properly.
asgi.py
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')
application = get_asgi_application()
I haven't touched this file.
Javascript
if (window.location.protocol == 'https:') {
wsProtocol = 'wss://'
} else {wsProtocol = 'ws://'}
updateSocket = new WebSocket(
wsProtocol + window.location.host +
'/ws/sheet/' + game_ID + '/');
This also seems to work fine, since I get redirected and it attempts to connect to proper URL with wss://.
systemd/system/daphne.service
[Unit]
Description=project Daphne Service
After=network.target
[Service]
User=ubuntu
Type=simple
WorkingDirectory=/home/ubuntu/django/project/project
ExecStart=/home/ubuntu/django/project/project/virtualenv/bin/daphne \
-u /run/daphne/daphne.sock \
project.asgi:application
[Install]
WantedBy=multi-user.target
systemd/system/daphne.socket
[Unit]
Description=daphne socket
[Socket]
ListenStream=/run/daphne/daphne.sock
[Install]
WantedBy=sockets.target
nginx
server {
listen 80;
server_name www.mywebsite.com mywebsite.com;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/ubuntu/django/project/project/;
}
location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
location /ws/ {
proxy_pass http://unix:/run/daphne/daphne.sock;
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;
}
}
WantedBy=sockets.target
I should probably add running 'sudo nginx -t' to get errors does not return any errors. Running 'sudo journalctl -u daphne' usually would not return errors, but I just checked again and I got another Permission Denied error at '/run/daphne/daphne.sock.lock' which I thought was fixed, so I tried to give permission again. Then, I got a 'Address already in use' for the daphne.sock. So, I restarted all services and now it just says 'Configuring endpoint unix:/run/daphne/daphne.sock' as the latest message, so I don't know if that is good or bad.

reconnecting-websocket.min.js:1 WebSocket connection to 'wss' failed: Error during WebSocket handshake: Unexpected response code: 200

I tried deploy my Django channels with WebSocket on Nginx, I tried a lot of ways but all failed try to change on my Nginx and tried to change the WebSocket URL but there is no any way work with me
My nginx
location /ws/ {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_pass http://127.0.0.1:8001;
}
my websocket url
var wsStart = 'ws://'
if (loc.protocol == 'https:'){
wsStart = 'wss://'
}
var endpoint = wsStart + loc.host + loc.pathname
var socket = new ReconnectingWebSocket(endpoint)
my django channel layer in settings
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
'hosts': [('localhost', 6379)],
},
},
}

CORS issue with Django and React hosted on same server

I am having a CORS issue with my Django Rest Framework and React app on the same server. I am running Vagrant with an Ubuntu 18 box and NGINX installed (I am assuming this issue will translate to DigitalOcean) I apologize ahead of time if I am providing too much information. DRF is using Supervisor and Gunicorn is on port 8000. I created my React app using create-react-app. I then used npm run build to create the static files.
NGINX Setup:
React Conf
server {
listen 8080;
server_name sandbox.dev;
root /var/sites/sandbox/frontend/build;
index index.html;
client_max_body_size 4G;
location / {
try_files $uri $uri/ /index.html;
}
Django Conf
upstream sandbox_server {
server unix:/var/tmp/gunicorn_sanbox.sock fail_timeout=0;
}
server {
listen 8000;
server_name api.sandbox.dev;
...
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
if (!-f $request_filename) {
proxy_pass http://sandbox_server;
break;
}
Django Setup:
INSTALLED_APPS = [
...
'rest_framework',
'corsheaders',
'myapp',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
...
]
I have tried the following with no luck
CORS_ORIGIN_ALLOW_ALL = True
and
CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = ('192.168.19.76:8080','localhost:8080',)
React App.js
...
fetch("http://localhost:8000/api/v1/token-auth/", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({"email":"test#user.com", "password":"testuser"}),
})
So to state the obvious CORS is correct because the Origin is localhost:8080 which is a different port so it sees it as cross origin. I have tried the different settings with cors origin allow, but it is still the same issue every time. Obviously I am doing something wrong, but I can't see it.
My thoughts are
Option 1
proxy pass using the django nginx conf file and do away with the react nginx conf file, but I don't know what affect that might cause in production or if this is a good idea. Is there a better way?
location /api {
proxy_set_header X-Forwarded_for $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://sandbox_server;
So finalize my thoughts and my question. After trying the different Django options for CORS I am still getting the CORS error. Why, and is it my NGINX conf files causing it or something else? Will I expect to see this in DigitalOcean?
UPDATE 1
I forgot to add the error. Here is the CORS error
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8000/api/v1/token-auth/. (Reason: CORS request did not succeed).
For those wanting to know the output from the network tab
Host localhost:8000
Origin http://192.168.19.76:8080
Pragma no-cache
Referer http://192.168.19.76:8080/
UPDATE 2
I did test using curl, and everything returned as expected so I know DRF is working corrently.
curl --data "email=test#user.com&password=testuser" http://localhost:8000/api/v1/token-auth/
FINAL UPDATE
Thanks to paulsm4 for all the help and just plain awesomeness.
So, I did abandon django-cors-headers and rolled my own. To answer paulsm4's question, I do not have add_header 'Access-Control-Allow-Origin' '*'; in the NGINX file although I did think about letting NGINX handle CORS vs Django, but never went that far. #paulsm4, this is the proxy_pass I was talking about. The key was adding this block of code to NGINX for the react portion in conjunction with my middleware.
location /api {
proxy_set_header X-Forwarded_For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://sandbox_server;
The above code by itself worked, but it did not allow me to whitelist any incoming URLs. Creating my own middleware allowed me to whitelist. I have no idea why django-cors-headers or even django-cors-middleware did not work for me. What was strange was that fetch never made it far enough with those two packages to get response headers and an error of any sorts, other than the CORS error I was asking about. With the middleware I wrote, fetch was able to fully make the call, and return some response headers whether it succeeded or failed.
For future reference, I might revisit NGINX and allowing it to handle CORS. Here is a good link
CORS on NGINX
NOTE
To clarify; the only middleware installed besides what Django already includes is the cors middleware. Both Django and React reside on the same server, but with different ports.
api.sandbox.com:8000 is the Django Rest Framework
app.sandbox.com:8080 is the React static files
Django 2.0.2
Python 3.6
django-cors-headers 2.4.0
Vagrant/VirtualBox Ubuntu 18.04
NGINX
Django settings.py
INSTALLED_APPS = [
...
# Third Party
'rest_framework',
'corsheaders',
# My Apps
'account',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
'django.middleware.csrf.CsrfViewMiddleware',
'corsheaders.middleware.CorsPostCsrfMiddleware',
...
]
CORS_ORIGIN_WHITELIST = (
'null',
'192.168.19.76:8080',
'localhost:8080',
'app.sandbox.com:8080'
)
React App.js
fetch("http://localhost:8000/api/v1/token-auth/", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"email": "test#user.com",
"password": "testuser"
}),
})
So I am at wits end here. It is either django-cors-headers that is not working or it could possibly be NGINX.
We've been exchanging comments; here's my current understanding:
PROBLEM: You have have a Django back-end API (on port 8080) and a React Front end (on port 8000). Both are currently running on localhost, but will ultimately reside on DigitalOcean. The React client is getting Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8000/api/v1/token-auth/. (Reason: CORS request did not succeed).
You need to configure CORS.
The request headers in the HTTP traffic you've captured clearly shows this isn't happening yet.
Some relevant links include:
django-cors-headers not work
Handling CORS in Django REST Framework
Djgano REST Framework: CORS
SUGGESTED NEXT STEP:
If you haven't already, please install and configure django-cors-headers

nginx location and Django auth

I'm trying to create a NGINX redirect based on an URL param in the querystring. Basically having:
http://localhost/redirect/?url=https://www.google.it/search?dcr=0&source=hp&q=django&oq=django
and
location /redirect/ {
proxy_cache STATIC;
# cache status code 200 responses for 10 minutes
proxy_cache_valid 200 1d;
proxy_cache_revalidate on;
proxy_cache_min_uses 3;
# use the cache if there's a error on app server or it's updating from another request
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
# don't let two requests try to populate the cache at the same time
proxy_cache_lock on;
# Strip out query param "timestamp"
if ($args ~ (.*)&timestamp=[^&]*(.*)) {
set $args $1$2;
}
return 302 $arg_url$args;
}
Now, only Django authenticated users (JWT/Cookie) can use the /redirect?url= end point, hence is it possible to implement a session/cookie check without opening a proxy to the entire world?
Anyway I could do it at the Django level (https://github.com/mjumbewu/django-proxy/blob/master/proxy/views.py) but I suppose it's faster and less computationally expensive at the NGINX level.
Thanks,
D
redirecting & proxying is different things, for getting django-proxy functionality you need to use nginx reverse proxy option instead of redirect.
# django-proxy code fragment
response = requests.request(request.method, url, **requests_args)
proxy_response = HttpResponse(
response.content,
status=response.status_code)
Nginx config for reverse proxying & auth
server {
listen 80;
server_name youtdomain.com;
location / {
# use django for authenticating request
auth_request /django-app/;
# a proxy to otherdomain
proxy_pass http://otherdomain.com;
proxy_set_header Host otherdomain.com;
}
location /django-app/{
internal; # protect from public access
proxy_pass http://django-app;
}
}
Django app should return 200 status code for authenticated users 401 otherwise, you can read more details about auth_request here
Based on the previous answers (thanks!) this is the solution:
http {
upstream app_api {
# server 172.69.0.10:8000;
server api:8000;
# fail_timeout=0 means we always retry an upstream even if it failed
# to return a good HTTP response (in case the Unicorn master nukes a
# single worker for timing out).
# server unix:/var/www/gmb/run/gunicorn.sock fail_timeout=0;
}
server {
location = /auth {
proxy_pass http://app_api/api-auth/login/;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
location /redirect/ {
auth_request /auth;
proxy_cache STATIC;
# cache status code 200 responses for 10 minutes
proxy_cache_valid 200 1d;
proxy_cache_revalidate on;
proxy_cache_min_uses 3;
# use the cache if there's a error on app server or it's updating from another request
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
# don't let two requests try to populate the cache at the same time
proxy_cache_lock on;
# Strip out query param "timestamp"
if ($args ~ (.*)&timestamp=[^&]*(.*)) {
set $args $1$2;
}
return 302 $arg_url$args;
}

CORS error while consuming calling REST API with React

I created a restful api with django-rest-framework accessible with this URL http://192.168.33.10:8002/scenarios/ and I'm creating a React app to make calls to the api an d consume its data.
I'm using fetch to make calls to the api
componentWillMount: function(){
this.setState({Problemstyle: this.props.Problemstyle})
fetch('http://192.168.33.10:8002/scenarios/')
.then(result=>result.json())
.then(result=> {
this.steState({items:result})
})
},
when i run my app i get an error in my browser
Fetch API cannot load http://192.168.33.10:8002/scenarios/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://192.168.33.10:8001' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
I'm not sure on how to solve this problem as i'm just starting to use React
Please Note: This solution is not for production configuration. This is merely a workaround for easier setup while development. Please refrain from using this in production configuration.
Install django-cors-headers through pip install django-cors-headers
Then, add in installed apps 'corsheaders'.
Add the setting,
CORS_ORIGIN_ALLOW_ALL = True
and,
ALLOWED_HOSTS = ['*']
This should do the trick.
UPDATE
You'll also need to add it to the middlewares,
MIDDLEWARE = [ # Or MIDDLEWARE_CLASSES on Django < 1.10
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
The currently accepted answer potentially opens sites up to security risks:
Why does the error happen:
In order for your AJAX request to work well, there are two things that need to happen:
The request has to be accepted by the server.
The returned request must be accepted by the browser, so that the client-side can do something with it.
The error that the OP reports, indicates that the second part of this process is failing. This is because if the request is sent from domain that is different to the server returning the request, the browser won't accept it unless the appropriate headers are set (that is, the server has given permission for the browser to read it).
How to fix it:
Now to fix this, we can use django-cors-headers. This will add the apropriate headers, so that the browser accepts the returned response. To install run:
pip install django-cors-headers
and add it to your middleware:
MIDDLEWARE = [
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
Now, you need to add the domain you are sending your AJAX request from, to the list of allowed domains:
CORS_ALLOWED_ORIGINS = [
"www.example.com",
"http://127.0.0.1:8000",
...
]
What about CORS_ORIGIN_ALLOW_ALL?
Do not use this unless you have a specific need to. Setting this to true, will mean that, any origin will be able to make a request to your API, and get a response. Unless you are making a public API, you probably won't need to do this. More likely you will only need to serve a single domain or a few domains (maybe you have a front-end, served from a different place to your API etc.)
If you are happy for any domain to access your API then you can set the following:
CORS_ORIGIN_ALLOW_ALL = True
If you do this, you will also need to set the following:
ALLOWED_HOSTS = ['*']
The reason for this, is Django will only accept certain hosts by default, so there's no point setting CORS_ORIGIN_ALLOW_ALL = True unless you're actually going to accept requests from anyone (that is the part 1 in the explanation above).
Note that by setting allowed hosts to a wildcard, you open yourself up to HTTP host header attacks. Make sure you understand these, and have made sure you are not affected. You can read more about them in the django docs.
Also note: if you have not set your ALLOWED_HOSTS and you are wondering why your requests are working, it is because when DEBUG=True certain hosts are allowed automatically, http://127.0.0.1:8000 etc.
Using django-cors-headers
Start by installing django-cors-headers using pip
pip install django-cors-headers
You need to add it to your project settings.py file:
INSTALLED_APPS = (
##...
'corsheaders'
)
Next you need to add corsheaders.middleware.CorsMiddleware middleware to the middleware classes in settings.py
MIDDLEWARE_CLASSES = (
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.BrokenLinkEmailsMiddleware',
'django.middleware.common.CommonMiddleware',
#...
)
You can then, either enable CORS for all domains by adding the following setting
CORS_ORIGIN_ALLOW_ALL = True
Or Only enable CORS for specified domains:
CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = (
'http//:localhost:8000',
)
See Yinon_90's answer for a better working version of this concept.
I wanted to propose a solution that does not require altering the behavior of the Django or React apps.
In production, you might want to serve both apps on the same domain/from the same server, under different paths. This wouldn't cause any CORS conflict in production.
Of course, we want to debug Django and utilize React HMR & Dev tools while debugging. For this, I've spun up an nginx docker container:
docker-compose.yml:
version: '3.8'
services:
web:
image: nginx:latest
ports:
- 8080:80
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
command: [nginx-debug, '-g', 'daemon off;']
environment:
- NGINX_HOST=localhost
- NGINX_PORT=80
nginx.conf:
Django debug is on port 8000, React HMR is on port 3000. I've allowed three paths to go to the Django app, /accounts, /api, and /admin. The rest goes to the React app (and into the React Router system)
events {}
http{
server {
listen 80;
server_name localhost;
location ~* /(accounts|api|admin) {
proxy_pass http://host.docker.internal:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://host.docker.internal:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
After docker-compose up, manage.py runserver, and npm start, both apps are available at localhost:8000!
UPDATE (12.12.22)
Just add to the package.json file the following line:
"proxy": "http://localhost:8000"
OLD ANSWER
Based on Joe Sadoski's great idea,
I suggest an improvement to his solution that also supports react hot-reload-on-changes:
(All other proposed solutions here do not work in chrome since the last upgrades in 2022)
version: '3.1'
services:
web:
image: nginx:latest
ports:
- 8080:80
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
command: [nginx-debug, '-g', 'daemon off;']
environment:
- NGINX_HOST=localhost
- NGINX_PORT=80
network_mode: host
restart: unless-stopped
My change here is: network_mode: host
Now the nginx.conf looks like this:
events {}
http{
server {
listen 80;
server_name localhost;
// this block handle react hot-reload
location /sockjs-node {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
// this block handle API calls
location ~* /(accounts|api|admin) {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
// this block handle all the rest (react/statics...)
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
Here I change the upstreams to point on localhost so both react server and django should run on the host computer in ports 3000 and 8000.
So, after docker-compose up, manage.py runserver, and npm start, both apps are available at http://localhost!