انواع کش کردن در Django مبتنی بر API
django
یکی از مهم ترین روش ها برای بهبود عملکرد API، کش کردن پاسخ ها است. در این مقاله، همه روش های کش کردن API در جنگو و Django REST Framework را بررسی می کنیم.
۱. پیکربندی کش برای API
ابتدا بک اند کش را پیکربندی کنید:
# settings.py
# استفاده از دیتابیس هایی مثل redis:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
# یا برای توسعه محلی و داخل رم:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
۲. کش کردن با استفاده از Decorator
۲.۱ کش ساده برای API View
# نمونه استفاده از object cache
from django.core.cache import cache
from rest_framework.views import APIView
from rest_framework.response import Response
class PostListAPIView(APIView):
def get(self, request):
cache_key = 'api_posts_list'
posts = cache.get(cache_key)
if not posts:
posts = Post.objects.all()
cache.set(cache_key, posts, 60 * 15) # ۱۵ دقیقه
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
# نمونه استفاده از دکوراتور
from django.views.decorators.cache import cache_page, cache_control, never_cache
from django.views.decorators.vary import vary_on_cookie
from django.utils.decorators import method_decorator
from rest_framework.views import APIView
from rest_framework.response import Response
class PostListAPIView(APIView):
@method_decorator(cache_control(max_age=60 * 15, public=True))
@method_decorator(vary_on_cookie)
def get(self, request):
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
class UserProfileAPIView(APIView):
@method_decorator(never_cache) # هرگز کش نشود
def get(self, request):
return Response({
'username': request.user.username,
'email': request.user.email
})
۲.۲ کش با پارامترهای درخواست
# کش با استفاده از object cache
import hashlib
from django.core.cache import cache
from rest_framework.views import APIView
from rest_framework.response import Response
class PostListAPIView(APIView):
def get(self, request):
# تولید کلید منحصر به فرد بر اساس پارامترها
query_params = request.GET.urlencode()
cache_key = f"api_posts_{hashlib.md5(query_params.encode()).hexdigest()}"
posts = cache.get(cache_key)
if not posts:
# اعمال فیلترها
posts = Post.objects.all()
if category := request.GET.get('category'):
posts = posts.filter(category=category)
if tag := request.GET.get('tag'):
posts = posts.filter(tags__slug=tag)
if search := request.GET.get('q'):
posts = posts.filter(title__icontains=search)
cache.set(cache_key, list(posts.values()), 60 * 15)
return Response(posts)
# نمونه استفاده از دکوراتور
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
from rest_framework.views import APIView
from rest_framework.response import Response
import hashlib
class PostListAPIView(APIView):
def get_cache_key(self, request):
"""کلید کش بر اساس پارامترها"""
query = request.GET.urlencode()
if query:
hash_key = hashlib.md5(query.encode()).hexdigest()[:8]
return f"api_posts_{hash_key}"
return "api_posts_all"
@method_decorator(cache_page(60 * 15))
def get(self, request):
cache_key = self.get_cache_key(request)
# فیلترها
posts = Post.objects.all()
if category := request.GET.get('category'):
posts = posts.filter(category__slug=category)
if tag := request.GET.get('tag'):
posts = posts.filter(tags__slug=tag)
if search := request.GET.get('q'):
posts = posts.filter(title__icontains=search)
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
۳. کش کردن با استفاده از Mixin
# utils/api_cache_mixin.py
import hashlib
from django.core.cache import cache
class CacheMixin:
"""Mixin برای کش کردن API Views"""
cache_timeout = 60 * 15
cache_key_prefix = 'api'
cache_vary_on_user = False
def _generate_cache_key(self):
prefix = self.cache_key_prefix
# تفکیک بر اساس کاربر
if self.cache_vary_on_user:
if self.request.user.is_authenticated:
prefix = f"{prefix}_user_{self.request.user.id}"
else:
prefix = f"{prefix}_anon"
# پارامترهای درخواست
if self.request.GET:
query_hash = hashlib.md5(
self.request.GET.urlencode().encode()
).hexdigest()[:8]
prefix = f"{prefix}_{query_hash}"
# متد HTTP
prefix = f"{prefix}_{self.request.method}"
return prefix
def get_cache_data(self):
cache_key = self._generate_cache_key()
return cache.get(cache_key)
def set_cache_data(self, data):
cache_key = self._generate_cache_key()
cache.set(cache_key, data, self.cache_timeout)
def get(self, request, *args, **kwargs):
if hasattr(self, 'get_cache_data'):
cached = self.get_cache_data()
if cached is not None:
return Response(cached)
response = super().get(request, *args, **kwargs)
if hasattr(self, 'set_cache_data') and response.status_code == 200:
self.set_cache_data(response.data)
return response
نحوه استفاده:
from utils.api_cache_mixin import CacheMixin
class PostListAPIView(CacheMixin, APIView):
cache_timeout = 60 * 15
cache_key_prefix = 'posts_list'
cache_vary_on_user = False
def get(self, request):
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
۴. کش کردن با استفاده از ViewSet
from rest_framework import viewsets
from django.core.cache import cache
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
def get_cache_key(self):
"""تولید کلید کش"""
prefix = 'post_viewset'
# بر اساس اکشن
prefix = f"{prefix}_{self.action}"
# برای detail بر اساس pk
if self.action == 'retrieve':
return f"{prefix}_{self.kwargs.get('pk')}"
# برای list بر اساس پارامترها
if self.request.GET:
query_hash = hashlib.md5(
self.request.GET.urlencode().encode()
).hexdigest()[:8]
prefix = f"{prefix}_{query_hash}"
return prefix
def list(self, request, *args, **kwargs):
cache_key = self.get_cache_key()
cached_data = cache.get(cache_key)
if cached_data is not None:
return Response(cached_data)
response = super().list(request, *args, **kwargs)
cache.set(cache_key, response.data, 60 * 15)
return response
def retrieve(self, request, *args, **kwargs):
cache_key = self.get_cache_key()
cached_data = cache.get(cache_key)
if cached_data is not None:
return Response(cached_data)
response = super().retrieve(request, *args, **kwargs)
cache.set(cache_key, response.data, 60 * 30)
return response
def perform_create(self, serializer):
serializer.save()
cache.clear() # پاک کردن کش لیست
def perform_update(self, serializer):
serializer.save()
cache.clear()
def perform_destroy(self, instance):
instance.delete()
cache.clear()
۵. کش کردن با استفاده از کتابخانه drf-extensions
این کتابخانه امکانات پیشرفته ای برای کش کردن فراهم می کند:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_CACHE_KEY_DEFAULT_PERMISSIONS': [],
}
# views.py
from rest_framework_extensions.cache.decorators import cache_response
from rest_framework_extensions.cache.mixins import CacheResponseMixin
class PostViewSet(CacheResponseMixin, viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
@cache_response(timeout=60*15, key_func='get_cache_key')
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
۶. کش کردن با استفاده از CachingRouter
# urls.py
from rest_framework_extensions.routers import CachingRouter
router = CachingRouter()
router.register(r'posts', PostViewSet, basename='post')
urlpatterns = router.urls
۷. ETags و Conditional Responses
جنگو پشتیبانی داخلی از ETag دارد:
from django.views.decorators.http import etag
from django.views.decorators.cache import cache_control
class PostListAPIView(APIView):
def get(self, request):
posts = list(Post.objects.values('id', 'title', 'created_at'))
# تولید ETag
import hashlib
etag_value = hashlib.md5(str(posts).encode()).hexdigest()
# بررسی If-None-Match
if_none_match = request.headers.get('If-None-Match')
if if_none_match == etag_value:
from rest_framework.response import Response
return Response(status=304)
response = Response(posts)
response['ETag'] = etag_value
return response
یا با استفاده از DRF:
from rest_framework.response import Response
from rest_framework.views import APIView
from django_etag import etag
class PostListAPIView(APIView):
@etag(etag_func=lambda request, view: f"posts_list_{request.GET.urlencode()}")
def get(self, request):
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
۸. کش کردن با Vary Headers در API
from django.views.decorators.vary import vary_on_cookie
from django.views.decorators.http import require_GET
@require_GET
@vary_on_cookie
def api_view(request):
# کش جدا برای هر کاربر
...
در DRF:
from rest_framework.views import APIView
from rest_framework.response import Response
class UserProfileAPIView(APIView):
def get(self, request):
# تنظیم Vary
response = Response(request.user.profile)
response['Vary'] = 'Cookie, Authorization'
return response
۹. کش کردن با Redis و Json
برای عملکرد بهتر، داده ها را به صورت JSON ذخیره کنید:
import json
from django.core.cache import cache
from rest_framework.response import Response
class PostListAPIView(APIView):
def get(self, request):
cache_key = 'api_posts_json'
cached = cache.get(cache_key)
if cached:
# تبدیل JSON به داده
data = json.loads(cached)
return Response(data)
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
data = serializer.data
# ذخیره به صورت JSON
cache.set(cache_key, json.dumps(data), 60 * 15)
return Response(data)
۱۰. Invalid کردن کش API
۱۰.۱ با استفاده از سیگنال ها
# signals.py
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.core.cache import cache
@receiver(post_save, sender=Post)
def invalidate_post_cache(sender, instance, **kwargs):
# پاک کردن کشهای مربوط به پست
cache.delete('api_posts_list')
cache.delete(f'api_post_detail_{instance.pk}')
cache.delete(f'api_post_detail_{instance.slug}')
# پاک کردن کشهای فیلتر شده
if instance.category:
cache.delete(f'api_posts_category_{instance.category.slug}')
@receiver(post_delete, sender=Post)
def invalidate_post_cache_on_delete(sender, instance, **kwargs):
invalidate_post_cache(sender, instance, **kwargs)
۱۰.۲ با استفاده از Pattern
from django_redis import get_redis_connection
def invalidate_api_cache(pattern):
conn = get_redis_connection("default")
keys = conn.keys(f"*{pattern}*")
if keys:
conn.delete(*keys)
# استفاده
invalidate_api_cache('api_posts')
۱۰.۳ در ViewSet
class PostViewSet(viewsets.ModelViewSet):
def perform_create(self, serializer):
serializer.save()
self._clear_cache()
def perform_update(self, serializer):
serializer.save()
self._clear_cache()
def perform_destroy(self, instance):
instance.delete()
self._clear_cache()
def _clear_cache(self):
from django.core.cache import cache
# پاک کردن کشهای مرتبط
cache.delete('api_posts_list')
# یا استفاده از pattern
conn = get_redis_connection("default")
conn.delete(*conn.keys('*api_posts*'))
۱۱. کش کردن با Rate Limiting ترکیبی
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/minute',
'user': '1000/minute',
},
'DEFAULT_CACHE_KEY_THROTTLE': 'throttle_%(scope)s_%(ident)s',
}
۱۲. کش کردن با استفاده از Middleware
# utils/api_cache_middleware.py
from django.core.cache import cache
from django.utils.deprecation import MiddlewareMixin
class APICacheMiddleware(MiddlewareMixin):
"""Middleware برای کش کردن خودکار API"""
CACHE_TIMEOUT = 60 * 15
def process_response(self, request, response):
# فقط GET requests
if request.method != 'GET':
return response
# فقط پاسخهای موفق
if response.status_code != 200:
return response
# فقط API URLs
if not request.path.startswith('/api/'):
return response
# تولید کلید کش
cache_key = f"api_cache_{request.path}_{request.GET.urlencode()}"
# ذخیره در کش
cache.set(cache_key, response.content, self.CACHE_TIMEOUT)
return response
def process_request(self, request):
# بررسی کش قبل از پردازش
if request.method != 'GET':
return None
if not request.path.startswith('/api/'):
return None
cache_key = f"api_cache_{request.path}_{request.GET.urlencode()}"
cached_content = cache.get(cache_key)
if cached_content:
from django.http import HttpResponse
return HttpResponse(cached_content, content_type='application/json')
return None
# settings.py
MIDDLEWARE = [
# ...
'utils.api_cache_middleware.APICacheMiddleware',
]
۱۳. کش کردن با پارامترهای احراز هویت
class PostListAPIView(APIView):
def get(self, request):
# کلید کش بر اساس نوع کاربر
if request.user.is_authenticated:
cache_key = f"api_posts_user_{request.user.id}"
else:
cache_key = "api_posts_anon"
# اضافه کردن پارامترها
if request.GET:
cache_key = f"{cache_key}_{request.GET.urlencode()}"
cached = cache.get(cache_key)
if cached is not None:
return Response(cached)
posts = Post.objects.all()
# پستهای عمومی برای همه
# پستهای خصوصی فقط برای کاربر
if not request.user.is_authenticated:
posts = posts.filter(is_public=True)
serializer = PostSerializer(posts, many=True)
cache.set(cache_key, serializer.data, 60 * 15)
return Response(serializer.data)
مثال کامل با ViewSet
# views.py
import hashlib
from rest_framework import viewsets
from rest_framework.response import Response
from django.core.cache import cache
class PostViewSet(viewsets.ModelViewSet):
"""ViewSet کامل با کش"""
queryset = Post.objects.all()
serializer_class = PostSerializer
def get_cache_key(self):
"""تولید کلید کش هوشمند"""
parts = ['api', 'posts', self.action]
# تفکیک بر اساس کاربر
if self.request.user.is_authenticated:
parts.append(f"user_{self.request.user.id}")
# اضافه کردن پارامترها
if self.request.GET:
query_hash = hashlib.md5(
self.request.GET.urlencode().encode()
).hexdigest()[:8]
parts.append(query_hash)
# اضافه کردن pk برای detail
if self.action == 'retrieve' and 'pk' in self.kwargs:
parts.append(f"pk_{self.kwargs['pk']}")
return '_'.join(parts)
def get_cache_timeout(self):
"""زمان کش بر اساس اکشن"""
timeouts = {
'list': 60 * 15, # ۱۵ دقیقه
'retrieve': 60 * 30, # ۳۰ دقیقه
'create': 0, # کش نشود
'update': 0,
'destroy': 0,
}
return timeouts.get(self.action, 60 * 15)
def list(self, request, *args, **kwargs):
cache_key = self.get_cache_key()
timeout = self.get_cache_timeout()
if timeout > 0:
cached = cache.get(cache_key)
if cached is not None:
return Response(cached)
response = super().list(request, *args, **kwargs)
if timeout > 0:
cache.set(cache_key, response.data, timeout)
return response
def retrieve(self, request, *args, **kwargs):
cache_key = self.get_cache_key()
timeout = self.get_cache_timeout()
if timeout > 0:
cached = cache.get(cache_key)
if cached is not None:
return Response(cached)
response = super().retrieve(request, *args, **kwargs)
if timeout > 0:
cache.set(cache_key, response.data, timeout)
return response
def perform_create(self, serializer):
serializer.save()
self._invalidate_cache()
def perform_update(self, serializer):
serializer.save()
self._invalidate_cache()
def perform_destroy(self, instance):
instance.delete()
self._invalidate_cache()
def _invalidate_cache(self):
"""پاک کردن کش مرتبط"""
# پاک کردن کش لیست
cache.delete('api_posts_list')
# پاک کردن با pattern
try:
from django_redis import get_redis_connection
conn = get_redis_connection("default")
keys = conn.keys('api_posts_*')
if keys:
conn.delete(*keys)
except:
pass
نتیجه گیری
برای کش کردن API در جنگو، چندین روش دارید:
- API ساده: از Decorator ساده استفاده کنید
- چندین View مشابه: از Mixin استفاده کنید
- API پیچیده با CRUD: از ViewSet استفاده کنید
- کش خودکار: از Middleware استفاده کنید
- کاهش ترافیک: از ETags استفاده کنید
فراموش نکنید که کش را در زمان مناسب invalid کنید تا کاربران داده های قدیمی دریافت نکنند!
ثبت دیدگاه
دیدگاه کاربران (0)
هیچ دیدگاهی ثبت نشده است. میتوانید اولین نفر باشید.