Last active
August 29, 2015 14:21
-
-
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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