[英]main.js:95 WebSocket connection to 'wss://website' failed :(
I have tried to make a simple video calling app using Django and WebRTC.我尝试使用 Django 和 WebRTC 制作一个简单的视频通话应用程序。 I tried running this on localhost and it works well, but I tried hosting it on a website hosting platform and now it doesn't work.
我尝试在 localhost 上运行它,它运行良好,但我尝试将它托管在网站托管平台上,现在它不起作用。 I surfed through many websites to correct this, but failed.
我浏览了许多网站来纠正这个问题,但都失败了。 I haven't used Redis server.
我没有使用过 Redis 服务器。
Here are some required files:以下是一些必需的文件:
asgi.py asgi.py
import os
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from channels.auth import AuthMiddlewareStack
import chat.routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myProject.settings')
application = ProtocolTypeRouter({
"https": get_asgi_application(),
# Just HTTP for now. (We can add other protocols later.)
"websocket": AuthMiddlewareStack(
URLRouter(
chat.routing.websocket_urlpatterns
)
)
})
settings.py设置.py
import os
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/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '****'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['https://myProject.herokuapp.com/']
# Application definition
INSTALLED_APPS = [
'channels',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'chat',
]
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',
]
ROOT_URLCONF = 'myProject.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'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 = 'myProject.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/3.0/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',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = '/home/myProject/static/'
ASGI_APPLICATION = 'myProject.asgi.application'
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [('https://myProject.herokuapp.com/'),
('redis://<my_secret_password_given_by_redislabs>@<my_redislabs_endpoint>')],
},
},
}
django_heroku.settings(locals())
This is my main.js file that consists code for VideoCalling functionality.这是我的main.js文件,其中包含 VideoCalling 功能的代码。
console.log('in main.js')
var ICE_config = {
'iceServers': [
{
'url': 'stun:stun.l.google.com:19302'
},
{
'url': 'turn:192.158.29.39:3478?transport=udp',
'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
'username': '28224511:1379330808'
},
{
'url': 'turn:192.158.29.39:3478?transport=tcp',
'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
'username': '28224511:1379330808'
}
]
}
var mapPeers = {};
var usernameInput = document.querySelector('#username');
var btnJoin = document.querySelector('#btn-join');
var username;
var webSocket;
function webSocketOnMessage(event)
{
var parsedData = JSON.parse(event.data);
var peerUsername = parsedData['peer'];
var action = parsedData['action'];
if(username == peerUsername)
{
return;
}
var receiver_channel_name = parsedData['message']['receiver_channel_name'];
if(action == 'new-peer'){
createOfferer(peerUsername, receiver_channel_name);
return;
}
if(action == 'new-offer'){
var offer = parsedData['message']['sdp'];
createAnswerer(offer, peerUsername, receiver_channel_name);
return;
}
if(action == 'new-answer'){
var answer = parsedData['message']['sdp'];
var peer = mapPeers[peerUsername][0];
peer.setRemoteDescription(answer);
return;
}
}
btnJoin.addEventListener('click', () =>{
username= usernameInput.value;
console.log(username);
if(username =='')
{
return;
}
usernameInput.value='';
usernameInput.disabled=true;
usernameInput.style.visibility = 'hidden';
btnJoin.disabled=true;
btnJoin.style.visibility = 'hidden';
var labelUsername = document.querySelector('#label-username');
labelUsername.innerHTML = username;
var loc = window.location;
var wsStart = 'ws://';
if(loc.protocol == 'https:')
{
wsStart = 'wss://';
}
var endpoint = wsStart + loc.host + loc.pathname;
console.log(endpoint);
line 95 --> webSocket = new WebSocket(endpoint);
webSocket.addEventListener('open', (e) =>{
console.log('Connection opened');
sendSignal('new-peer',{});
webSocket.addEventListener('message', webSocketOnMessage);
webSocket.addEventListener('close', (e) =>{
console.log('Connection closed');
});
webSocket.addEventListener('error', (e) =>{
console.log('Error occurred');
});
});
});
var localStream = new MediaStream();
const constraints = {
'video':true,
'audio':true
}
const localVideo = document.querySelector('#local-video');
const btnToggleAudio = document.querySelector('#btn-toggle-audio');
const btnToggleVideo= document.querySelector('#btn-toggle-video');
var userMedia = navigator.mediaDevices.getUserMedia(constraints)
.then(stream => {
localStream = stream;
localVideo.srcObject = localStream;
localVideo.muted = true;
var audioTracks = stream.getAudioTracks();
var videoTracks = stream.getVideoTracks();
audioTracks[0].enabled = true;
videoTracks[0].enabled = true;
btnToggleAudio.addEventListener('click', () =>{
audioTracks[0].enabled = !audioTracks[0].enabled;
if(audioTracks[0].enabled){
btnToggleAudio.innerHTML = '<i class="fas fa-microphone" style="font-size:20px;"></i>';
return;
}
btnToggleAudio.innerHTML = '<i class="fas fa-microphone-slash" style="font-size:20px;"></i>';
});
btnToggleVideo.addEventListener('click', () =>{
videoTracks[0].enabled = !videoTracks[0].enabled;
if(videoTracks[0].enabled){
btnToggleVideo.innerHTML = '<i class="fas fa-video" style="font-size:20px;"></i>';
return;
}
btnToggleVideo.innerHTML = '<i class="fas fa-video-slash" style="font-size:20px;"></i>';
});
})
.catch(error =>{
console.log('Error accessing media devices!', error);
});
var btnSendMsg = document.querySelector('#btn-send-msg');
var messageList = document.querySelector('#message-list');
var messageInput = document.querySelector('#msg');
btnSendMsg.addEventListener('click', sendMsgOnClick);
function sendMsgOnClick(){
var message = messageInput.value;
var li = document.createElement('li');
li.appendChild(document.createTextNode('Me: '+ message));
messageList.append(li);
var dataChannels = getDataChannels();
message = username + ': ' + message;
for(index in dataChannels){
dataChannels[index].send(message);
}
messageInput.value = '';
}
function sendSignal(action, message)
{
var jsonStr = JSON.stringify({
'peer': username,
'action': action,
'message': message,
});
webSocket.send(jsonStr);
}
function createOfferer(peerUsername, receiver_channel_name)
{
var peer = new RTCPeerConnection(ICE_config);
addLocalTracks(peer);
var dc = peer.createDataChannel('channel');
dc.addEventListener('open',() =>{
console.log('Connection opened!');
});
dc.addEventListener('message', dcOnMessage);
var remoteVideo = createVideo(peerUsername);
setOnTrack(peer, remoteVideo);
mapPeers[peerUsername] = [peer,dc];
peer.addEventListener('iceconnectionstatechange', ()=>{
var iceConnectionState = peer.iceConnectionState;
if(iceConnectionState === 'failed'|| iceConnectionState === 'disconnected' || iceConnectionState === 'closed'){
delete mapPeers[peerUsername];
if(iceConnectionState != 'closed')
{
peer.close();
}
removeVideo(remoteVideo);
}
});
peer.addEventListener('icecandidate', (event)=>{
if(event.candidate){
console.log('New ice candidate', JSON.stringify(peer.localDescription));
return;
}
sendSignal('new-offer',{
'sdp' : peer.localDescription,
'receiver_channel_name': receiver_channel_name
});
});
peer.createOffer()
.then(o => peer.setLocalDescription(o))
.then(() =>{
console.log('Local description set successfully');
});
}
function addLocalTracks(peer){
localStream.getTracks().forEach(track => {
peer.addTrack(track, localStream);
});
return;
}
function dcOnMessage(event){
var message = event.data;
var li = document.createElement('li');
li.appendChild(document.createTextNode(message));
messageList.appendChild(li);
}
function createAnswerer(offer, peerUsername, receiver_channel_name)
{
var peer = new RTCPeerConnection(ICE_config);
addLocalTracks(peer);
var remoteVideo = createVideo(peerUsername);
setOnTrack(peer, remoteVideo);
peer.addEventListener('datachannel', e => {
peer.dc = e.channel;
peer.dc.addEventListener('open',() => {
console.log('Connection opened!');
});
peer.dc.addEventListener('message', dcOnMessage);
mapPeers[peerUsername] = [peer,peer.dc];
});
peer.addEventListener('iceconnectionstatechange', () => {
var iceConnectionState = peer.iceConnectionState;
if(iceConnectionState === 'failed'|| iceConnectionState === 'disconnected' || iceConnectionState === 'closed'){
delete mapPeers[peerUsername];
if(iceConnectionState != 'closed')
{
peer.close();
}
removeVideo(remoteVideo);
}
});
peer.addEventListener('icecandidate', (event) => {
if(event.candidate){
console.log('New ice candidate', JSON.stringify(peer.localDescription));
return;
}
sendSignal('new-answer',{
'sdp' : peer.localDescription,
'receiver_channel_name': receiver_channel_name
});
});
peer.setRemoteDescription(offer)
.then(() =>{
console.log('Remote Description set successfully for %s.', peerUsername);
return peer.createAnswer();
})
.then(a =>{
console.log('Answer created!');
peer.setLocalDescription(a);
});
}
function createVideo(peerUsername){
var videoContainer = document.querySelector('#video-container');
var remoteVideo = document.createElement('video');
remoteVideo.id = peerUsername + '-video';
remoteVideo.autoplay = true;
remoteVideo.playsInline = true;
var videoWrapper = document.createElement('div');
videoContainer.appendChild(videoWrapper);
videoWrapper.appendChild(remoteVideo);
return remoteVideo;
}
function setOnTrack(peer, remoteVideo){
var remoteStream = new MediaStream()
remoteVideo.srcObject = remoteStream;
peer.addEventListener('track', async(event) =>{
remoteStream.addTrack(event.track, remoteStream);
});
}
function removeVideo(video){
var videoWrapper = video.parentNode;
videoWrapper.parentNode.removeChild(videoWrapper);
}
function getDataChannels(){
var dataChannels =[];
for(peerUsername in mapPeers){
var dataChannel = mapPeers[peerUsername][1];
dataChannels.push(dataChannel);
}
return dataChannels;
}
I'm also attaching main.html for reference:我还附上main.html以供参考:
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>TClone</title>
<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li>
<div style="float:right;" class="nav-link">
<button class="bg-dark" id="btn-toggle-audio"><i class="fas fa-microphone" style="font-size:20px;"></i></button>
<button class="bg-dark" id="btn-toggle-video"><i class="fas fa-video" style="font-size:20px;"></i></button>
<button class="bg-dark" id="main" onclick="openNav()"><i class="far fa-comments" style="font-size:20px;"></i></button>
</div>
</li>
</ul>
</div>
</nav>
<h3 id="label-username">USERNAME</h3>
<div>
<input id="username"><button id="btn-join" class="bg-dark">Join Room</button>
</div>
<div>
<div id="video-container">
<div>
<video id="local-video" style="float:left;" poster="{% static 'images/profile.png' %}" controls width="590px" height="480px" autoplay playsinline></video>
</div>
</div>
<div class="sidenav bg-dark" id="mySidebar">
<h3 style="color:white">CHAT</h3>
<button class="closebtn bg-dark"">×</button>
<div id="messages">
<ul id="message-list" class="navbar-nav mr-auto"></ul>
</div>
<div>
<input id="msg"><button id="btn-send-msg" class="bg-dark">Send Message</button>
</div>
<button id="btn-share-screen" class="bg-dark">Share screen</button>
</div>
</div>
<script src="{% static 'js/main.js' %}"></script>
</body>
</html>
EDIT:编辑:
consumers.py消费者.py
import json
from channels.generic.websocket import AsyncJsonWebsocketConsumer
class ChatConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
self.room_group_name = 'Test-Room'
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, code):
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
print('Disconnected')
async def receive(self, text_data):
receive_dict = json.loads(text_data)
message = receive_dict['message']
action = receive_dict['action']
if (action == 'new-offer') or (action == 'new-answer'):
receiver_channel_name = receive_dict['message']['receiver_channel_name']
receive_dict['message']['receiver_channel_name'] = self.channel_name
await self.channel_layer.send(
receiver_channel_name,
{
'type': 'send.sdp',
'receive_dict': receive_dict
}
)
return
receive_dict['message']['receiver_channel_name'] = self.channel_name
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'send.sdp',
'receive_dict': receive_dict
}
)
async def send_sdp(self, event):
receive_dict = event['receive_dict']
await self.send(text_data=json.dumps(receive_dict))
Also adding Procfile .还添加Procfile 。
web: gunicorn myProject.wsgi:application --log-file - --log-level debug
python manage.py collectstatic --noinput
python manage.py migrate
worker: python manage.py runworker channel_layer
web: python myProject/manage.py runserver 0.0.0.0:$PORT
requirements.txt要求.txt
django
django-channels
gunicorn
django_heroku
And when I run this app, I get this error:当我运行这个应用程序时,我得到了这个错误:
in main.js line 95:WebSocket connection to 'wss://(name of website).com/' failed:在 main.js 第 95 行:WebSocket 连接到“wss://(网站名称).com/”失败:
I tried checking the security of the website hosting service, and it is https.我尝试检查网站托管服务的安全性,它是 https。 Now I am really NOT able to identify where my error is and I really need help in correcting this.
现在我真的无法确定我的错误在哪里,我真的需要帮助来纠正这个问题。 Thanks in advance!
提前致谢!
The problem lies in your Procfile
.问题在于您的
Procfile
。 Gunicorn
is a WSGI HTTP Server. Gunicorn
是一个 WSGI HTTP 服务器。 It doesn't support ASGI.它不支持 ASGI。 This is what you have to do.
这是你必须做的。
But first things first, in requirements.txt
that's not how you're supposed to add packages.但首先,在
requirements.txt
,这不是您添加包的方式。 Create a virtual environment(if you haven't already) with either by pipenv
, virtualenv
or if you don't want to download additional packages, then you can use venv
.通过
pipenv
或virtualenv
创建虚拟环境(如果您还没有),或者如果您不想下载其他软件包,则可以使用venv
。 ( Note: There is no major difference among the three mentioned packages. ). (注意:上面提到的三个包之间没有太大区别。 )。
In your working directory, run this on the root level of your django-channels
project: python -m venv venv
.在您的工作目录中,在
django-channels
项目的根级别运行此命令: python -m venv venv
。 Then activate the venv
:然后激活
venv
:
venv\\Scripts\\activate
- if you are on Windows venv\\Scripts\\activate
- 如果您使用的是 Windows
source venv/bin/activate
- if you are on Mac or Linux. source venv/bin/activate
- 如果您使用的是 Mac 或 Linux。
Then install django
, channels
, channels_redis
with pip
.然后使用
pip
安装django
、 channels
、 channels_redis
。
Now run: pip freeze > requirements.txt
.现在运行:
pip freeze > requirements.txt
。 This will add all the required packages correctly.这将正确添加所有必需的包。
Now, in your Procfile
delete everything and add these lines:现在,在您的
Procfile
删除所有内容并添加以下行:
web: daphne <your_project>.asgi:application --port $PORT --bind 0.0.0.0 -v2
worker: python manage.py runworker channels --settings=<your_project>.settings -v2
Deploy again with these settings.使用这些设置再次部署。
EDIT:编辑:
Change your Procfile
to this:将您的
Procfile
更改为:
release: python manage.py migrate
web: daphne <your_project>.asgi:application --port $PORT --bind 0.0.0.0 -v2
worker: python manage.py runworker channels --settings=<your_project>.settings -v2
And I see that you have changed your Redis configuration to this:我看到您已将 Redis 配置更改为:
"CONFIG": {
"hosts": [('https://myProject.herokuapp.com/'), // <--- I don't think this line is necessary, so change it back to what it was
('redis://<my_secret_password_given_by_redislabs>@<my_redislabs_endpoint>')],
},
You have to use daphne.你必须使用达芙妮。
https://github.com/django/daphne https://github.com/django/daphne
I will try to show you the minimum configuration.我将尝试向您展示最低配置。
app
├── app
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── db.sqlite3
├── main
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ ├── models.py
│ ├── routing.py
│ ├── consumers.py
│ ├── tests.py
│ ├── urls.py
│ ├── views.py
│ └── templates
│ │
│ └── chat
│ └── room.html
├── manage.py
asgi.py asgi.py
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings')
import django
django.setup()
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
import main.routing
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter(
main.routing.websocket_urlpatterns
)
),
})
routing.py路由.py
from django.urls import re_path
import main.consumers
websocket_urlpatterns = [
re_path(r"ws/chat/(?P<root_name>\w+)/$", main.consumers.ChatConsumer.as_asgi()),
]
consumers.py消费者.py
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
self.room_group_name = "chat_%s" % self.room_name
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
async def receive(self, text_data):
print("receive")
settings.py设置.py
ASGI_APPLICATION = 'main.asgi.application'
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
views.py视图.py
def room(request, room_name):
return render(request, "chat/room.html", {"room_name": room_name})
room.html房间.html
{{ room_name|json_script:"roomName" }}
roomName = JSON.parse(document.getElementById("roomName").textContent);
chatSocket = new WebSocket(
"ws://"
+ window.location.host
+ "/ws/chat/"
+ roomName
+ "/"
);
Run跑
~/app$ daphne app.asgi:application
Open打开
http://127.0.0.1:8000/room_1/
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.