How to enforce POST idempotency in DRF?

Question:

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?

Asked By: mgalgs

||

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]
Answered By: mgalgs
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.