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
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 isinstance(private_settings.WS4REDIS_PROCESS_REQUEST, six.string_types): import_string(private_settings.WS4REDIS_PROCESS_REQUEST)(request) elif 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)) elif private_settings.WS4REDIS_ALLOWED_CHANNELS is not None: try: mod, callback = private_settings.WS4REDIS_ALLOWED_CHANNELS.rsplit('.', 1) callback = getattr(import_module(mod), callback, None) if callable(callback): channels = list(callback(request, channels)) except AttributeError: pass websocket = self.upgrade_websocket(environ, start_response) self._websockets.add(websocket) 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_persisted_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: while True: parsed_msg = subscriber.parse_response() if parsed_msg is None: break sendmsg = RedisMessage(parsed_msg) 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) # Remove websocket from _websockets if closed if websocket.closed: self._websockets.remove(websocket) 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