forked from 0x2620/pandora
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
|
||||
a2enmod xsendfile
|
||||
a2enmod proxy_http
|
||||
a2enmod proxy_wstunnel
|
||||
cp /srv/pandora/etc/apache2/pandora.conf /etc/apache2/sites-available/pandora.conf
|
||||
a2ensite pandora
|
||||
|
||||
|
|
5
ctl
5
ctl
|
@ -1,4 +1,5 @@
|
|||
#!/bin/sh
|
||||
SERVICES="pandora pandora-tasks pandora-encoding pandora-cron pandora-websocketd"
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 (start|stop|restart|reload)"
|
||||
exit 1
|
||||
|
@ -17,7 +18,7 @@ if [ "$action" = "install" ]; then
|
|||
cp $BASE/etc/systemd/*.service /lib/systemd/system/
|
||||
cp $BASE/etc/tmpfiles.d/pandora.conf /usr/lib/tmpfiles.d/
|
||||
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
|
||||
done
|
||||
fi
|
||||
|
@ -28,6 +29,6 @@ if [ "$action" = "install" ]; then
|
|||
fi
|
||||
exit 0
|
||||
fi
|
||||
for service in pandora pandora-tasks pandora-encoding pandora-cron; do
|
||||
for service in $SERVICES; do
|
||||
service $service $action
|
||||
done
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
Alias /data /srv/pandora/data
|
||||
|
||||
ProxyPreserveHost On
|
||||
ProxyPass /api/ws/ ws://127.0.0.1:2622/ retry=0
|
||||
|
||||
ProxyPass / http://127.0.0.1:2620/
|
||||
ProxyPassReverse / http://127.0.0.1:2620/
|
||||
|
||||
|
|
|
@ -34,6 +34,19 @@ server {
|
|||
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 / {
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto http;
|
||||
|
|
|
@ -9,6 +9,7 @@ from django.db import models
|
|||
from ox.django import fields
|
||||
import ox
|
||||
|
||||
import websocket
|
||||
import managers
|
||||
|
||||
'''
|
||||
|
@ -34,6 +35,7 @@ def add_changelog(request, data, id=None):
|
|||
c.changeid = id or data.get('id')
|
||||
c.created = datetime.now()
|
||||
c.save()
|
||||
websocket.trigger_event('change', {'action': c.action, 'id': c.changeid})
|
||||
|
||||
class Log(models.Model):
|
||||
|
||||
|
|
|
@ -69,6 +69,9 @@ STATICFILES_FINDERS = (
|
|||
|
||||
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.
|
||||
TEMPLATE_LOADERS = (
|
||||
|
@ -129,6 +132,7 @@ INSTALLED_APPS = (
|
|||
'tv',
|
||||
'document',
|
||||
'entity',
|
||||
'websocket'
|
||||
)
|
||||
|
||||
# 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
|
||||
South
|
||||
requests>=2.0.0
|
||||
tornado==4.1
|
||||
|
|
|
@ -120,6 +120,8 @@ if __name__ == "__main__":
|
|||
]
|
||||
with open('pandora/local_settings.py', 'w') as f:
|
||||
f.write('\n'.join(local_settings))
|
||||
if old < 4947:
|
||||
run('./bin/pip', 'install', 'tornado==4.1')
|
||||
else:
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
|
|
Loading…
Reference in a new issue