Websocket can only send and receive messages within the same network. (Django3 channels3 on Elasticbeanstalk(linux2) and elasticache)

Viewed 163

I developed a web program for video call and chat

using Django-channels

and deployed it to AWS Elasticbeanstalk and Elasticache redis for websocket.

After the deployment was completed, it worked very well when tested in the company. And even now, me and my wife are testing in the same wifi environment and it's working good.

However, messages cannot send or receive between different networks.

For example, the websocket connection between the person connected at work and me at home is not working well.

Logs say they are connected to each other.


Environment

Django 3.0

channels 3

supervisor

daphne

AWS Elastic Beanstalk

(Platform2, Linux2, nginx, gunicorn)

AWS Elasticache

(Redis without cluster)


Javascript code

let mapPeers = {};

let usernameInput = document.querySelector('#username');
let btnJoin = document.querySelector('#btn-join');

let username;
let webSocket;
function webSocketOnMessage(event) {
    let parsedData = JSON.parse(event.data);
    let peerUsername = parsedData['peer'];
    let action = parsedData['action'];

    if (username === peerUsername){
        return;
    }

    let receiver_channel_name = parsedData['message']['receiver_channel_name'];

    if (action === 'new-peer'){
        createOfferer(peerUsername, receiver_channel_name);

        return;
    }

    if (action === 'new-offer'){
        let offer = parsedData['message']['sdp']

        createAnswerer(offer, peerUsername, receiver_channel_name);

        return;
    }

    if (action === 'new-answer'){
        let answer = parsedData['message']['sdp'];

        let peer = mapPeers[peerUsername][0];

        peer.setRemoteDescription(answer);

        return;
    }

    // console.log('message : ', message)
}

btnJoin.addEventListener('click', () => {
    username = usernameInput.value;

    console.log('username : ', username);

    if (username === ''){
        return;
    }
    usernameInput.value = '';
    usernameInput.disabled = true;
    usernameInput.style.visibility = 'hidden';

    btnJoin.disabled = true;
    btnJoin.style.visibility = 'hidden';

    let labelUsername = document.querySelector('#label-username');
    labelUsername.innerHTML = username;

    let loc = window.location;
    let wsStart = 'ws://';

    if (loc.protocol === 'https:'){
        wsStart = 'wss://';
    }

    let endpoint = wsStart + loc.host + loc.pathname + 'ws/';

    console.log(loc.host)
    console.log(loc.pathname)
    console.log('endpoint: ', endpoint);

    webSocket = new WebSocket(endpoint);
    console.log('--------', webSocket)

    webSocket.addEventListener('open', (e) => {
        console.log('Connection opened!');

        sendSignal('new-peer', {});

        // let jsonStr = JSON.stringify({
        //     "message": "This is a message!"
        // });
        //
        // webSocket.send(jsonStr);

    });
    webSocket.addEventListener('message', webSocketOnMessage);
    webSocket.addEventListener('close', (e) => {
        console.log('Connection closed!', e)
    });
    // webSocket.addEventListener('error', (e) => {
    //     console.log('Error occurred!', e)
    // });
})


// for video
let 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');

let userMedia = navigator.mediaDevices.getUserMedia(constraints)
    .then(stream => {
        localStream = stream;
        localVideo.srcObject = localStream;
        localVideo.muted = true;

        let audioTracks = stream.getAudioTracks();
        let 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 = 'Audio mute'
                return;
            }
            btnToggleAudio.innerHTML = 'Audio unmute'
        });

        btnToggleVideo.addEventListener('click', () => {
            videoTracks[0].enabled = !videoTracks[0].enabled;
            if (videoTracks[0].enabled){
                btnToggleVideo.innerHTML = 'Video off'
                return;
            }
            btnToggleVideo.innerHTML = 'Video on'
        });

    })
    .catch(error => {
        console.log('Error accessing media devices', error);
    });

let btnSendMsg = document.querySelector('#btn-send-msd');
let messageList = document.querySelector('#message-list');
let messageInput = document.querySelector('#msg');

btnSendMsg.addEventListener('click', sendMsgOnclick);

function sendMsgOnclick() {
    let message = messageInput.value;
    let li = document.createElement('li');
    li.appendChild(document.createTextNode('Me: ' + message));
    messageList.appendChild(li);

    let dataChannels = getDataChannels();

    message = username + ': ' + message;

    console.log("---------console.log(dataChannels)----------")
        console.log(dataChannels)

    for (index in dataChannels){

        console.log("---------console.log(index)----------")
        console.log(index)

        dataChannels[index].send(message);
    }

    messageInput.value = '';

}

// let jsonStr = JSON.stringify({
//     'peer': username,
//     'action': 'new-answer',
//     "message": {
//
//     },
// });

// webSocket.send(jsonStr);

function sendSignal(action, message){
    let jsonStr = JSON.stringify({
        'peer': username,
        'action': action,
        "message": message,
    });
    webSocket.send(jsonStr);
}

function createOfferer(peerUsername, receiver_channel_name) {
    let peer = new RTCPeerConnection(null);
    addLocalTracks(peer);

    let dc = peer.createDataChannel('channel');
    dc.addEventListener('open', () => {
        console.log('connection opened!')
    })
    dc.addEventListener('message', dcOnMessage);

    let remoteVideo = createVideo(peerUsername);
    setOnTrack(peer, remoteVideo);

    mapPeers[peerUsername] = [peer, dc];

    peer.addEventListener('iceconnectionstatechange', () => {
        let 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 createAnswerer(offer, peerUsername, receiver_channel_name) {
    let peer = new RTCPeerConnection(null);
    addLocalTracks(peer);

    let 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', () => {
        let 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);
        })

    // 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) {
    let message = event.data;

    let li = document.createElement('li')
    li.appendChild(document.createTextNode(message));
    messageList.appendChild(li);
}

function createVideo(peerUsername) {
    let videoContainer = document.querySelector('#video-container');
    let remoteVideo = document.createElement('video');

    remoteVideo.id = peerUsername + '-video';
    remoteVideo.autoplay = true;
    remoteVideo.playsInline = true;

    let videoWrapper = document.createElement('div');
    videoContainer.appendChild(videoWrapper);
    videoWrapper.appendChild(remoteVideo);

    return remoteVideo;

}

function setOnTrack(peer, remoteVideo) {
    let remoteStream = new MediaStream();
    remoteVideo.srcObject = remoteStream;

    peer.addEventListener('track', async (event) => {
        remoteStream.addTrack(event.track, remoteStream);
    });
}

function removeVideo(video) {
    let videoWrapper = video.parentNode;
    videoWrapper.parentNode.removeChild(videoWrapper);
}

function getDataChannels() {
    let dataChannels = []

    for (peerUsername in mapPeers){
        let dataChannel = mapPeers[peerUsername][1];

        dataChannels.push(dataChannel);
    }
    return dataChannels;
}

django code

# Settings
CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels_redis.core.RedisChannelLayer",
            "CONFIG": {
                "hosts": [("redis-2.*************.cache.amazonaws.com", 6379)]
            },
        },
    }

USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

CORS_ORIGIN_ALLOW_ALL = True


# asgi
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Project.settings')
django_asgi_app = get_asgi_application()

from channels.auth import AuthMiddlewareStack
import video_app.routing

application = ProtocolTypeRouter({
    "http": django_asgi_app,
    "websocket": AuthMiddlewareStack(
            URLRouter(
                video_app.routing.websocket_urlpatterns
            )
        ),
})


# routing
from django.urls import re_path
from . import consumers

websocket_urlpatterns = [
    re_path(r"^ws/$", consumers.ChatConsumer.as_asgi()),
]

# Project.url
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('video_app.urls'))
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)


# video_app.url
urlpatterns = [
    path('', views.video_index, name='video_index')
]



# Consumer
import json
from channels.generic.websocket import AsyncWebsocketConsumer


class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        print('connect!!')
        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, close_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):
        print('send_sdp!!')
        receive_dict = event['receive_dict']

        await self.send(text_data=json.dumps(receive_dict))

# view
def video_index(request):
    context = {}
    return render(request, 'video_app/index.html', context)

Procfile

#Procfile
web: gunicorn --bind :8000 --workers 3 --threads 2 Project.wsgi:application
websocket: daphne -b :: -p 5000 Project.asgi:application

config for elasticbeanstalk

# option.config
#packages:
#  yum:
#    python3-devel: [ ]
#    mariadb-devel: [ ]

option_settings:
  aws:elasticbeanstalk:application:environment:
    DJANGO_SETTINGS_MODULE: Project.settings

  aws:elasticbeanstalk:environment:proxy:staticfiles:
    /static: staticfiles

  aws:elasticbeanstalk:container:python:
    WSGIPath: Project.wsgi:application

container_commands:
  00_wsgipass:
    command: 'echo "WSGIPassAuthorization On" >> ../wsgi.conf'
#  01_virtualenv:
#    command: 'source /var/app/venv/*/bin/activate'
#  02_collectstatic:
#    command: 'django-admin.py collectstatic --noinput'
#  00_pip_upgrade:
#    command: "source /var/app/venv/*/bin/activate && pip install --upgrade pip"
#    command: "source /opt/python/run/venv/bin/activate && pip install --upgrade pip"
#    ignoreErrors: false

#  02_collectstatic:
#    command: "source /var/app/venv/*/bin/activate && python3 manage.py collectstatic --noinput"



# Supervisor
files:
  /usr/local/etc/supervisord.conf:
    mode: "000755"
    owner: root
    group: root
    content: |
      [unix_http_server]
      file=/tmp/supervisor.sock   ; (the path to the socket file)
      [supervisord]
      logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log)
      logfile_maxbytes=50MB        ; (max main logfile bytes b4 rotation;default 50MB)
      logfile_backups=10           ; (num of main logfile rotation backups;default 10)
      loglevel=info                ; (log level;default info; others: debug,warn,trace)
      pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
      nodaemon=false               ; (start in foreground if true;default false)
      minfds=1024                  ; (min. avail startup file descriptors;default 1024)
      minprocs=200                 ; (min. avail process descriptors;default 200)
      [rpcinterface:supervisor]
      supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
      [supervisorctl]
      serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL  for a unix socket
      [include]
      files = /usr/local/etc/*.conf
      [inet_http_server]
      port = 127.0.0.1:9001
  /etc/init.d/supervisord:
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/bin/bash
      # Get into root mode
      sudo su
      # Source function library
      . /etc/rc.d/init.d/functions
      # Source system settings
      if [ -f /etc/sysconfig/supervisord ]; then
      . /etc/sysconfig/supervisord
      fi
      # Path to the supervisorctl script, server binary,
      # and short-form for messages.
      supervisorctl=/usr/local/bin/supervisorctl
      supervisord=${SUPERVISORD-/usr/local/bin/supervisord}
      prog=supervisord
      pidfile=${PIDFILE-/tmp/supervisord.pid}
      lockfile=${LOCKFILE-/var/lock/subsys/supervisord}
      STOP_TIMEOUT=${STOP_TIMEOUT-60}
      OPTIONS="${OPTIONS--c /usr/local/etc/supervisord.conf}"
      RETVAL=0
      start() {
      echo -n $"Starting $prog: "
      daemon --pidfile=${pidfile} $supervisord $OPTIONS
      RETVAL=$?
      echo
      if [ $RETVAL -eq 0 ]; then
      touch ${lockfile}
      $supervisorctl $OPTIONS status
      fi
      return $RETVAL
      }
      stop() {
      echo -n $"Stopping $prog: "
      killproc -p ${pidfile} -d ${STOP_TIMEOUT} $supervisord
      RETVAL=$?
      echo
      [ $RETVAL -eq 0 ] && rm -rf ${lockfile} ${pidfile}
      }
      reload() {
      echo -n $"Reloading $prog: "
      LSB=1 killproc -p $pidfile $supervisord -HUP
      RETVAL=$?
      echo
      if [ $RETVAL -eq 7 ]; then
      failure $"$prog reload"
      else
      $supervisorctl $OPTIONS status
      fi
      }
      restart() {
      stop
      start
      }
      case "$1" in
      start)
      start
      ;;
      stop)
      stop
      ;;
      status)
      status -p ${pidfile} $supervisord
      RETVAL=$?
      [ $RETVAL -eq 0 ] && $supervisorctl $OPTIONS status
      ;;
      restart)
      restart
      ;;
      condrestart|try-restart)
      if status -p ${pidfile} $supervisord >&/dev/null; then
      stop
      start
      fi
      ;;
      force-reload|reload)
      reload
      ;;
      *)
      echo $"Usage: $prog {start|stop|restart|condrestart|try-restart|force-reload|reload}"
      RETVAL=2
      esac
      exit $RETVAL
      commands:
      01_start_supervisor:
      command: '/etc/init.d/supervisord restart'
      leader_only: true

And the images below are "AWS" settings.

Elasticbeanstalk -> configuration -> loadbalancer(Application Loadbalancer) enter image description here

ElastiCache (redis without cluster) enter image description here

Security groups enter image description here enter image description here enter image description here enter image description here

Please advise on what I should do.

0 Answers
Related