Django-rest-framework: 轻松添加/验证 etag。

创建于 2011-06-29  ·  24评论  ·  资料来源: encode/django-rest-framework

Django 有一个很棒的 etag/condition/last_modified 装饰器。 它不适用于基于 drf 类的视图,因为您无法用它们装饰“get”。 由于 get 返回的对象不是 http 响应,因此无法将 etag 标头添加到响应中。

我想看到一种从 drf 中做到这一点的方法。 我正在考虑类似于资源上的可覆盖方法或可用于生成 etag 的视图(或 mixin)。

在 Django 中执行此操作的另一种方法是使用中间件,但它不能像装饰器那样完全快捷地运行视图主体。

Enhancement

最有用的评论

不幸的是,drf-extensions 中 Etag 功能的默认实现和文档完全是错误的并且存在危险的错误。 如果 _request_ 更改,它会更改 Etag,而不是 _response_ 更改。 这正是您想要的服务器端缓存,以及您不想要的 Etag。

所有24条评论

好的,我有一个初步补丁: https :

不过,很高兴收到有关此的反馈。

好吧,所以,最初我写的是这个......

酷,是的,我真的很想看到这个。

Coupla 想法 - 您应该能够只使用 View.add_header,而不是您目前拥有的 View._ETAG。
(看起来 .add_header 可能应该移到 ResponseMixin 类中。)

其次,我想看到@condition@etag@last_modified装饰可能几乎是https://github.com/django/django/blob/master/django/views/decorators的直接克隆

但是正在研究更多的东西......

也许这毕竟不是正确的方法......

您实际上可以从 REST 框架视图返回 HttpResponses,它们只是没有应用所有常见的内容协商/序列化内容。 @last_modified@etag@condition装饰器只返回空的 HttpResponses,所以这不是真正的问题。

所以,我在想的是,如果我们简单地将__setitem__ __getitem__has_header到 Response 类,那么我认为 Django 现有的@last_modified@etag@condition装饰器在 REST 框架视图上应该可以正常工作_只要_视图使用return Response(status, data)样式而不是return data样式。

Obv 如果我们记录下来会很有帮助,但它可能比必须复制 Django 已经做的事情更有意义。

你怎么认为?

使用 django 装饰器可能存在一个问题:我不确定它们是否可以使用方法,只能使用裸函数。 我编写的装饰器很大程度上基于我通过 StackOverflow 找到的一个装饰器,仅适用于这种情况。

情况可能并非如此,在这种情况下,此解决方案听起来更胜一筹。

话虽如此,我一直在返回返回数据样式,因为它更少样板,而且我通常只返回我想序列化为 json 的对象。 我们也许可以让它以两种方式工作。

另一种选择可能是将这些添加到 View 类的 mixin。

我还发现 django 装饰器可能不会对有条件的 PUT、POST 和 DELETE 请求做正确的事情。 刚刚向 sinatra 提交了一个补丁来解决这个问题。

忽略最后一点:显然我没有正确阅读代码。

实际上有一点:那些装饰器可能无法在 _methods_ 上使用 ATM,因为它们有额外的“self”参数。 我会调查一下,然后向 Django 提交一张票,因为他们也应该与 CBV 一起工作......

啊,好的 - 我现在看到@method_decorator ... https://docs.djangoproject.com/en/dev/topics/class-based-views/#decorating -class-based-views
所以我想这是关闭我们需要的:

  1. 对 Response 进行一些调整
  2. 一些简单的文档

我确定我查看了那个文档,但是我没有看到那个装饰器!

我尝试使用 django 装饰器,最初的结果真的很奇怪,信息应该只属于出现在不相关的 etag 函数中的视图。

经过几个小时试图让它变得更好一点后,我想出了一个解决方案来解决我的问题并且看起来足够通用。 您对此有何看法:

https://bitbucket.org/vitormazzi/django-rest-framework/changeset/6f8de4500c6f

现在我们正在发布 2.x 版本,希望为这个问题注入一些新的活力。

我的想法主要是通过尝试在项目中添加功能以及阅读这篇文章而拼凑起来的,所以绝对仍然很粗糙。

我看到 DRF 需要考虑 ETag 的两个领域 - 在视图中的使用以及如何获得实例版本的唯一表示。

观看次数

得到
GET 请求只需要在适当的标头中为对象 ETag 提供服务。 对RetrieveModelMixin一行更改可以轻松添加:

def retrieve(self, request, *args, **kwargs):
    self.object = self.get_object()
    serializer = self.get_serializer(self.object)
    headers = {'ETag': self.object.etag}
    return Response(serializer.data, headers=headers)

放置、修补、删除
更新 HTTP 动词的一般检查可以在视图的dispatch或者可能拉到另一个方法中,因为它需要检查 ETags 是否打开(请参阅下面的选项部分):

    header_etag = request.META.get('HTTP_IF_MATCH')
    if header_etag is None:
        return Response({'error': 'IF_MATCH header is required'}, status=400)

然后在检索对象后进行更详细的检查,以查看请求是否认为它正在查看正确的对象:

    if self.object.etag != header_etag:
        return Response({'error': 'object has been updated since you last saw it'}, status=412)

实例版本的唯一表示

我不认为对象的 ETag 的实际生成应该是 DRF 的问题。 我一直在使用对象的updated字段的纪元时间进行测试,但我可以很容易地看到需要更复杂的。

我建议 DRF 默认查找obj.etag但它可以使用正常的 CBV 流进行配置,例如get_etag()etag_var = 'get_my_objects_etag'

我们还需要强制将 ETag 作为字符串从对象中检索,因为我们正在与标头进行比较并且尝试解释类型充其量是痛苦的。

选项

  • 全局设置(如序列化程序等)以打开或关闭 ETag 的使用。
  • 视图上的两个设置:

    • use_etags (或类似的东西) - 一个布尔值

    • etag_var - 我们可以在相关对象上getattr的函数名称字符串

@ghickman - 我想看到确定 ETags 和 LastModified 的行为看起来与其他可插入类相似。 IE。 有一个类似的东西:

class MyView(views.APIView):
    cache_lookup_classes = []

缓存签名应该同时处理 ETags 和 LastModified,我们想要提供两种不同的东西:

  • 确定给定对象实例的 etag 和/或上次修改。
  • 给定传入请求,预先确定 etag 和/或上次修改。

会有一个BaseCacheLookup ,有两个方法签名,可能看起来像:

.object_etag_and_last_modified(self, view, obj)
.preemptive_etag_and_last_modified(self, view, request, *view_kwargs, **view_kwargs)

给定一个对象,返回一个(etag, last modified)的二元组,其中任何一个都可能没有。
如果传入的请求包含匹配的 If-Modified-Since 或 If-None-Match 标头,则将返回 304 Not Modified 响应。 如果传入的响应包含匹配的 If-Match 或 If-Unmodified-Since,则将返回 412 Precondition Failed 响应。

这将允许 CacheLookupClass 匹配您所描述的实现,但也允许其他变体。

您还可以应用多个缓存查找类,以不同的最后修改粒度,例如,
包括GlobalLastModifiedLookup除了一个ObjectETagLookup 。 如果自缓存副本以来没有进行任何写入,则允许视图在进行任何数据库调用之前抢先返回。 (如果您使用带有 Varnish 的服务器端缓存,即使是这样的基本策略也会产生巨大的差异)

这对您来说听起来合理吗?

我没有考虑过 LastModified,因为我没有在当前的实现中使用它,但考虑到它的目的,包含它绝对是有意义的。

可插入类听起来是个好主意,特别是如果我们将 LastModified 和 ETag 实现作为基本示例。 我喜欢这样的想法,即只需对项目进行最少的更改即可轻松打开 GET 缓存。

我更愿意将 etag 和 last_modified 生成分成两个方法(这些名称),正如您所建议的那样,在未实现时返回None 。 CacheLookup 后端然后可以选择实现一个和/或另一个。 为了方便起见,我们总是可以提供一种实用方法(可能是cachable_obj_reprunique_obj_repr ?),如果您觉得它有用的话,可以将两者结合起来。

tl; dr是的,可插拔类方面听起来合理,应该提供更大的灵活性。 我很高兴开始为此编写补丁。

大家好。 如果您感兴趣,我已经在我的扩展库中实现了不同的 etag 支持方法http://chibisov.github.io/drf-extensions/docs/

@chibisov Neato。 我们真的应该完成#1019,所以我们在文档中的某个地方可以链接到这样的包。

将其关闭为 #1019 已关闭, @chibisov的包已列出。

这个是故意作为 3.3 的里程碑,因为我有点希望我们在某个时候就此给出一些正式的指导。 如果我们确实选择关闭它,我不会特别担心,但这一直在我的内部路线图上。

不幸的是,drf-extensions 中 Etag 功能的默认实现和文档完全是错误的并且存在危险的错误。 如果 _request_ 更改,它会更改 Etag,而不是 _response_ 更改。 这正是您想要的服务器端缓存,以及您不想要的 Etag。

@mbox最好的办法是在 drf-extensions 上打开一个关于此的问题,或者如果您认为这是一个 DRF“核心”问题,请在此处打开。 请注意,失败的测试将是我们查看问题的良好开端。

@mbox @xordoquy
我刚刚向 drf-extensions (https://github.com/chibisov/drf-extensions/pull/171) 提交了一个 PR,它允许通过 DRF API 操作资源的乐观并发控制。 它使用所有对象字段的语义散列,我包含了一个用于演示目的的测试应用程序。 它已经在 Python 2.7、3.4、3.5 上针对 DRF>=3.3.1 和 django>=1.8 进行了测试。

谢谢

只是给未来读者的一个说明 - 我已经创建了一个小包来使用来自 Django 的条件装饰器和 DRF。 所以如果你有兴趣:
https://github.com/jozo/django-rest-framework-condition

此页面是否有帮助?
0 / 5 - 0 等级