add inital implementation for a websocket, disabled by default for now
This commit is contained in:
parent
7aa609c246
commit
e7f83f674e
15 changed files with 191 additions and 2 deletions
1
README
1
README
|
@ -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
5
ctl
|
@ -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
|
||||||
|
|
|
@ -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/
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
12
pandora/websocket/__init__.py
Normal file
12
pandora/websocket/__init__.py
Normal 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)
|
77
pandora/websocket/daemon.py
Normal file
77
pandora/websocket/daemon.py
Normal 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)
|
0
pandora/websocket/management/__init__.py
Normal file
0
pandora/websocket/management/__init__.py
Normal file
0
pandora/websocket/management/commands/__init__.py
Normal file
0
pandora/websocket/management/commands/__init__.py
Normal file
38
pandora/websocket/management/commands/websocketd.py
Normal file
38
pandora/websocket/management/commands/websocketd.py
Normal 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()
|
0
pandora/websocket/models.py
Normal file
0
pandora/websocket/models.py
Normal file
36
pandora/websocket/worker.py
Normal file
36
pandora/websocket/worker.py
Normal 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...')
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue