파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 강의를 듣고 정리한 글입니다.
DRF에서 지원하는 Throttling 설정을 통해 시간당 최대 횟수를 제한할 수 있다. 또한 APIView에 scope를 지정함으로써 scope별로 다르게 설정도 가능하다.
본 포스팅에서는 Throttling의 Rate, Scope 등의 개념을 살펴보고 어떻게 활용하면 되는지 살펴본다.
anon
user
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [], # Throttle 호출 제한을 하지 않음.
'DEFAULT_THROTTLE_RATES': {
'anon': None,
'user': None,
},
}
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.UserRateThrottle', # 모든 API에 대해서 유저 단위로 횟수를 제한하고, 비인증 요청에 대해서는 IP단위로 횟수 제한
],
'DEFAULT_THROTTLE_RATES': {
# 'anon': None, # default
'user': '10/day', # 하루에 10번 허용
},
}
from rest_framework.throttling import UserRateThrottle
class PostViewSet(ViewSet):
throttle_classes = UserRateThrottle
429 Too Many Requests 응답
예외 메시지에 API 활용이 가능한 시점을 알려줍니다.
→ 이는 Throttle의 wait 멤버 함수를 통해 계산
# rest_framework/throttling.py
from django.core.cache import cache as default_cache
class SimpleRateThrottle(BaseThrottle):
cache = default_cache
```
## 2.2. 장고의 Cache 지원
---
- 기본 settings의 디폴트 캐시 : 로컬 메모리 캐시
- 다양한 캐시 지원
1. **Memcached** 서버 지원 : `django.core.cache.backends.MemcachedCache` 혹은 `PyLibMCCache`
- Memcache도 메모리 기반의 서버이기 때문에, Memcached 재시작되면 데이터가 초기화가 된다.
2. 데이터베이스 캐시 : `django.core.cache.backends.DatabaseCache`
3. 파일 시스템 캐시 : `django.core.cache.backends..FileBasedCache`
4. **로컬 메모리 캐시**(default) : `django.core.cache.backends.LocMemCache`
5. 더미 캐시 : `django.core.cache.backends.dummy.DummyCache` ⇒ 실제로 캐시를 수행하진 않습니다.
- redis를 활용한 캐시
- [**django-redis-cache**](https://github.com/sebleier/django-redis-cache)
- Redis는 Memcached와 유사하나 메모리 캐시 뿐만 아니라 디스크에 동기화 싱크를 맞추기 때문에 재시작 시에도 데이터가 초기화 되지 않는다. (장점)
## 2.3. Throttle별 캐시 설정
---
>[https://docs.djangoproject.com/en/2.2/ref/settings/#caches](https://docs.djangoproject.com/en/2.2/ref/settings/#caches)
[https://www.django-rest-framework.org/api-guide/throttling/#setting-up-the-cache](https://www.django-rest-framework.org/api-guide/throttling/#setting-up-the-cache)
settings.CACHES의 "default" 사용
Throttle 클래스 별로 다른 캐시 설정 지원
```python
from django.core.cache import caches
class CustomAnonRateThrottle(AnonRateThrottle):
cache = caches['alternate']
SingleRateThrottle에서는 요청한 시간의 timestamp를 list로 유지. 매 요청시마다 다음을 수행.
우선순위 : X-Forwarded-For > REMOTE_ADDR
# 프로젝트/settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [],
'DEFAULT_THROTTLE_RATES': {
'contact': '1000/day',
'upload': '20/day',
},
}
# myapp/throttles.py
class CotactRateThrottle(UserRateThrottle):
scope = 'contact'
class UploadRateThrottle(UserRateThrottle):
scope = 'upload'
# myapp/views.py
class ContactListView(APIView):
throttle_classes = [CotactRateThrottle]
class ContactDetailView(APIView):
throttle_classes = [ContactRateThrottle]
class UploadView(APIView):
throttle_classes = [UploadRateThrottle]
# 프로젝트/settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.ScopedRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'contact': '1000/day',
'upload': '20/day',
},
}
# myapp/views.py
class ContactListView(APIView):
throttle_scope = 'contact'
class ContactDetailView(APIView):
throttle_scope = 'contact'
class UploadView(APIView):
throttle_scope = 'upload'
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'premium_user': '1000/day',
'light_user': '10/day',
},
}
from rest_framework import viewsets
from .serializers import PostSerializer
from .throttling import PremiumThrottle
from .models import Post
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
throttle_classes = [PremiumThrottle]
premium_scope = 'premium_user'
light_scope = 'light_user'
def perform_create(self, serializer):
serializer.save(author=self.request.user)
from rest_framework.throttling import UserRateThrottle
class PremiumThrottle(UserRateThrottle):
def __init__(self):
"""User에 따라 scope이 달라지기에, 생성자에서는 get_rate()를 수행하지 않도록 했습니다."""
pass # 생성자에서 기존의 scope을 구현하는 부분이 동작하지 않도록 비워둔다(pass).
def allow_request(self, request, view): # allow_request라는 멤버함수 구현을 통해서 현재 이 요청을 허용/거부를 결정한다.
premium_scope = getattr(view, 'premium_scope', None)
light_scope = getattr(view, 'light_scope', None)
if request.user.profile.is_premium_user:
if not premium_scope:
return True
self.scope = premium_scope # premium_scope설정이 없다면, 제한을 두지 않습니다.
else:
if not light_scope:
return True
self.scope = light_scope # light_scope 설정이 없다면, 제한을 두지 않습니다.
self.rate = self.get_rate()
self.num_requests, self.duration = self.parse_rate(self.rate)
return super().allow_request(request, view)