Permalink
Switch branches/tags
Nothing to show
Find file
9089a17 Feb 4, 2016
96 lines (70 sloc) 2.35 KB
# -*- coding: utf-8 -*-
"""
Chat Server
===========
This simple application uses WebSockets to run a primitive chat server.
"""
import os
import logging
import redis
import gevent
from flask import Flask, render_template
from flask_sockets import Sockets
REDIS_URL = os.environ['REDIS_URL']
REDIS_CHAN = 'chat'
app = Flask(__name__)
app.debug = 'DEBUG' in os.environ
sockets = Sockets(app)
redis = redis.from_url(REDIS_URL)
class ChatBackend(object):
"""Interface for registering and updating WebSocket clients."""
def __init__(self):
self.clients = list()
self.pubsub = redis.pubsub()
self.pubsub.subscribe(REDIS_CHAN)
def __iter_data(self):
for message in self.pubsub.listen():
data = message.get('data')
if message['type'] == 'message':
app.logger.info(u'Sending message: {}'.format(data))
yield data
def register(self, client):
"""Register a WebSocket connection for Redis updates."""
self.clients.append(client)
def send(self, client, data):
"""Send given data to the registered client.
Automatically discards invalid connections."""
try:
client.send(data)
except Exception:
self.clients.remove(client)
def run(self):
"""Listens for new messages in Redis, and sends them to clients."""
for data in self.__iter_data():
for client in self.clients:
gevent.spawn(self.send, client, data)
def start(self):
"""Maintains Redis subscription in the background."""
gevent.spawn(self.run)
chats = ChatBackend()
chats.start()
@app.route('/')
def hello():
return render_template('index.html')
@sockets.route('/submit')
def inbox(ws):
"""Receives incoming chat messages, inserts them into Redis."""
while not ws.closed:
# Sleep to prevent *constant* context-switches.
gevent.sleep(0.1)
message = ws.receive()
if message:
app.logger.info(u'Inserting message: {}'.format(message))
redis.publish(REDIS_CHAN, message)
@sockets.route('/receive')
def outbox(ws):
"""Sends outgoing chat messages, via `ChatBackend`."""
chats.register(ws)
while not ws.closed:
# Context switch while `ChatBackend.start` is running in the background.
gevent.sleep(0.1)