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
Exemplo n.º 2
0
 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