최대 호출 제한이 없는 API 접속은 위험합니다. DRF 에서 지원하는 Throttling 설정을 사용하면 최대 호출 횟수를 제한할 수 있습니다. Throttling 을 통해 분당 최대 요청을 제한해 봅시다.
Throttle
는 특정 조건 하에 최대 호출 횟수를 결정하는 클래스입니다. 이와 관련된 포맷으로는 Rate
, Scope
가 있습니다.
{숫자}/{간격}
"60/s" # 분당 최대 60개의 요청
"1000/day" # 하루 요청 회수 1000개 제한
AnonRateThrottle
Throttle
클래스 별로 scope
1개만 지정 가능anon
UserRateThrottle
Throttle
클래스 별로 scope
1개만 지정 가능user
ScopedRateThrottle
throttle_scope
값을 읽어들여, APIView 별로 다른 scope
적용비인증 요청 횟수 제한이 아닌, 비인증 요청 거부에 대한 설정은 Throttling 이 아닌 Permission 의 영역 입니다.
Throttle
의 default 설정은 다음과 같습니다.
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [],
'DEFAULT_THROTTLE_RATES': {
`anon': None
'user': None
},
}
전역 설정을 원한다면 settings.py
에서 아래와 같이 원하는 CLASS 와 RATE 를 설정해주면 됩니다.
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.UserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'user': '10/day', # 하루에 10번까지의 요청만 허용. 11번째 요청 거부
},
}
전역 설정이 아닌 각 APIView 별로 개별 설정을 원한다면 throttle_classes
로 지정해주면 됩니다.
# views.py
from rest_framework.throttling import UserRateThrottle
class PostViewSet(ViewSet):
throttle_classes = UserRateThrottle
최대 호출 횟수 제한을 넘길 시 429 Too Many Requests 응답을 반환합니다.
{
"detail": "Request was throttled. Expected available in 86361 seconds."
}
X-Fowarded-For
: 프록시나 로드 밸런스를 통해 서버에 접속하는 클라이언트의 IP 주소를 식별하는 표준 헤더.클라이언트 ip 에서 요청 시, X-Fowarded-For
헤더 이름으로 요청이 들어옵니다.
AWS의 로드밸런스와 같은 요청을 중계하는 서버에서 X-Fowarded-For
를 지원한다면 실제 클라이언트 ip 요청 횟수를 체크할 수 있습니다. X-Forwarded-for
를 지원하지않는다면 REMOTE_ADDR
을 활용합니다.
우선순위 : X-Forwarded-For > REMOTE_ADDR
views.py 의 throttle_classes
에 각기 필요한 Throttle
을 지정합니다.
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [],
'DEFAULT_THROTTLE_RATES': {
'contact': '1000/day',
'upload': '20/day',
},
}
# throttles.py
class ContactRateThrottle(UserRateThrottle):
scope = 'contact'
class = UploadRateThrottle(UserRateThrottle):
scope = 'upload'
# views.py
class ContactListView(APIView):
throttle_classes = [ContactRateThrottle]
class ContactDetailView(APIView):
throttle_classes = [ContactRateThrottle]
class UploadView(APIView):
throttle_classes = [UploadRateThrottle]
throttle_classes
를 사용하는 방법은 매번 클래스 이름을 상속시켜 주는 일이 번거롭습니다.
ScopedRateThrottle 을 settings.py
에 적용하여 코드를 간결하게 만듭니다.
# settings.py
REST_FRAMEWORK = {
# ScopedRateThrottle 적용
'DEFAULT_THROTTLE_CLASSES': [rest_framework.throttling.ScopedRateThrottle],
'DEFAULT_THROTTLE_RATES': {
'contact': '1000/day',
'upload': '20/day',
},
}
# views.py
class ContactListView(APIView):
throttle_scope = 'contact'
class ContactDetailView(APIView):
throttle_scope = 'contact'
class UploadView(APIView):
throttle_scope = 'upload'
커스텀 Throttle 을 만들기 위해 allow_request
함수를 재정의 합니다.
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'premium_user': '1000/day', # premium 유저는 하루에 1000회 요청 제한
'light_user': '10/day', # light 유저는 하루에 10회 요청 제한
},
}
# throttles.py
from rest_framework.throttling import UserRateThrottle
class PremiumThrottle(UserRateThrottle):
# 본 Throttle에서는 생성자에서 get_rate가져오는 것은 불필요하므로
# 생성자 오버로딩을 통해 루틴 제거
def __init__(self):
pass
def allow_request(self, request, view):
premium_scope = getattr(view, 'premium_scope', None)
light_scope = getattr(view, 'light_scope', None)
# Profile모델에 is_premium_user 필드가 있다고 가정
if request.user.profile.is_premium_user:
if not premium_scope: # premium_scope 미지정 시에는 Throttling제한을 하지않음
return True
self.scope = premium_scope
else:
if not light_scope: # light_scope 미지정 시에는 Throttling제한을 하지않음
return True
self.scope = light_scope
self.rate = self.get_rate()
self.num_requests, self.duration = self.parse_rate(self.rate)
return super().allow_request(request, view)
# views.py
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):
print(self.request.FILES)
serializer.save(author=self.request.user)
매 요청 시마다 cache 에서 timestamp list
를 get/set 하고 있어 프로젝트가 커질수록 cache 의 성능이 중요해집니다. django 기본의 SimpleRateThrottle
에서는 다음과 같이 default_cache 를 설정하고 있습니다.
# rest_framewor/throttlig.py
from django.core.cache import cache as default_cache
class SimpleRateThrottle(BaseThrottle):
cache = default_cache
django settings 에서는 default 로 로컬 메모리 캐시 를 사용하고 있어, 서버가 재시작되면 cache 가 모두 초기화 됩니다. 로컬 메모리 캐시 이외에도 다양한 캐시를 지원하고 있습니다.
django.core.cache.backends.MemcachedCache
django.core.cache.backends.PyLibMCCache
django.core.cache.backends.DatabaseCache
django.core.cache.backends.filebased.FileBasedCache
django.core.cache.backends.LocMemCache
django.core.cache.backends.DummyCache
redis 를 활용한 캐시
Memcached 와 유사하나 메모리 캐시 뿐만이 아닌, 디스크 동기화에도 싱크를 맞추고 있어 재시작하여도 초기화 되지 않는 장점이 있습니다.