Пример #1
0
    def deliver_hook(self, instance, payload_override=None):
        """
        Deliver the payload to the target URL.

        By default it serializes to JSON and POSTs.

        Args:
            instance: instance that triggered event.
            payload_override: JSON-serializable object or callable that will
                return such object. If callable is used it should accept 2
                arguments: `hook` and `instance`.
        """
        if payload_override is None:
            payload = self.serialize_hook(instance)
        else:
            payload = payload_override

        if callable(payload):
            payload = payload(self, instance)

        if getattr(settings, 'HOOK_DELIVERER', None):
            deliverer = get_module(settings.HOOK_DELIVERER)
            deliverer(self.target, payload, instance=instance, hook=self)
        else:
            client.post(
                url=self.target,
                data=json.dumps(payload, cls=DjangoJSONEncoder),
                headers={'Content-Type': 'application/json'}
            )

        hook_sent_event.send_robust(sender=self.__class__, payload=payload, instance=instance, hook=self)
        return None
Пример #2
0
    def deliver_hook(self, instance, payload_override=None):
        """
        Deliver the payload to the target URL.

        By default it serializes to JSON and POSTs.

        Args:
            instance: instance that triggered event.
            payload_override: JSON-serializable object or callable that will
                return such object. If callable is used it should accept 2
                arguments: `hook` and `instance`.
        """
        if payload_override is None:
            payload = self.serialize_hook(instance)
        else:
            payload = payload_override

        if callable(payload):
            payload = payload(self, instance)

        if getattr(settings, 'HOOK_DELIVERER', None):
            deliverer = get_module(settings.HOOK_DELIVERER)
            deliverer(self.target, payload, instance=instance, hook=self)
        else:
            client.post(
                url=self.target,
                data=json.dumps(payload, cls=DjangoJSONEncoder),
                headers={'Content-Type': 'application/json'}
            )

        hook_sent_event.send_robust(sender=self.__class__, payload=payload, instance=instance, hook=self)
        return None
Пример #3
0
    def deliver_hook(self, instance, payload_override=None):
        """
        Deliver the payload to the target URL.

        By default it serializes to JSON and POSTs.
        """
        payload = payload_override or self.serialize_hook(instance)
        """
        Tweak for when the payload is none no action is taken
        Needs refactoring: filter hooks by a subset of users
	"""
        if payload is None:
            return None
        if getattr(settings, 'HOOK_DELIVERER', None):
            deliverer = get_module(settings.HOOK_DELIVERER)
            deliverer(self.target, payload, instance=instance, hook=self)
        else:
            client.post(url=self.target,
                        data=json.dumps(
                            payload, cls=serializers.json.DjangoJSONEncoder),
                        headers={'Content-Type': 'application/json'})

        signals.hook_sent_event.send_robust(sender=self.__class__,
                                            payload=payload,
                                            instance=instance,
                                            hook=self)
        return None
Пример #4
0
    def deliver_hook(self, instance, payload_override=None):
        """
        Deliver the payload to the target URL.

        By default it serializes to JSON and POSTs.
        """
        payload = payload_override or self.serialize_hook(instance)
        """
        Tweak for when the payload is none no action is taken
        Needs refactoring: filter hooks by a subset of users
	"""
        if payload is None:
            return None
        if getattr(settings, 'HOOK_DELIVERER', None):
            deliverer = get_module(settings.HOOK_DELIVERER)
            deliverer(self.target, payload, instance=instance, hook=self)
        else:
            client.post(
                url=self.target,
                data=json.dumps(payload, cls=serializers.json.DjangoJSONEncoder),
                headers={'Content-Type': 'application/json'}
            )

        signals.hook_sent_event.send_robust(sender=self.__class__, payload=payload, instance=instance, hook=self)
        return None
Пример #5
0
    def serialize_hook(self, instance):
        """
        Serialize the object down to Python primitives.

        By default it uses Django's built in serializer.
        """
        if getattr(instance, 'serialize_hook', None) and callable(
                instance.serialize_hook):
            return instance.serialize_hook(hook=self)
        if getattr(settings, 'HOOK_SERIALIZER', None):
            serializer = get_module(settings.HOOK_SERIALIZER)
            return serializer(instance, hook=self)
        # if no user defined serializers, fallback to the django builtin!
        data = serializers.serialize('python', [instance])[0]
        for k, v in data.items():
            if isinstance(v, OrderedDict):
                data[k] = dict(v)

        if isinstance(data, OrderedDict):
            data = dict(data)

        return {
            'hook': self.dict(),
            'data': data,
        }
def retry_hook(modeladmin, request, queryset):
    deliverer = getattr(settings, "HOOK_DELIVERER", None)
    if not deliverer:
        modeladmin.message_user(request, "No custom HOOK_DELIVERER set in " "settings.py", messages.ERROR)
        return

    deliverer = get_module(deliverer)
    count = 0
    for hook in queryset.filter(target=F("hook__target"), event=F("hook__event"), user_id=F("hook__user_id")):
        deliverer(hook.target, hook.payload, hook=hook.hook, cleanup=True)
        count += 1
    modeladmin.message_user(request, "Retried %d failed webhooks" % count)
 def handle(self, *args, **options):
     deliverer = getattr(settings, 'HOOK_DELIVERER', None)
     if not deliverer:
         raise CommandError("No custom HOOK_DELIVERER set in settings.py")
         return 5
     deliverer = get_module(deliverer)
     count = 0
     for hook in FailedHook.objects.filter(target=F('hook__target'),
                                           event=F('hook__event'),
                                           user_id=F('hook__user_id')):
         deliverer(hook.target, hook.payload, hook=hook.hook,
                   cleanup=True)
         count += 1
     self.stdout.write("Retried %d failed webhooks" % count)
Пример #8
0
    def serialize_hook(self, instance):
        """
        Serialize the object down to Python primitives.

        By default it uses Django's built in serializer.
        """
        if getattr(instance, 'serialize_hook', None) and callable(instance.serialize_hook):
            return instance.serialize_hook(hook=self)
        if getattr(settings, 'HOOK_SERIALIZER', None):
            serializer = get_module(settings.HOOK_SERIALIZER)
            return serializer(instance, hook=self)
        # if no user defined serializers, fallback to the django builtin!
        return {
            'hook': self.dict(),
            'data': serializers.serialize('python', [instance])[0]
        }
Пример #9
0
    def handle(self, *args, **options):
        deliverer = getattr(settings, 'HOOK_DELIVERER', None)
        if not deliverer:
            raise CommandError('No custom HOOK_DELIVERER set in settings.py')
            return 5
        deliverer = get_module(deliverer)
        
        count = 0

        # Backoff schedule: 1min, 3min, 10min, 30min, 60min
        backoff_minutes = {
            1: 1,
            2: 2,
            3: 7,
            4: 20,
            5: 30,
        }
        
        for hook in FailedHook.objects.filter(retries__lte=5): # TODO: Add backoff algorithm

            if hook.last_retry + timedelta(minutes=backoff_minutes.get(hook.retries, 120)) > now():
                continue # It's not time yet to retry
            
            event_model = hook.event.split('.')[0] + '.'

            payload_dict = json.loads(hook.payload)
            has_older_hooks = len(FailedHook.objects.filter(
                event__startswith=event_model,
                payload__contains=payload_dict['url'], 
                pk__lt=hook.pk)) > 0
            
            if has_older_hooks:
                # Don't retry newer hooks if an older one fails for a model url
                print('Skipping failed hook because there is an older one that '
                    'needs to succeed first')
                continue

            #TODO: Handle parents and foreign key hooks first

            deliverer(hook.target, hook.payload, hook=hook.hook, failed_hook=hook)
            count += 1
        
        self.stdout.write('Retried {} failed webhooks'.format(count))
Пример #10
0
    def deliver_hook(self, instance, payload_override=None):
        """
        Deliver the payload to the target URL.

        By default it serializes to JSON and POSTs.
        """
        payload = payload_override or self.serialize_hook(instance)
        if getattr(settings, 'HOOK_DELIVERER', None):
            deliverer = get_module(settings.HOOK_DELIVERER)
            deliverer(self.target, payload, instance=instance, hook=self)
        else:
            client.post(
                url=self.target,
                data=json.dumps(payload, cls=DjangoJSONEncoder),
                headers={'Content-Type': 'application/json'}
            )

        signals.hook_sent_event.send_robust(sender=self.__class__, payload=payload, instance=instance, hook=self)
        return None
Пример #11
0
    def deliver_hook(self, instance, payload_override=None):
        """
        Deliver the payload to the target URL.

        By default it serializes to JSON and POSTs.
        """
        payload = payload_override or self.serialize_hook(instance)
        if getattr(settings, 'HOOK_DELIVERER', None):
            deliverer = get_module(settings.HOOK_DELIVERER)
            deliverer(self.target, payload, instance=instance, hook=self)
        else:
            client.post(url=self.target,
                        data=json.dumps(payload, cls=DjangoJSONEncoder),
                        headers={'Content-Type': 'application/json'})

        signals.hook_sent_event.send_robust(sender=self.__class__,
                                            payload=payload,
                                            instance=instance,
                                            hook=self)
        return None
Пример #12
0
def retry_hook(modeladmin, request, queryset):
    deliverer = getattr(settings, 'HOOK_DELIVERER', None)
    if not deliverer:
        modeladmin.message_user(request, "No custom HOOK_DELIVERER set in "
                                "settings.py", messages.ERROR)
        return

    deliverer = get_module(deliverer)
    count = 0

    # Ensure that only "valid" requests are run; TODO: does this really matter?
    for hook in queryset.filter(target=F('hook__target'),
                                #event=F('hook__event'), # TODO: this won't match for any.any or model.any events
                                user_id=(
                                    # For non-superusers, limit access to user's own failed webhooks
                                    F('hook__user_id') if request.user.is_superuser else request.user.id)
                                ):
        deliverer(hook.target, hook.payload, hook=hook.hook,
                  failed_hook=hook)
        count += 1
    modeladmin.message_user(request, "Retried %d failed webhooks" % count)
    def sync_flush(self):
        while len(self.queue) > 0:
            method, args, kwargs = self.queue.pop()
            hook_id = kwargs.pop('_hook_id')
            hook_event = kwargs.pop('_hook_event')
            hook_user_id = kwargs.pop('_hook_user_id')
            failed_hook = kwargs.pop('_failed_hook')

            try:
                r = None
                r = getattr(requests, method)(*args, **kwargs)
                payload = kwargs.get('data', '{}')
                if r.status_code > 299:

                    if failed_hook:
                        if failed_hook.retries == 5:
                            # send email after several failed attempts,
                            # so that the issue can be investigated
                            r.raise_for_status()

                        failed_hook.response_headers = {
                            k: r.headers[k]
                            for k in r.headers.iterkeys()
                        }
                        failed_hook.response_body = r.content
                        failed_hook.last_status = r.status_code
                        failed_hook.retries = F('retries') + 1
                        failed_hook.save()

                    else:
                        FailedHook.objects.create(
                            target=r.request.url,
                            payload=payload,
                            response_headers={
                                k: r.headers[k]
                                for k in r.headers.iterkeys()
                            },
                            response_body=r.content,
                            last_status=r.status_code,
                            event=hook_event,
                            user_id=hook_user_id,
                            hook_id=hook_id)

                elif failed_hook:
                    failed_hook.delete()

                self.total_sent += 1

            except (requests.exceptions.HTTPError, requests.exceptions.Timeout,
                    requests.exceptions.RequestException,
                    requests.exceptions.SSLError) as e:

                send_mail = False

                # record failed hook for retrying
                if failed_hook:
                    send_mail = failed_hook.retries == 5
                    failed_hook.response_headers = {
                        k: r.headers[k]
                        for k in r.headers.iterkeys()
                    }
                    failed_hook.response_body = r.content
                    failed_hook.last_status = r.status_code
                    failed_hook.retries = F('retries') + 1
                    failed_hook.save()

                else:
                    send_mail = True  #TODO: Consider not sending it on the first go
                    FailedHook.objects.create(
                        target=r.request.url,
                        payload=payload,
                        response_headers={
                            k: r.headers[k]
                            for k in r.headers.iterkeys()
                        },
                        response_body=r.
                        content,  # TODO: Test what happens when there is no response, e.g. with SSLError
                        last_status=r.status_code,
                        event=hook_event,
                        user_id=hook_user_id,
                        hook_id=hook_id)

                if send_mail and getattr(settings, 'HOOK_EXCEPTION_MAILER',
                                         None):
                    if 'data' in kwargs:
                        kwargs['data'] = json.loads(kwargs['data'])
                    extra_body = {'webhook': kwargs}

                    if r is not None:
                        extra_body['response'] = {
                            'status_code': r.status_code,
                            'url': r.url,
                            'reason': r.reason,
                            'content': r.content,
                        }

                    mailer = get_module(settings.HOOK_EXCEPTION_MAILER)
                    mailer('Error sending webhook',
                           request=None,
                           exception=e,
                           extra_body=extra_body)