def send(self, message, binary=False): """ Send a frame over the websocket with message as its payload """ if binary is None: binary = not isinstance(message, six.string_types) opcode = self.OPCODE_BINARY if binary else self.OPCODE_TEXT try: self.send_frame(message, opcode) except WebSocketError, e: logger.info('Socket is dead {}'.format(e))
def run(addr, port, wsgi_handler, ipv6=False, threading=False): """ Function to monkey patch the internal Django command: manage.py runserver """ logger.info("Websocket support is enabled") server_address = (addr, port) if not threading: raise Exception("Django's Websocket server must run with threading enabled") httpd_cls = type("WSGIServer", (socketserver.ThreadingMixIn, WSGIServer), {"daemon_threads": True}) httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6) httpd.set_app(wsgi_handler) httpd.serve_forever()
def run(addr, port, wsgi_handler, ipv6=False, threading=False): """ Function to monkey patch the internal Django command: manage.py runserver """ logger.info('Websocket support is enabled') server_address = (addr, port) if not threading: raise Exception('Django\'s Websocket server must run with threading enabled') httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, WSGIServer), { 'daemon_threads': True }) httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6) httpd.set_app(wsgi_handler) httpd.serve_forever()
def receive(self,clientip): """ Read and return a message from the stream. If `None` is returned, then the socket is considered closed/errored. """ if self._closed: raise WebSocketError("Connection is already closed") try: message= self.read_message() #deal with zombie messages try: msg = ZombieMessage(message,clientip) if 'heartbeat' in msg.get(): message = settings.WS4REDIS_HEARTBEAT ZombieWebsocket().heartbeat(msg) elif 'endpoint' in msg.get(): if msg.get()['endpoint'] == "register": ZombieWebsocket().register(msg) elif msg.get()['endpoint'] == "update": ZombieWebsocket().update(msg) except Exception: pass return message except UnicodeError as e: logger.info('websocket.receive: UnicodeError {}'.format(e)) self.close(1007) except WebSocketError as e: logger.info('websocket.receive: WebSocketError {}'.format(e)) self.close(1002) except Exception as e: pdb.set_trace() logger.info('websocket.receive: Unknown error {}'.format(e)) raise e
def receive(self): """ Read and return a message from the stream. If `None` is returned, then the socket is considered closed/errored. """ if self._closed: raise WebSocketError("Connection is already closed") try: return self.read_message() except UnicodeError as e: logger.info('websocket.receive: UnicodeError {}'.format(e)) self.close(1007) except WebSocketError as e: logger.info('websocket.receive: WebSocketError {}'.format(e)) self.close(1002) except Exception as e: logger.info('websocket.receive: Unknown error {}'.format(e)) raise e
def receive(self): """ Read and return a message from the stream. If `None` is returned, then the socket is considered closed/errored. """ if self._closed: raise WebSocketError("Connection is already closed") try: return self.read_message() except UnicodeError: logger.info('websocket.receive: UnicodeError') self.close(1007) except WebSocketError: logger.info('websocket.receive: WebSocketError') self.close(1002) except Exception as e: logger.info('websocket.receive: Unknown error %s', e) raise e
def __call__(self, environ, start_response): """ Hijack the main loop from the original thread and listen on events on Redis and Websockets""" websocket = None request = None subscriber = self.Subscriber(self._redis_connection) try: self.assure_protocol_requirements(environ) request = WSGIRequest(environ) self.process_request(request) channels, echo_message = self.process_subscriptions(request) if callable(private_settings.WS4REDIS_ALLOWED_CHANNELS): channels = list(private_settings.WS4REDIS_ALLOWED_CHANNELS(request, channels)) websocket = self.upgrade_websocket(environ, start_response) logger.debug('Subscribed to channels: {0}'.format(', '.join(channels))) subscriber.set_pubsub_channels(request, channels) subscriber.user_connect(request) websocket_fd = websocket.get_file_descriptor() listening_fds = [websocket_fd] redis_fd = subscriber.get_file_descriptor() if redis_fd: listening_fds.append(redis_fd) subscriber.send_persited_messages(websocket) recvmsg = None while websocket and not websocket.closed: ready = self.select(listening_fds, [], [], 4.0)[0] if not ready: # flush empty socket websocket.flush() for fd in ready: if fd == websocket_fd: recvmsg = RedisMessage(websocket.receive()) if recvmsg: subscriber.publish_message(recvmsg) elif fd == redis_fd: sendmsg = RedisMessage(subscriber.parse_response()) if sendmsg and (echo_message or sendmsg != recvmsg): websocket.send(sendmsg) else: logger.error('Invalid file descriptor: {0}'.format(fd)) if private_settings.WS4REDIS_HEARTBEAT: websocket.send(private_settings.WS4REDIS_HEARTBEAT) except WebSocketError as excpt: logger.warning('WebSocketError: ', exc_info=sys.exc_info()) response = http.HttpResponse(status=1001, content='Websocket Closed') except UpgradeRequiredError as excpt: logger.info('Websocket upgrade required') response = http.HttpResponseBadRequest(status=426, content=excpt) except HandshakeError as excpt: logger.warning('HandshakeError: ', exc_info=sys.exc_info()) response = http.HttpResponseBadRequest(content=excpt) except PermissionDenied as excpt: logger.warning('PermissionDenied: ', exc_info=sys.exc_info()) response = http.HttpResponseForbidden(content=excpt) except Exception as excpt: logger.error('Other Exception: ', exc_info=sys.exc_info()) response = http.HttpResponseServerError(content=excpt) else: response = http.HttpResponse() if websocket: subscriber.user_disconnect(request) websocket.close(code=1001, message='Websocket Closed') if hasattr(start_response, 'im_self') and not start_response.im_self.headers_sent: logger.warning('Staring late response on websocket') status_text = STATUS_CODE_TEXT.get(response.status_code, 'UNKNOWN STATUS CODE') status = '{0} {1}'.format(response.status_code, status_text) start_response(force_str(status), response._headers.values()) logger.info('Finish long living response with status code: '.format(response.status_code)) return response
def __call__(self, environ, start_response): """ Hijack the main loop from the original thread and listen on events on the Redis and the Websocket filedescriptors. """ websocket = None subscriber = self.Subscriber(self._redis_connection) try: self.assure_protocol_requirements(environ) request = WSGIRequest(environ) if callable(private_settings.WS4REDIS_PROCESS_REQUEST): private_settings.WS4REDIS_PROCESS_REQUEST(request) else: self.process_request(request) channels, echo_message = self.process_subscriptions(request) if callable(private_settings.WS4REDIS_ALLOWED_CHANNELS): channels = list(private_settings.WS4REDIS_ALLOWED_CHANNELS(request, channels)) websocket = self.upgrade_websocket(environ, start_response) logger.debug('Subscribed to channels: {0}'.format(', '.join(channels))) subscriber.set_pubsub_channels(request, channels) websocket_fd = websocket.get_file_descriptor() listening_fds = [websocket_fd] redis_fd = subscriber.get_file_descriptor() if redis_fd: listening_fds.append(redis_fd) subscriber.send_persited_messages(websocket) recvmsg = None while websocket and not websocket.closed: ready = self.select(listening_fds, [], [], 4.0)[0] if not ready: # flush empty socket websocket.flush() for fd in ready: if fd == websocket_fd: recvmsg = RedisMessage(websocket.receive()) if recvmsg: subscriber.publish_message(recvmsg) elif fd == redis_fd: sendmsg = RedisMessage(subscriber.parse_response()) if sendmsg and (echo_message or sendmsg != recvmsg): websocket.send(sendmsg) else: logger.error('Invalid file descriptor: {0}'.format(fd)) # Check again that the websocket is closed before sending the heartbeat, # because the websocket can closed previously in the loop. if private_settings.WS4REDIS_HEARTBEAT and not websocket.closed: websocket.send(private_settings.WS4REDIS_HEARTBEAT) except WebSocketError as excpt: logger.warning('WebSocketError: {}'.format(excpt), exc_info=sys.exc_info()) response = http.HttpResponse(status=1001, content='Websocket Closed') except UpgradeRequiredError as excpt: logger.info('Websocket upgrade required') response = http.HttpResponseBadRequest(status=426, content=excpt) except HandshakeError as excpt: logger.warning('HandshakeError: {}'.format(excpt), exc_info=sys.exc_info()) response = http.HttpResponseBadRequest(content=excpt) except PermissionDenied as excpt: logger.warning('PermissionDenied: {}'.format(excpt), exc_info=sys.exc_info()) response = http.HttpResponseForbidden(content=excpt) except Exception as excpt: logger.error('Other Exception: {}'.format(excpt), exc_info=sys.exc_info()) response = http.HttpResponseServerError(content=excpt) else: response = http.HttpResponse() finally: subscriber.release() if websocket: websocket.close(code=1001, message='Websocket Closed') else: logger.warning('Starting late response on websocket') status_text = http_client.responses.get(response.status_code, 'UNKNOWN STATUS CODE') status = '{0} {1}'.format(response.status_code, status_text) headers = response._headers.values() if six.PY3: headers = list(headers) start_response(force_str(status), headers) logger.info('Finish non-websocket response with status code: {}'.format(response.status_code)) return response
def __call__(self, environ, start_response): """ Hijack the main loop from the original thread and listen on events on the Redis and the Websocket filedescriptors. """ websocket = None subscriber = self.Subscriber(self._redis_connection) try: self.assure_protocol_requirements(environ) request = WSGIRequest(environ) if callable(private_settings.WS4REDIS_PROCESS_REQUEST): private_settings.WS4REDIS_PROCESS_REQUEST(request) else: self.process_request(request) channels, echo_message = self.process_subscriptions(request) if callable(private_settings.WS4REDIS_ALLOWED_CHANNELS): channels = list(private_settings.WS4REDIS_ALLOWED_CHANNELS(request, channels)) websocket = self.upgrade_websocket(environ, start_response) logger.debug('Subscribed to channels: {0}'.format(', '.join(channels))) subscriber.set_pubsub_channels(request, channels) websocket_fd = websocket.get_file_descriptor() listening_fds = [websocket_fd] redis_fd = subscriber.get_file_descriptor() if redis_fd: listening_fds.append(redis_fd) subscriber.send_persited_messages(websocket) recvmsg = None while websocket and not websocket.closed: ready = self.select(listening_fds, [], [], 4.0)[0] if not ready: # flush empty socket websocket.flush() for fd in ready: if fd == websocket_fd: recvmsg = RedisMessage(websocket.receive()) if recvmsg: subscriber.publish_message(recvmsg) elif fd == redis_fd: sendmsg = RedisMessage(subscriber.parse_response()) if sendmsg and (echo_message or sendmsg != recvmsg): websocket.send(sendmsg) else: logger.error('Invalid file descriptor: {0}'.format(fd)) if private_settings.WS4REDIS_HEARTBEAT: websocket.send(private_settings.WS4REDIS_HEARTBEAT) except WebSocketError as excpt: logger.warning('WebSocketError: {}'.format(excpt), exc_info=sys.exc_info()) response = http.HttpResponse(status=1001, content='Websocket Closed') except UpgradeRequiredError as excpt: logger.info('Websocket upgrade required') response = http.HttpResponseBadRequest(status=426, content=excpt) except HandshakeError as excpt: logger.warning('HandshakeError: {}'.format(excpt), exc_info=sys.exc_info()) response = http.HttpResponseBadRequest(content=excpt) except PermissionDenied as excpt: logger.warning('PermissionDenied: {}'.format(excpt), exc_info=sys.exc_info()) response = http.HttpResponseForbidden(content=excpt) except Exception as excpt: logger.error('Other Exception: {}'.format(excpt), exc_info=sys.exc_info()) response = http.HttpResponseServerError(content=excpt) else: response = http.HttpResponse() finally: subscriber.release() if websocket: websocket.close(code=1001, message='Websocket Closed') else: logger.warning('Starting late response on websocket') status_text = STATUS_CODE_TEXT.get(response.status_code, 'UNKNOWN STATUS CODE') status = '{0} {1}'.format(response.status_code, status_text) start_response(force_str(status), response._headers.values()) logger.info('Finish non-websocket response with status code: {}'.format(response.status_code)) return response
class WebsocketWSGIServer(object): def __init__(self, redis_connection=None): """ redis_connection can be overriden by a mock object. """ comps = str(redis_settings.WS4REDIS_STORE).split('.') module = import_module('.'.join(comps[:-1])) RedisStore = getattr(module, comps[-1]) self.allowed_channels = RedisStore.subscription_channels + RedisStore.publish_channels self._redis_connection = redis_connection and redis_connection or StrictRedis( **redis_settings.WS4REDIS_CONNECTION) self.RedisStore = RedisStore def assure_protocol_requirements(self, environ): if environ.get('REQUEST_METHOD') != 'GET': raise HandshakeError('HTTP method must be a GET') if environ.get('SERVER_PROTOCOL') != 'HTTP/1.1': raise HandshakeError('HTTP server protocol must be 1.1') if environ.get('HTTP_UPGRADE', '').lower() != 'websocket': raise HandshakeError( 'Client does not wish to upgrade to a websocket') def process_request(self, request): request.session = None request.user = None if 'django.contrib.sessions.middleware.SessionMiddleware' in settings.MIDDLEWARE_CLASSES: engine = import_module(settings.SESSION_ENGINE) session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None) if session_key: request.session = engine.SessionStore(session_key) if 'django.contrib.auth.middleware.AuthenticationMiddleware' in settings.MIDDLEWARE_CLASSES: from django.contrib.auth import get_user request.user = SimpleLazyObject(lambda: get_user(request)) def process_subscriptions(self, request): requested_channels = [p.strip().lower() for p in request.GET] agreed_channels = [ p for p in requested_channels if p in self.allowed_channels ] return agreed_channels def __call__(self, environ, start_response): """ Hijack the main loop from the original thread and listen on events on Redis and Websockets""" websocket = None redis_store = self.RedisStore(self._redis_connection) try: self.assure_protocol_requirements(environ) request = WSGIRequest(environ) self.process_request(request) channels = self.process_subscriptions(request) websocket = self.upgrade_websocket(environ, start_response) logger.debug('Subscribed to channels: {0}'.format( ', '.join(channels))) redis_store.subscribe_channels(request, channels) websocket_fd = websocket.get_file_descriptor() listening_fds = [websocket_fd] redis_fd = redis_store.get_file_descriptor() if redis_fd: listening_fds.append(redis_fd) redis_store.send_persited_messages(websocket) while websocket and not websocket.closed: ready = self.select(listening_fds, [], [], 4.0)[0] if not ready: # flush empty socket websocket.flush() for fd in ready: if fd == websocket_fd: message = websocket.receive() redis_store.publish_message(message) elif fd == redis_fd: response = redis_store.parse_response() if response[0] == 'message': message = response[2] websocket.send(message) else: logger.error('Invalid file descriptor: {0}'.format(fd)) except WebSocketError, excpt: logger.warning('WebSocketError: ', exc_info=sys.exc_info()) response = HttpResponse(status=1001, content='Websocket Closed') except UpgradeRequiredError: logger.info('Websocket upgrade required') response = HttpResponseBadRequest(status=426, content=excpt)
if response[0] == 'message': message = response[2] websocket.send(message) else: logger.error('Invalid file descriptor: {0}'.format(fd)) if redis_settings.WS4REDIS_HEARTBEAT: websocket.send(redis_settings.WS4REDIS_HEARTBEAT) except WebSocketError, excpt: logger.warning('WebSocketError: ', exc_info=sys.exc_info()) response = HttpResponse(status=1001, content='Websocket Closed') except UpgradeRequiredError: logger.info('Websocket upgrade required') response = HttpResponseBadRequest(status=426, content=excpt) except HandshakeError, excpt: logger.warning('HandshakeError: ', exc_info=sys.exc_info()) response = HttpResponseBadRequest(content=excpt) except Exception, excpt: logger.error('Other Exception: ', exc_info=sys.exc_info()) response = HttpResponseServerError(content=excpt) else: response = HttpResponse() if websocket: websocket.close(code=1001, message='Websocket Closed') if hasattr(start_response, 'im_self') and not start_response.im_self.headers_sent: logger.warning('Staring late response on websocket') status_text = STATUS_CODE_TEXT.get(response.status_code, 'UNKNOWN STATUS CODE') status = '{0} {1}'.format(response.status_code, status_text) start_response(force_str(status), response._headers.values()) logger.info('Finish long living response with status code: '.format(response.status_code)) return response
else: logger.error('Invalid file descriptor: {0}'.format(fd)) if redis_settings.WS4REDIS_HEARTBEAT: websocket.send(redis_settings.WS4REDIS_HEARTBEAT) except WebSocketError, excpt: logger.warning('WebSocketError: ', exc_info=sys.exc_info()) response = HttpResponse(status=1001, content='Websocket Closed') except UpgradeRequiredError, excpt: logger.info('Websocket upgrade required') response = HttpResponseBadRequest(status=426, content=excpt) except HandshakeError, excpt: logger.warning('HandshakeError: ', exc_info=sys.exc_info()) response = HttpResponseBadRequest(content=excpt) except Exception, excpt: logger.error('Other Exception: ', exc_info=sys.exc_info()) response = HttpResponseServerError(content=excpt) else: response = HttpResponse() if websocket: websocket.close(code=1001, message='Websocket Closed') if hasattr(start_response, 'im_self') and not start_response.im_self.headers_sent: logger.warning('Staring late response on websocket') status_text = STATUS_CODE_TEXT.get(response.status_code, 'UNKNOWN STATUS CODE') status = '{0} {1}'.format(response.status_code, status_text) start_response(force_str(status), response._headers.values()) logger.info('Finish long living response with status code: '.format( response.status_code)) return response