forked from bag-of-projects/python-websockets-chat
-
Notifications
You must be signed in to change notification settings - Fork 0
/
chat.py
95 lines (70 loc) · 2.35 KB
/
chat.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# -*- 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)