Skip to content

Instantly share code, notes, and snippets.

@stephenmcd
Last active August 29, 2015 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save stephenmcd/d7551ba68d01b1d889d2 to your computer and use it in GitHub Desktop.
Save stephenmcd/d7551ba68d01b1d889d2 to your computer and use it in GitHub Desktop.
Django model updates over websockets - run with: `gunicorn -c conf.py server:serve`
# This is the gunicorn config file for the websocket server.
worker_class = "geventwebsocket.gunicorn.workers.GeventWebSocketWorker"
bind = "0.0.0.0:9000"
workers = 1
timeout = 10000000000
loglevel = "error"
proc_name = "websocket-server"
# Modify paths here as required - allows websocket server to run
# over SSL, using certs configured for nginx.
import os
cert_path = "/etc/nginx/conf/"
if os.path.exists(cert_path):
certfile = os.path.join(cert_path, "cert.crt")
keyfile = os.path.join(cert_path, "cert.key")
# This is just an example of the Django app publishing
# to a pubsub channel on Redis. We simply publish an
# object ID when it is saved.
from django.db.models.signals import post_save
from json import dumps
from redis import Redis
from someapp.models import MyModel
redis = Redis()
CHANNEL = "foo"
def saved(sender, instance, created, **kwargs):
redis.publish(CHANNEL, dumps({"updated_id": instance.id}))
post_save.connect(saved, sender=MyModel)
# Known to work with these versions of required libs.
gunicorn==0.17.4
gevent==0.13.8
gevent-socketio==0.3.5-beta
gevent-websocket==0.3.6
hiredis==0.1.1
redis==2.7.5
# Main websocket server. Handles incoming websocket connections,
# listens to Redis pubsub channel, and relays data from it back
# to the client via its websocket connection.
import os
import sys
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
from Cookie import SimpleCookie
from django.conf import settings
from django.contrib.auth import SESSION_KEY
from django.utils.importlib import import_module
from gevent import spawn
from geventwebsocket import WebSocketError
from redis import Redis
redis = Redis()
CHANNEL = "foo"
class WebSocketSession(object):
def __init__(self, socket, environ):
self.socket = socket
self.pubsub = redis.pubsub()
self.channels = []
user_id = self.get_user_id(environ)
# Above is Django user ID from session - do auth here if required.
self.pubsub.subscribe(CHANNEL)
self.listener = spawn(self.listen)
def run(self):
# Main loop - receives messages from client until disconnected.
try:
while True:
message = self.socket.receive()
if message is None:
break
# Handle message received from client here if required.
finally:
self.pubsub.unsubscribe(CHANNEL)
self.listener.kill()
self.socket.close()
def get_user_id(self, environ):
# Parses session ID from cookie and returns user ID from session data.
try:
cookie = SimpleCookie(environ["HTTP_COOKIE"])
session_key = cookie[settings.SESSION_COOKIE_NAME].value
except KeyError:
return
engine = import_module(settings.SESSION_ENGINE)
return engine.SessionStore(session_key).get(SESSION_KEY)
def listen(self):
# Greenlet spawned that receives messages from Redis pubsub channel,
# and sends them back to websocket.
try:
for message in self.pubsub.listen():
if message["type"] == "message":
self.socket.send(message["data"])
except WebSocketError:
pass
# This is the WSGI handler function we refer to
# when running gunicorn.
def serve(environ, start_response):
try:
socket = environ["wsgi.websocket"]
except KeyError:
return
WebSocketSession(socket, environ).run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment