Exemple #1
0
    def rt_request(self, request):
        """Handle a Django-RT internal API request."""

        # Check client IP is allowed
        if not settings.DEBUG and settings.RT_COURIER_IPS: 
            if request.META['REMOTE_ADDR'] not in settings.RT_COURIER_IPS:
                return HttpResponseForbidden()

        # Deserialize ResourceRequest from body and verify signature
        #! TODO since the request is still considered unauthorized at this point, add exception handling here for bad data:
        #! * utf-8 errors
        #! * json errors
        #! * data errors
        #! * bad signature
        body = request.body.decode('utf-8')
        res_req = ResourceRequest.from_json(body)
        res_req.path = self.rt_get_path(request)
        res_req.verify_signature()

        # Open Redis connection
        redis_conn = redis.StrictRedis(
            host=settings.RT_REDIS_HOST,
            port=settings.RT_REDIS_PORT,
            db=settings.RT_REDIS_DB,
            password=settings.RT_REDIS_PASSWORD
        )

        # Get subscription status
        sub_key = get_subscription_key(res_req.sub_id)
        sub_status = redis_conn.get(sub_key).decode('utf-8')
        if not sub_status:
            return HttpResponseBadRequest('Invalid subscription ID')

        if res_req.action == 'subscribe':
            # Handle subscription request

            # Check subscription has 'requested' status
            if sub_status != 'requested':
                return HttpResponseBadRequest('Invalid subscription ID')

            # Check subscription is allowed
            if self.rt_get_permission('subscribe', request) is not True:
                return HttpResponseForbidden()

            # Set subscription status to 'granted'
            result = redis_conn.set(sub_key, 'granted')
            assert result

            # Return Resource object
            res = self.rt_get_resource(request)
            return JsonResponse(res.serialize(),
                content_type=Resource.CONTENT_TYPE+'; charset=utf-8'
            )
        else:
            # Shouldn't ever land here
            assert False
Exemple #2
0
    def cleanup_request(self, sub_id, redis_conn, redis_subscription):
        logger.debug('Connection closed; cleaning up')

        # Close existing Redis connection
        if redis_conn:
            redis_conn.close()

        # Ensure subscription key is removed
        if sub_id:
            # Open new connection (this is necessary as asyncio_redis won't allow the DEL command to run after a subscription)
            redis_conn = yield from asyncio_redis.Connection.create(
                host=settings.RT_REDIS_HOST,
                port=settings.RT_REDIS_PORT,
                db=settings.RT_REDIS_DB,
                password=settings.RT_REDIS_PASSWORD)

            # Remove key
            res = yield from redis_conn.delete([get_subscription_key(sub_id)])
            if res:
                logger.debug('Removed subscription %s' % (sub_id, ))

            # Close connection
            redis_conn.close()
    def cleanup_request(self, sub_id, redis_conn, redis_subscription):
        logger.debug('Connection closed; cleaning up')

        # Close existing Redis connection
        if redis_conn:
            redis_conn.close()

        # Ensure subscription key is removed
        if sub_id:
            # Open new connection (this is necessary as asyncio_redis won't allow the DEL command to run after a subscription)
            redis_conn = yield from asyncio_redis.Connection.create(
                host=settings.RT_REDIS_HOST,
                port=settings.RT_REDIS_PORT,
                db=settings.RT_REDIS_DB,
                password=settings.RT_REDIS_PASSWORD
            )

            # Remove key
            res = yield from redis_conn.delete([get_subscription_key(sub_id)])
            if res:
                logger.debug('Removed subscription %s' % (sub_id,))

            # Close connection
            redis_conn.close()
Exemple #4
0
    def handle_sse(self, path, suffix, env, start_response):
        res_path = path

        # Append slash to resource path if URL ends with slash
        if suffix.endswith('/'):
            res_path += '/'

        req_hdrs = self.get_headers(env)

        # Check route is a Django-RT resource
        try:
            logger.debug('Verifying %s is an RT resource' % (res_path, ))
            verify_resource_view(res_path)
        except NotAnRtResourceError:
            logger.debug('Not an RT resource; aborting')
            start_response(self.full_status(406), [])
            return [b'']
        except ResourceError as e:
            logger.debug('Caught ResourceError; aborting')
            start_response(self.full_status(e.status), [])
            return [b'']

        # Connect to Redis server
        redis_conn = redis.StrictRedis(host=settings.RT_REDIS_HOST,
                                       port=settings.RT_REDIS_PORT,
                                       db=settings.RT_REDIS_DB,
                                       password=settings.RT_REDIS_PASSWORD)

        # Create subscription
        while True:
            sub_id = generate_subscription_id()
            result = redis_conn.setnx(get_subscription_key(sub_id),
                                      'requested')
            if result:
                break
        logger.debug('Created subscription ID: %s' % (sub_id, ))

        # Request resource from Django API
        try:
            logger.debug('Requesting subscription for %s' % (res_path, ))
            res = self.request_resource(path, sub_id, req_hdrs)
        except NotAnRtResourceError:
            logger.debug(
                "Subscription denied: not an rt resource. This shouldn't happen..."
            )
            start_response(self.full_status(406), [])
            return [b'']
        except ResourceError as e:
            logger.debug('Subscription denied: HTTP error %d' % (e.status, ))
            start_response(self.full_status(e.status), [])
            return [b'']

        # Check subscription status and change to 'subscribed'
        sub_key = get_subscription_key(sub_id)
        sub_status = redis_conn.get(sub_key).decode('utf-8')
        assert sub_status == 'granted'
        logger.debug('Subscription granted')
        if redis_conn.set(sub_key, 'subscribed'):
            logger.debug('Subscription %s status changed to "subscribed"' %
                         (sub_id, ))

        # Delete subscription key (not currently used for anything else)
        redis_conn.delete(sub_key)

        # Subscribe to Redis channel
        pubsub = redis_conn.pubsub()
        pubsub.subscribe(get_full_channel_name(res.channel))

        # Prepare response
        hdrs = {'Content-Type': 'text/event-stream'}
        cors_hdrs = get_cors_headers(req_hdrs.get('ORIGIN', None))
        hdrs.update(cors_hdrs)
        start_response('200 OK', [hdr for hdr in hdrs.items()])

        # Loop
        first_event = True
        while True:
            # Wait for event on channel
            msg = pubsub.get_message(
                timeout=settings.RT_SSE_HEARTBEAT if settings.
                RT_SSE_HEARTBEAT else (10 * 60.0))
            if msg:
                if msg['type'] == 'message':
                    # Deserialize ResourceEvent
                    event_json = msg['data'].decode('utf-8')
                    event = ResourceEvent.from_json(event_json)

                    # Create SSE event
                    sse_evt = SseEvent.from_resource_event(event)

                    # Send 'retry' field on first SSE event delivered
                    if first_event:
                        sse_evt.retry = settings.RT_SSE_RETRY
                        first_event = False

                    # Send SSE event to client
                    yield sse_evt.as_utf8()
            else:
                # Timeout, send SSE heartbeat if necessary
                if settings.RT_SSE_HEARTBEAT:
                    yield SseHeartbeat().as_utf8()
    def handle_sse(self, path, suffix, env, start_response):
        res_path = path

        # Append slash to resource path if URL ends with slash
        if suffix.endswith("/"):
            res_path += "/"

        req_hdrs = self.get_headers(env)

        # Check route is a Django-RT resource
        try:
            logger.debug("Verifying %s is an RT resource" % (res_path,))
            verify_resource_view(res_path)
        except NotAnRtResourceError:
            logger.debug("Not an RT resource; aborting")
            start_response(self.full_status(406), [])
            return [b""]
        except ResourceError as e:
            logger.debug("Caught ResourceError; aborting")
            start_response(self.full_status(e.status), [])
            return [b""]

        # Connect to Redis server
        redis_conn = redis.StrictRedis(
            host=settings.RT_REDIS_HOST,
            port=settings.RT_REDIS_PORT,
            db=settings.RT_REDIS_DB,
            password=settings.RT_REDIS_PASSWORD,
        )

        # Create subscription
        while True:
            sub_id = generate_subscription_id()
            result = redis_conn.setnx(get_subscription_key(sub_id), "requested")
            if result:
                break
        logger.debug("Created subscription ID: %s" % (sub_id,))

        # Request resource from Django API
        try:
            logger.debug("Requesting subscription for %s" % (res_path,))
            res = self.request_resource(path, sub_id, req_hdrs)
        except NotAnRtResourceError:
            logger.debug("Subscription denied: not an rt resource. This shouldn't happen...")
            start_response(self.full_status(406), [])
            return [b""]
        except ResourceError as e:
            logger.debug("Subscription denied: HTTP error %d" % (e.status,))
            start_response(self.full_status(e.status), [])
            return [b""]

        # Check subscription status and change to 'subscribed'
        sub_key = get_subscription_key(sub_id)
        sub_status = redis_conn.get(sub_key).decode("utf-8")
        assert sub_status == "granted"
        logger.debug("Subscription granted")
        if redis_conn.set(sub_key, "subscribed"):
            logger.debug('Subscription %s status changed to "subscribed"' % (sub_id,))

        # Delete subscription key (not currently used for anything else)
        redis_conn.delete(sub_key)

        # Subscribe to Redis channel
        pubsub = redis_conn.pubsub()
        pubsub.subscribe(get_full_channel_name(res.channel))

        # Prepare response
        hdrs = {"Content-Type": "text/event-stream"}
        cors_hdrs = get_cors_headers(req_hdrs.get("ORIGIN", None))
        hdrs.update(cors_hdrs)
        start_response("200 OK", [hdr for hdr in hdrs.items()])

        # Loop
        first_event = True
        while True:
            # Wait for event on channel
            msg = pubsub.get_message(timeout=settings.RT_SSE_HEARTBEAT if settings.RT_SSE_HEARTBEAT else (10 * 60.0))
            if msg:
                if msg["type"] == "message":
                    # Deserialize ResourceEvent
                    event_json = msg["data"].decode("utf-8")
                    event = ResourceEvent.from_json(event_json)

                    # Create SSE event
                    sse_evt = SseEvent.from_resource_event(event)

                    # Send 'retry' field on first SSE event delivered
                    if first_event:
                        sse_evt.retry = settings.RT_SSE_RETRY
                        first_event = False

                    # Send SSE event to client
                    yield sse_evt.as_utf8()
            else:
                # Timeout, send SSE heartbeat if necessary
                if settings.RT_SSE_HEARTBEAT:
                    yield SseHeartbeat().as_utf8()
    def handle_sse(self, request):
        res_path = request.match_info.get('resource')
        res_path = '/' + res_path

        # Append slash to resource path if URL ends with slash
        suffix = request.match_info.get('suffix')
        if suffix.endswith('/'):
            res_path += '/'

        redis_conn = None
        redis_subscription = None
        sub_id = None
        try:
            # Check route is a Django-RT resource
            try:
                logger.debug('Verifying %s is an RT resource' % (res_path,))
                verify_resource_view(res_path)
            except NotAnRtResourceError:
                logger.debug('Not an RT resource; aborting')
                return web.Response(status=406)
            except ResourceError as e:
                logger.debug('Caught ResourceError; aborting')
                return web.Response(status=e.status)

            # Connect to Redis server
            redis_conn = yield from asyncio_redis.Connection.create(
                host=settings.RT_REDIS_HOST,
                port=settings.RT_REDIS_PORT,
                db=settings.RT_REDIS_DB,
                password=settings.RT_REDIS_PASSWORD
            )

            # Create subscription
            while True:
                sub_id = generate_subscription_id()
                result = yield from redis_conn.setnx(get_subscription_key(sub_id), 'requested')
                if result:
                    break
            logger.debug('Created subscription ID: %s' % (sub_id,))

            # Request resource from Django API
            try:
                logger.debug('Requesting subscription for %s' % (res_path,))
                res = yield from self.request_resource(res_path, request, sub_id)
            except NotAnRtResourceError:
                logger.debug("Subscription denied: not an rt resource. This shouldn't happen...")
                return web.Response(status=406)
            except ResourceError as e:
                logger.debug('Subscription denied: HTTP error %d' % (e.status,))
                return web.Response(status=e.status)

            # Check subscription status and change to 'subscribed'
            sub_key = get_subscription_key(sub_id)
            sub_status = yield from redis_conn.get(sub_key)
            assert sub_status == 'granted'
            logger.debug('Subscription granted')
            result = yield from redis_conn.set(sub_key, 'subscribed')
            if result:
                logger.debug('Subscription %s status changed to "subscribed"' % (sub_id,))

            # Delete subscription key (not currently used for anything else)
            yield from redis_conn.delete([sub_key])

            # Subscribe to Redis channel
            chan = get_full_channel_name(res.channel)
            logger.debug('Subscribing to Redis channel %s' % (chan,))
            redis_subscription = yield from redis_conn.start_subscribe()
            yield from redis_subscription.subscribe([ chan ])

            # Prepare response
            response = web.StreamResponse()
            response.content_type = 'text/event-stream'
            cors_hdrs = get_cors_headers(request.headers.get('Origin', None))
            response.headers.update(cors_hdrs)
            yield from response.prepare(request)

            # Loop
            first_event = True
            while True:
                # Wait for event on channel
                try:
                    reply = yield from asyncio.wait_for(
                        redis_subscription.next_published(),
                        settings.RT_SSE_HEARTBEAT
                    )
                except asyncio.TimeoutError:
                    # Timeout, send SSE heartbeat
                    response.write(SseHeartbeat().as_utf8()) 
                    yield from response.drain()
                else:
                    # Deserialize ResourceEvent
                    event = ResourceEvent.from_json(reply.value)

                    # Create SSE event
                    sse_evt = SseEvent.from_resource_event(event)

                    # Send 'retry' field on first SSE event delivered
                    if first_event:
                        sse_evt.retry = settings.RT_SSE_RETRY
                        first_event = False

                    # Send SSE event to client
                    response.write(sse_evt.as_utf8())
                    yield from response.drain()

            yield from response.write_eof()
            return response
        finally:
            # Cleanup
            asyncio.async(self.cleanup_request(sub_id, redis_conn, redis_subscription))
Exemple #7
0
    def handle_sse(self, request):
        res_path = request.match_info.get('resource')
        res_path = '/' + res_path

        # Append slash to resource path if URL ends with slash
        suffix = request.match_info.get('suffix')
        if suffix.endswith('/'):
            res_path += '/'

        redis_conn = None
        redis_subscription = None
        sub_id = None
        try:
            # Check route is a Django-RT resource
            try:
                logger.debug('Verifying %s is an RT resource' % (res_path, ))
                verify_resource_view(res_path)
            except NotAnRtResourceError:
                logger.debug('Not an RT resource; aborting')
                return web.Response(status=406)
            except ResourceError as e:
                logger.debug('Caught ResourceError; aborting')
                return web.Response(status=e.status)

            # Connect to Redis server
            redis_conn = yield from asyncio_redis.Connection.create(
                host=settings.RT_REDIS_HOST,
                port=settings.RT_REDIS_PORT,
                db=settings.RT_REDIS_DB,
                password=settings.RT_REDIS_PASSWORD)

            # Create subscription
            while True:
                sub_id = generate_subscription_id()
                result = yield from redis_conn.setnx(
                    get_subscription_key(sub_id), 'requested')
                if result:
                    break
            logger.debug('Created subscription ID: %s' % (sub_id, ))

            # Request resource from Django API
            try:
                logger.debug('Requesting subscription for %s' % (res_path, ))
                res = yield from self.request_resource(res_path, request,
                                                       sub_id)
            except NotAnRtResourceError:
                logger.debug(
                    "Subscription denied: not an rt resource. This shouldn't happen..."
                )
                return web.Response(status=406)
            except ResourceError as e:
                logger.debug('Subscription denied: HTTP error %d' %
                             (e.status, ))
                return web.Response(status=e.status)

            # Check subscription status and change to 'subscribed'
            sub_key = get_subscription_key(sub_id)
            sub_status = yield from redis_conn.get(sub_key)
            assert sub_status == 'granted'
            logger.debug('Subscription granted')
            result = yield from redis_conn.set(sub_key, 'subscribed')
            if result:
                logger.debug('Subscription %s status changed to "subscribed"' %
                             (sub_id, ))

            # Delete subscription key (not currently used for anything else)
            yield from redis_conn.delete([sub_key])

            # Subscribe to Redis channel
            chan = get_full_channel_name(res.channel)
            logger.debug('Subscribing to Redis channel %s' % (chan, ))
            redis_subscription = yield from redis_conn.start_subscribe()
            yield from redis_subscription.subscribe([chan])

            # Prepare response
            response = web.StreamResponse()
            response.content_type = 'text/event-stream'
            cors_hdrs = get_cors_headers(request.headers.get('Origin', None))
            response.headers.update(cors_hdrs)
            yield from response.prepare(request)

            # Loop
            first_event = True
            while True:
                # Wait for event on channel
                try:
                    reply = yield from asyncio.wait_for(
                        redis_subscription.next_published(),
                        settings.RT_SSE_HEARTBEAT)
                except asyncio.TimeoutError:
                    # Timeout, send SSE heartbeat
                    response.write(SseHeartbeat().as_utf8())
                    yield from response.drain()
                else:
                    # Deserialize ResourceEvent
                    event = ResourceEvent.from_json(reply.value)

                    # Create SSE event
                    sse_evt = SseEvent.from_resource_event(event)

                    # Send 'retry' field on first SSE event delivered
                    if first_event:
                        sse_evt.retry = settings.RT_SSE_RETRY
                        first_event = False

                    # Send SSE event to client
                    response.write(sse_evt.as_utf8())
                    yield from response.drain()

            yield from response.write_eof()
            return response
        finally:
            # Cleanup
            asyncio. async (self.cleanup_request(sub_id, redis_conn,
                                                 redis_subscription))