【问题标题】:Are POST requests cached in Django?POST 请求是否缓存在 Django 中?
【发布时间】:2020-04-09 21:49:42
【问题描述】:

我想根据发送给它的 POST 数据缓存一些视图。

django.views.decorators.cache.cache_page 装饰器会自动执行此操作,还是我需要以某种方式对其进行调整?在后一种情况下,我该怎么做?

我正在尝试缓存 GraphQL POST 请求。

【问题讨论】:

    标签: django graphql django-cache


    【解决方案1】:

    不,POST 响应永远不会被缓存:

        if request.method not in ('GET', 'HEAD'):
            request._cache_update_cache = False
            return None  # Don't bother checking the cache.
    

    (来自 django.middleware.cache 中的 FetchFromCacheMiddleware)。

    您必须使用low-level cache API 自己实现一些东西。缓存对 POST 请求的响应是最不常见的,因为 POST 请求旨在更改数据库中的内容,并且结果对于特定请求始终是唯一的。您必须考虑到底要缓存什么。

    【讨论】:

    • 为了提供更多上下文,我正在尝试缓存 GraphQL 请求,这些请求不会更改数据库中的任何内容,但由 POST 方法请求。
    • 看看 Django 如何在django.utils.cache.get_cache_key 中构建缓存键。这将使您了解 url 如何与标头结合以生成请求的唯一缓存键。您必须向其中添加帖子数据。
    【解决方案2】:

    我最终创建了一个自定义装饰器,它根据请求路径、查询参数和发布的数据缓存响应:

    # myproject/apps/core/caching.py
    
    import hashlib
    import base64
    from functools import wraps
    
    from django.core.cache import cache
    from django.conf import settings
    
    
    def make_hash_sha256(o):
        hasher = hashlib.sha256()
        hasher.update(repr(make_hashable(o)).encode())
        return base64.b64encode(hasher.digest()).decode()
    
    
    def make_hashable(o):
        if isinstance(o, (tuple, list)):
            return tuple((make_hashable(e) for e in o))
    
        if isinstance(o, dict):
            return tuple(sorted((k,make_hashable(v)) for k,v in o.items()))
    
        if isinstance(o, (set, frozenset)):
            return tuple(sorted(make_hashable(e) for e in o))
    
        return o
    
    
    def cache_get_and_post_requests(duration=600):
        def view_decorator(view):
            @wraps(view)
            def view_wrapper(request, *args, **kwargs):
                # TODO: make the key also dependable on the user or cookies if necessary
                cache_key = "-".join((
                    settings.CACHE_MIDDLEWARE_KEY_PREFIX,
                    make_hash_sha256((
                        request.path,
                        list(request.GET.items()),
                        list(request.POST.items()),
                        request.body,
                    )),
                ))
                cached_response = cache.get(cache_key)
                if cached_response:
                    return cached_response
                response = view(request, *args, **kwargs)
                cache.set(cache_key, response, duration)
                return response
            return view_wrapper
        return view_decorator
    

    然后我可以像这样在 URL 配置中使用它:

    # myproject/urls.py
    
    from django.urls import path
    from django.conf.urls.i18n import i18n_patterns
    from graphene_django.views import GraphQLView
    from myproject.apps.core.caching import cache_get_and_post_requests
    
    urlpatterns = i18n_patterns(
        # …
        path("graphql/", cache_get_and_post_requests(60*5)(GraphQLView.as_view(graphiql=True))),
    )
    

    【讨论】:

    • 您忽略了 HTTP 标头。例如。如果响应包含翻译文本或本地化数字/日期等,您的解决方案将不起作用...
    • 是的,还有一些改进的空间:)
    • 在我的例子中,语言是由路径前缀控制的。我更新了 urls.py 示例以反映这一点。
    猜你喜欢
    • 2020-01-26
    • 1970-01-01
    • 2014-11-25
    • 2012-02-19
    • 2016-12-26
    • 2014-06-17
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    相关资源
    最近更新 更多