Paris.py, 16/05/2018
Web interface
Rest Resource
HTTP PUT
Web interface
Rest Resource
Web interface
Rest Resource
Guido
Ada
Guido and Ada see the same resource...
HTTP GET
HTTP GET
Web interface
Rest Resource
Web interface
Rest Resource
Guido
Ada
Guido modifies the resource...
HTTP PUT
Web interface
Rest Resource
Web interface
Rest Resource
Guido
Ada
Ada modifies the resource...
HTTP PUT
HTTP PUT
Web interface
Rest Resource
Web interface
Rest Resource
Guido
Ada
Guido's changes are lost
Main idea: use of a token for current state of the resource. Usually done by a hash function
Web Client
Rest Resource
HTTP GET
Token
Token retrieved via ETag header
Web Client
Rest Resource
HTTP PUT
Token
Token sent via a If-Match header
Web Client
Rest Resource
HTTP PUT
Case 1: the token sent matches the current resource token
=> the resource is updated and the new token is sent
New Token
Web Client
Rest Resource
HTTP PUT
Case 2: the token sent does not match the current resource token (it is outdated)
=> the resource is not updated and a 412 response is sent.
Error 412
from rest_framework.generics import RetrieveAPIView, UpdateAPIView
from .serializers import DocumentSerializer
from ..models import Document
class DocumentView(RetrieveAPIView, UpdateAPIView):
queryset = Document.objects.all()
serializer_class = DocumentSerializer
Django rest framework view: reminder
def optimistic_concurrency_control(compute_etag_func):
# [NOTE] - we only consider GET and PUT methods in this example
def cls_decorator(cls):
get_meth = cls.get
cls.get = get_decorator(get_meth)
put_meth = cls.put
cls.put = put_decorator(put_meth)
return cls
...
return cls_decorator
Occ implemented via a class decorator
def optimistic_concurrency_control(compute_etag_func):
def cls_decorator(cls):
...
def get_decorator(meth):
def g(self, request, *args, **kwargs):
resp = meth(self, request, *args, **kwargs)
resp['etag'] = compute_etag_func(self)
return resp
return g
return cls_decorator
GET method decoration
def optimistic_concurrency_control(compute_etag_func):
def cls_decorator(cls):
...
def put_decorator(meth):
def g(self, request, *args, **kwargs):
etag = request.META.get('HTTP_IF_MATCH')
if etag != compute_etag_func(self):
return Response(status=status.HTTP_412_PRECONDITION_FAILED)
resp = meth(self, request, *args, **kwargs)
resp['etag'] = compute_etag_func(self)
return resp
return g
return cls_decorator
PUT method decoration
from hashlib import md5
from rest_framework.generics import RetrieveAPIView, UpdateAPIView
from .serializers import DocumentSerializer
from .optimistic_concurrency import optimistic_concurrency_control
from ..models import Document
def _compute_etag(self):
return md5(
self.get_object().text.encode('utf-8')
).hexdigest()
@optimistic_concurrency_control(_compute_etag)
class DocumentView(RetrieveAPIView, UpdateAPIView):
queryset = Document.objects.all()
serializer_class = DocumentSerializer
Example of usage
Demo with small Django web app
Contact: vperez@legalstart.fr
On recrute!