add inital implementation for a websocket, disabled by default for now

This commit is contained in:
j 2015-04-28 23:05:15 +05:30
parent 7aa609c246
commit e7f83f674e
15 changed files with 191 additions and 2 deletions

1
README
View file

@ -121,6 +121,7 @@ b) apache2 (if you need it for other sites on the same server)
apt-get install apache2-mpm-prefork libapache2-mod-xsendfile apt-get install apache2-mpm-prefork libapache2-mod-xsendfile
a2enmod xsendfile a2enmod xsendfile
a2enmod proxy_http a2enmod proxy_http
a2enmod proxy_wstunnel
cp /srv/pandora/etc/apache2/pandora.conf /etc/apache2/sites-available/pandora.conf cp /srv/pandora/etc/apache2/pandora.conf /etc/apache2/sites-available/pandora.conf
a2ensite pandora a2ensite pandora

5
ctl
View file

@ -1,4 +1,5 @@
#!/bin/sh #!/bin/sh
SERVICES="pandora pandora-tasks pandora-encoding pandora-cron pandora-websocketd"
if [ -z "$1" ]; then if [ -z "$1" ]; then
echo "Usage: $0 (start|stop|restart|reload)" echo "Usage: $0 (start|stop|restart|reload)"
exit 1 exit 1
@ -17,7 +18,7 @@ if [ "$action" = "install" ]; then
cp $BASE/etc/systemd/*.service /lib/systemd/system/ cp $BASE/etc/systemd/*.service /lib/systemd/system/
cp $BASE/etc/tmpfiles.d/pandora.conf /usr/lib/tmpfiles.d/ cp $BASE/etc/tmpfiles.d/pandora.conf /usr/lib/tmpfiles.d/
systemd-tmpfiles --create /usr/lib/tmpfiles.d/pandora.conf >/dev/null || true systemd-tmpfiles --create /usr/lib/tmpfiles.d/pandora.conf >/dev/null || true
for service in pandora pandora-tasks pandora-encoding pandora-cron; do for service in $SERVICES; do
systemctl enable ${service}.service systemctl enable ${service}.service
done done
fi fi
@ -28,6 +29,6 @@ if [ "$action" = "install" ]; then
fi fi
exit 0 exit 0
fi fi
for service in pandora pandora-tasks pandora-encoding pandora-cron; do for service in $SERVICES; do
service $service $action service $service $action
done done

View file

@ -34,6 +34,8 @@
Alias /data /srv/pandora/data Alias /data /srv/pandora/data
ProxyPreserveHost On ProxyPreserveHost On
ProxyPass /api/ws/ ws://127.0.0.1:2622/ retry=0
ProxyPass / http://127.0.0.1:2620/ ProxyPass / http://127.0.0.1:2620/
ProxyPassReverse / http://127.0.0.1:2620/ ProxyPassReverse / http://127.0.0.1:2620/

View file

@ -34,6 +34,19 @@ server {
root /srv/pandora; root /srv/pandora;
} }
location /api/ws/ {
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_read_timeout 999999999;
if (!-f $request_filename) {
proxy_pass http://127.0.0.1:2622;
break;
}
}
location / { location / {
proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto http; proxy_set_header X-Forwarded-Proto http;

View file

@ -9,6 +9,7 @@ from django.db import models
from ox.django import fields from ox.django import fields
import ox import ox
import websocket
import managers import managers
''' '''
@ -34,6 +35,7 @@ def add_changelog(request, data, id=None):
c.changeid = id or data.get('id') c.changeid = id or data.get('id')
c.created = datetime.now() c.created = datetime.now()
c.save() c.save()
websocket.trigger_event('change', {'action': c.action, 'id': c.changeid})
class Log(models.Model): class Log(models.Model):

View file

@ -69,6 +69,9 @@ STATICFILES_FINDERS = (
GEOIP_PATH = normpath(join(PROJECT_ROOT, '..', 'data', 'geo')) GEOIP_PATH = normpath(join(PROJECT_ROOT, '..', 'data', 'geo'))
WEBSOCKET = False
WEBSOCKET_PORT = 2622
WEBSOCKET_ADDRESS = '127.0.0.1'
# List of callables that know how to import templates from various sources. # List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = ( TEMPLATE_LOADERS = (
@ -129,6 +132,7 @@ INSTALLED_APPS = (
'tv', 'tv',
'document', 'document',
'entity', 'entity',
'websocket'
) )
# Log errors into db # Log errors into db

View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from celery.execute import send_task
from django.conf import settings
key = 'websocket'
def trigger_event(event, data):
if settings.WEBSOCKET:
send_task('trigger_event', [event, data], exchange=key, routing_key=key)

View file

@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
import json
from threading import Thread
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application
from tornado.websocket import WebSocketHandler
import logging
logger = logging.getLogger('pandora.websocket')
sockets = []
class Daemon(Thread):
def __init__(self, port, address):
self.port = port
self.address = address
Thread.__init__(self)
self.daemon = True
self.start()
def join(self):
self.main.stop()
def run(self):
options = {
'debug': False,
'gzip': False
}
handlers = [
(r'/(.*)', Handler),
]
self.http_server = HTTPServer(Application(handlers, **options))
self.main = IOLoop.instance()
self.http_server.listen(self.port, self.address)
self.main.start()
class Handler(WebSocketHandler):
'''
def check_origin(self, origin):
# bypass same origin check
return True
'''
def open(self, path):
if self not in sockets:
sockets.append(self)
#websocket calls
def on_close(self):
if self in sockets:
sockets.remove(self)
def on_message(self, message):
pass
#logger.debug('got message %s', message)
def post(self, event, data):
message = json.dumps([event, data])
main = IOLoop.instance()
main.add_callback(lambda: self.write_message(message))
def trigger_event(event, data):
logger.debug('trigger event %s %s to %s clients', event, data, len(sockets))
main = IOLoop.instance()
message = json.dumps([event, data])
for ws in sockets:
try:
main.add_callback(lambda: ws.write_message(message))
except:
logger.debug('failed to send to ws %s %s %s', ws, event, data, exc_info=1)

View file

View file

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from __future__ import absolute_import
import os
from optparse import make_option
from django.core.management.base import BaseCommand
from django.conf import settings
from ... import daemon, worker
import logging
class Command(BaseCommand):
"""
"""
help = 'run websocket daemon'
args = ''
option_list = BaseCommand.option_list + (
make_option('--debug',
action='store_true',
dest='debug',
default=False,
help='enable debug'),
make_option("--pidfile", dest="pidfile",metavar="PIDFILE"),
)
def handle(self, **options):
socket = daemon.Daemon(settings.WEBSOCKET_PORT, settings.WEBSOCKET_ADDRESS)
if options['debug']:
logging.basicConfig(level=logging.DEBUG)
if options['pidfile']:
with open(options['pidfile'], 'w') as pid:
pid.write('%s' % os.getpid())
worker.run()
socket.join()

View file

View file

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from __future__ import absolute_import
from django.conf import settings
from kombu import Connection, Exchange, Queue
from kombu.mixins import ConsumerMixin
from . import daemon, key
queue = Queue('websocket', Exchange(key, type='direct'), routing_key=key)
class Worker(ConsumerMixin):
def __init__(self, connection):
self.connection = connection
def get_consumers(self, Consumer, channel):
return [Consumer(queues=queue,
accept=['pickle', 'json'],
callbacks=[self.process_task])]
def process_task(self, body, message):
if body['task'] == 'trigger_event':
daemon.trigger_event(*body['args'])
message.ack()
def run():
with Connection(settings.BROKER_URL) as conn:
try:
worker = Worker(conn)
worker.run()
except KeyboardInterrupt:
print('shutting down...')

View file

@ -14,3 +14,4 @@ gunicorn>=0.14.3,<0.19
html5lib html5lib
South South
requests>=2.0.0 requests>=2.0.0
tornado==4.1

View file

@ -120,6 +120,8 @@ if __name__ == "__main__":
] ]
with open('pandora/local_settings.py', 'w') as f: with open('pandora/local_settings.py', 'w') as f:
f.write('\n'.join(local_settings)) f.write('\n'.join(local_settings))
if old < 4947:
run('./bin/pip', 'install', 'tornado==4.1')
else: else:
if len(sys.argv) == 1: if len(sys.argv) == 1: