Implementing WebSockets With Django

implementing_websockets_with_django

Users now demand information as soon as it's available. If you have to refresh the page to get new information, it's already too late. Luckily, a protocol supported by all modern browsers allows for direct data exchange: WebSockets.

According to Google, WebSockets is an advanced technology that makes it possible to open an interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply. In plain words: There is a persistent connection between the client and the server and both parties can start sending data at any time.

We use Django as the backbone for most of our projects, recently we attempted at developing a multiplayer javascript gaming platform. In the course of that project WebSockets played a vital role in communication between players and the server and also among the other players. To achieve this we used a library called django-websockets-redis developed by Jacob Rief. I would like to give you a few details on how we used this library to achieve our goal.

In django-websockets-redis, there are four types in which we can subscribe for asynchronous notifications from the server,

  • subscribe-broadcast
  • subscribe-user
  • subscribe-group
  • subscribe-session

In our project we mainly used subscribe-broadcast and subscribe-user. Our use case is as follows, when a player logs in he/she can choose a game from a multitude of games and then the player can send a request to another player asking them to play the game with him/her. If the other player accepts the request, then a game session is created and the players are redirected to the game page and play their game in peace. The following are the snippets of code which deals with websockets,

Firstly when a player logs in he/she is subscribed to a ‘notifications’ channel which is of type ‘broadcast’. The purpose of this channel is that, whenever the site administrator wants to issue a public notification which is to be viewed by all the members then the administrator can publish a message into the ‘notifications’ channel, it is immediately broadcasted to all the players who are listening this particular channel.

Client Side

var ws4redis = WS4Redis({
    uri: "ws://127.0.0.1/ws/notifications?subscribe-broadcast",
    receive_message: function(message){
         console.log(message);
    },
    heartbeat_msg: '--heartbeat--'
});

Server Side

from ws4redis.publisher import RedisPublisher
# the message is broadcasted to everyone listening to ‘notifications’ channel
redis_publisher = RedisPublisher(facility='notifications', broadcast=True)
message = RedisMessage('There will be a site maintenance from 01:00 to 04:00 tomorrow.')
redis_publisher.publish_message(message)

Secondly when a player requests to play a game with another player and the other player accepts the request, a game session with an unique gameID is created and the players are redirected to the game page and in the game page they are subscribed to the ‘gameID’ channel of type ‘user’ to receive websocket notifications.

Client Side

var ws4redis = WS4Redis({
    uri: "ws://127.0.0.1/ws/"+gameID+"?subscribe-user",
    receive_message: function(message){
        console.log(message);
    },
    heartbeat_msg: '--heartbeat--'
});

Server Side

from ws4redis.publisher import RedisPublisher

# playerID of the player who published the message
playerID = request.GET[‘playerID’] 

# rest of the players excluding the player who published the message
remaining_players = players.remove(playerID)
gameID = request.GET[‘gameID’]

# the message is sent to the players subscribed to a particular gameID channel
redis_publisher = RedisPublisher(facility=gameID, users=players)

message = RedisMessage(gameData)
redis_publisher.publish_message(message)

In deployment, we used gunicorn to deploy the django webserver and used uWSGI to deploy the websocket server at the back of nginx.

Nginx Snippet

upstream webserver {
    server 0.0.0.0:8000;
}

upstream websocket {
    server 0.0.0.0:8001;
}

map $http_upgrade $connection_upgrade {
    default upgrade;
    "" close;
}

server {
    listen      80;
    server_name 127.0.0.1; # substitute your machine's IP address or FQDN
    charset     utf-8;
    # websocket path
    location /ws {
        proxy_pass http://websocket;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }
    # webserver path
    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_pass http://webserver;
        proxy_redirect off;      
    }
}

Gunicorn Snippet

gunicorn DJANGO_WSGI_MODULE:application \
--workers 3 \
--bind=0.0.0.0:8000

uWSGI Snippet

INI File

[uwsgi]
umask = 002
virtualenv = 
chdir = 
module = :application
no-orphans = true
die-on-term = true
memory-report = true
http-socket = 0.0.0.0:8001
http-websockets = true
gevent = 1000
threads = 1
processes = 1
master = true
pidfile = /tmp/uwsgi-emperor.pid
daemonize = 
logto = 
stop = /tmp/uwsgi-emperor.pid
#to start uwsgi server
uwsgi --ini 

Finally you can have a look at our attempt at developing the multiplayer gaming platform in github aptuz/funguilds.