Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to enforce POST idempotency in DRF?

I have an API using Django Rest Framework that I'd like to protect against duplicate POST requests (in the spirit of Post Once Exactly (POE)). The specific scenario I'm trying to handle is:

  1. Client sends HTTP POST to create object.
  2. API backend creates the object and commits it to the database.
  3. Client loses network connectivity.
  4. API backend tries to send back a success response, but is unable to do so since the client lost network.
  5. The client never gets the "success" response, so assumes that the request fails. Client retries the request, creating a duplicate object.

There was some discussion about this on the mailing list but no code materialized. How are people solving this problem right now?

like image 677
mgalgs Avatar asked Feb 04 '23 08:02

mgalgs


1 Answers

I solved this by adding support for an X-Idempotency-Key http header which can be set by the client. I then check for non-idempotent requests using a custom permission class that checks if the idempotency key has been seen recently (in the cache):

class IsIdempotent(permissions.BasePermission):
    message = 'Duplicate request detected.'

    def has_permission(self, request, view):
        if request.method != 'POST':
            return True
        ival = request.META.get('HTTP_X_IDEMPOTENCY_KEY')
        if ival is None:
            return True
        ival = ival[:128]
        key = 'idemp-{}-{}'.format(request.user.pk, ival)
        is_idempotent = bool(cache.add(key, 'yes',
                                       settings.IDEMPOTENCY_TIMEOUT))
        if not is_idempotent:
            logger.info(u'Duplicate request (non-idempotent): %s', key)
        return is_idempotent

which I can add to my views like so:

class MyViewSet(mixins.RetrieveModelMixin,
                mixins.UpdateModelMixin,
                mixins.ListModelMixin,
                viewsets.GenericViewSet):
    permission_classes = [permissions.IsAuthenticated,
                          IsIdempotent]
like image 102
mgalgs Avatar answered Feb 06 '23 22:02

mgalgs