import json import threading import importlib import six from django.conf import settings from django.http import HttpResponse from django.core.serializers.json import DjangoJSONEncoder from gripcontrol import HttpStreamFormat try: from urllib import quote except ImportError: from urllib.parse import quote tlocal = threading.local() def have_channels(): try: from channels.generic.http import AsyncHttpConsumer return True except ImportError: return False # return dict of (channel, last-id) def parse_last_event_id(s): out = {} parts = s.split(',') for part in parts: channel, last_id = part.split(':') out[channel] = last_id return out def make_id(ids): id_parts = [] for channel, id in six.iteritems(ids): enc_channel = quote(channel) id_parts.append('%s:%s' % (enc_channel, id)) return ','.join(id_parts) def build_id_escape(s): out = '' for c in s: if c == '%': out += '%%' else: out += c return out def sse_encode_event(event_type, data, event_id=None, escape=False): data_str = json.dumps(data, cls=DjangoJSONEncoder) if escape: event_type = build_id_escape(event_type) data_str = build_id_escape(data_str) out = 'event: %s\n' % event_type if event_id: out += 'id: %s\n' % event_id out += 'data: %s\n\n' % data_str return out def sse_error_response(condition, text, extra=None): if extra is None: extra = {} data = {'condition': condition, 'text': text} for k, v in six.iteritems(extra): data[k] = v body = sse_encode_event('stream-error', data, event_id='error') return HttpResponse(body, content_type='text/event-stream') def publish_event(channel, event_type, data, pub_id, pub_prev_id, skip_user_ids=None): from django_grip import publish if skip_user_ids is None: skip_user_ids = [] content_filters = [] if pub_id: event_id = '%I' content_filters.append('build-id') else: event_id = None content = sse_encode_event(event_type, data, event_id=event_id, escape=bool(pub_id)) meta = {} if skip_user_ids: meta['skip_users'] = ','.join(skip_user_ids) publish( 'events-%s' % quote(channel), HttpStreamFormat(content, content_filters=content_filters), id=pub_id, prev_id=pub_prev_id, meta=meta) def publish_kick(user_id, channel): from django_grip import publish msg = 'Permission denied to channels: %s' % channel data = {'condition': 'forbidden', 'text': msg, 'channels': [channel]} content = sse_encode_event('stream-error', data, event_id='error') meta = {'require_sub': 'events-%s' % channel} publish( 'user-%s' % user_id, HttpStreamFormat(content), id='kick-1', meta=meta) publish( 'user-%s' % user_id, HttpStreamFormat(close=True), id='kick-2', prev_id='kick-1', meta=meta) def load_class(name): at = name.rfind('.') if at == -1: raise ValueError('class name contains no \'.\'') module_name = name[0:at] class_name = name[at + 1:] return getattr(importlib.import_module(module_name), class_name)() # load and keep in thread local storage def get_class(name): if not hasattr(tlocal, 'loaded'): tlocal.loaded = {} c = tlocal.loaded.get(name) if c is None: c = load_class(name) tlocal.loaded[name] = c return c def get_class_from_setting(setting_name, default=None): if hasattr(settings, setting_name): return get_class(getattr(settings, setting_name)) elif default: return get_class(default) else: return None def get_storage(): return get_class_from_setting('EVENTSTREAM_STORAGE_CLASS') def get_channelmanager(): return get_class_from_setting( 'EVENTSTREAM_CHANNELMANAGER_CLASS', 'django_eventstream.channelmanager.DefaultChannelManager') def add_default_headers(headers): headers['Cache-Control'] = 'no-cache' headers['X-Accel-Buffering'] = 'no' augment_cors_headers(headers) def augment_cors_headers(headers): cors_origin = '' if hasattr(settings, 'EVENTSTREAM_ALLOW_ORIGIN'): cors_origin = settings.EVENTSTREAM_ALLOW_ORIGIN if cors_origin: headers['Access-Control-Allow-Origin'] = cors_origin allow_credentials = False if hasattr(settings, 'EVENTSTREAM_ALLOW_CREDENTIALS'): allow_credentials = settings.EVENTSTREAM_ALLOW_CREDENTIALS if allow_credentials: headers['Access-Control-Allow-Credentials'] = 'true'