Skip to content

Rate Limiting Decorators

Rate limiting decorators allow you to apply custom rate limits to specific endpoints, overriding global settings and providing fine-grained control over request frequencies. These decorators help prevent abuse and ensure fair usage of your API resources.

All examples below assume the following setup exists once at module level:

from tornadoapi_guard import (
    SecurityConfig,
    SecurityDecorator,
    SecurityHandler,
    SecurityMiddleware,
)

config = SecurityConfig()
middleware = SecurityMiddleware(config=config)
guard_deco = SecurityDecorator(config)
middleware.set_decorator_handler(guard_deco)

Basic Rate Limiting

Apply custom rate limits to specific routes:

. Simple Rate Limit

class RateCustomLimitHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=10, window=300)  # 10 requests per 5 minutes
    async def get(self) -> None:
        self.write({"data": "rate limited"})

. Override Global Settings

# Global rate limit: 100 requests/hour
config = SecurityConfig(rate_limit_requests=100, rate_limit_window=3600)


class RatePublicHandler(SecurityHandler):
    async def get(self) -> None:
        # Uses global: 100 requests/hour
        self.write({"data": "public"})


class RateStrictLimitHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=5, window=300)  # Override: 5 requests/5min
    async def get(self) -> None:
        # Uses decorator: 5 requests/5min
        self.write({"data": "strictly limited"})

. Different Limits for Different Operations

class RateReadHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=100, window=3600)  # 100 reads/hour
    async def get(self) -> None:
        self.write({"data": "read operation"})


class RateWriteHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=10, window=3600)  # 10 writes/hour
    async def post(self) -> None:
        self.write({"status": "write operation"})


class RateDeleteHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=5, window=3600)  # 5 deletes/hour
    async def delete(self) -> None:
        self.write({"status": "delete operation"})

Endpoint-Specific Rate Limits

Tailor rate limits to endpoint sensitivity and usage patterns:

. Authentication Endpoints

class LoginHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=5, window=300)  # 5 attempts per 5 minutes
    async def post(self) -> None:
        self.write({"token": "jwt_token"})


class RegisterHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=3, window=3600)  # 3 registrations per hour
    async def post(self) -> None:
        self.write({"status": "user created"})


class ForgotPasswordHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=2, window=3600)  # 2 reset requests per hour
    async def post(self) -> None:
        self.write({"status": "reset email sent"})

. Sensitive Operations

class PaymentHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=3, window=600)  # 3 payments per 10 minutes
    async def post(self) -> None:
        self.write({"status": "payment processed"})


class AdminBanHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=10, window=3600)  # 10 bans per hour
    async def post(self) -> None:
        self.write({"status": "user banned"})


class AdminPurgeHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=1, window=86400)  # 1 purge per day
    async def delete(self) -> None:
        self.write({"status": "data purged"})

. Resource-Intensive Operations

class ExportHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=2, window=3600)  # 2 exports per hour
    async def post(self) -> None:
        self.write({"status": "export started"})


class ReportHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=5, window=3600)  # 5 reports per hour
    async def post(self) -> None:
        self.write({"status": "report generating"})


class ComplexSearchHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=20, window=300)  # 20 searches per 5 minutes
    async def post(self) -> None:
        self.write({"results": "search results"})

Geographic Rate Limiting

Apply different rate limits based on user's geographic location:

. Country-Specific Limits

class RateGeoHandler(SecurityHandler):
    @guard_deco.geo_rate_limit(
        {
            "US": (100, 3600),    # 100 requests/hour for US
            "CA": (100, 3600),    # 100 requests/hour for Canada
            "GB": (80, 3600),     # 80 requests/hour for UK
            "DE": (80, 3600),     # 80 requests/hour for Germany
            "CN": (10, 3600),     # 10 requests/hour for China
            "*": (50, 3600),      # 50 requests/hour for others
        }
    )
    async def get(self) -> None:
        self.write({"data": "geographic rate limited"})

. Tiered Geographic Access

class PremiumGeoHandler(SecurityHandler):
    @guard_deco.geo_rate_limit(
        {
            # Tier 1: Premium countries
            "US": (200, 3600),
            "CA": (200, 3600),
            "GB": (200, 3600),
            "DE": (200, 3600),
            "AU": (200, 3600),
            # Tier 2: Standard countries
            "FR": (100, 3600),
            "IT": (100, 3600),
            "ES": (100, 3600),
            "JP": (100, 3600),
            # Tier 3: Limited countries
            "IN": (50, 3600),
            "BR": (50, 3600),
            "MX": (50, 3600),
            # Default: Very limited
            "*": (20, 3600),
        }
    )
    async def get(self) -> None:
        self.write({"data": "premium geographic content"})

. Regional Business Hours

class SupportGeoHandler(SecurityHandler):
    @guard_deco.geo_rate_limit(
        {
            "US": (50, 3600),     # US business hours
            "EU": (40, 3600),     # European business hours
            "APAC": (30, 3600),   # Asia-Pacific business hours
            "*": (20, 3600),      # Outside business regions
        }
    )
    async def get(self) -> None:
        self.write({"data": "support information"})

Time-Based Rate Limiting

Combine rate limiting with time windows:

. Business Hours vs After Hours

class BusinessHoursOrderHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=50, window=3600)  # 50 orders/hour during business
    @guard_deco.time_window("09:00", "17:00", "EST")
    async def post(self) -> None:
        self.write({"status": "business hours order"})


class AfterHoursOrderHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=20, window=3600)  # 20 orders/hour after hours
    async def post(self) -> None:
        self.write({"status": "after hours order"})

. Weekend vs Weekday

class WeekdayDataHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=100, window=3600)
    async def get(self) -> None:
        self.write({"data": "weekday business data"})


class WeekendDataHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=30, window=3600)
    async def get(self) -> None:
        self.write({"data": "weekend personal data"})

Advanced Rate Limiting Patterns

. Graduated Rate Limits

Different limits based on endpoint complexity:

class SimpleStatusHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=1000, window=3600)
    async def get(self) -> None:
        self.write({"status": "ok"})


class MediumDataHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=100, window=3600)
    async def get(self) -> None:
        self.write({"data": "medium complexity"})


class ComplexAnalysisHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=10, window=3600)
    async def post(self) -> None:
        self.write({"result": "complex analysis"})

. User Tier-Based Limits

class FreeTierHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=100, window=86400)  # 100 per day
    async def get(self) -> None:
        self.write({"data": "free tier"})


class PremiumTierHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=1000, window=3600)  # 1000 per hour
    async def get(self) -> None:
        self.write({"data": "premium tier"})


class EnterpriseTierHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=10000, window=3600)  # 10000 per hour
    async def get(self) -> None:
        self.write({"data": "enterprise tier"})

. API Versioning Rate Limits

class V1DataHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=50, window=3600)
    async def get(self) -> None:
        self.write({"data": "legacy v1", "deprecated": True})


class V2DataHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=100, window=3600)
    async def get(self) -> None:
        self.write({"data": "current v2"})


class V3DataHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=200, window=3600)
    async def get(self) -> None:
        self.write({"data": "beta v3"})

Combining with Other Decorators

Stack rate limiting with other security measures:

. Rate Limiting + Access Control

class AdminActionHandler(SecurityHandler):
    @guard_deco.require_ip(whitelist=["10.0.0.0/8"])     # Internal network only
    @guard_deco.rate_limit(requests=20, window=3600)     # 20 actions per hour
    async def post(self) -> None:
        self.write({"status": "admin action completed"})

. Rate Limiting + Authentication

class UserProfileHandler(SecurityHandler):
    @guard_deco.require_auth(type="bearer")
    @guard_deco.rate_limit(requests=60, window=3600)  # 60 requests per hour
    async def get(self) -> None:
        self.write({"profile": "user data"})

. Rate Limiting + Behavioral Analysis

class GameActionHandler(SecurityHandler):
    @guard_deco.rate_limit(requests=100, window=3600)      # 100 actions per hour
    @guard_deco.usage_monitor(max_calls=50, window=300)    # Monitor for burst usage
    async def post(self) -> None:
        self.write({"result": "game action completed"})

Error Handling

Rate limiting decorators return specific HTTP status codes:

  • 429 Too Many Requests: Rate limit exceeded
  • 503 Service Unavailable: Rate limiting service unavailable

. Custom Rate Limit Messages

config = SecurityConfig(
    custom_error_responses={
        429: "Rate limit exceeded. Please try again later.",
    }
)

# The response will include rate limit headers:
# X-RateLimit-Limit: 10
# X-RateLimit-Remaining: 0
# X-RateLimit-Reset: 1640995200

Best Practices

. Match Limits to Endpoint Purpose

Consider the business purpose and resource cost:

# Good: High limits for lightweight operations
@guard_deco.rate_limit(requests=1000, window=3600)  # Status checks

# Good: Low limits for expensive operations
@guard_deco.rate_limit(requests=5, window=3600)     # Data exports

# Avoid: Same limits for all endpoints

. Consider User Experience

Don't make limits so strict they hurt legitimate users:

# Good: Reasonable limits for normal usage
@guard_deco.rate_limit(requests=100, window=3600)   # Allows normal browsing

# Bad: Too restrictive for normal use
# @guard_deco.rate_limit(requests=5, window=3600)   # Hurts legitimate users

. Use Geographic Limits Thoughtfully

Consider infrastructure and business presence:

# Good: Higher limits where you have better infrastructure
@guard_deco.geo_rate_limit({
    "US": (200, 3600),  # Strong US presence
    "EU": (150, 3600),  # Good EU infrastructure
    "AS": (100, 3600),  # Developing APAC presence
})

. Provide Clear Error Messages

Help users understand the limits:

config = SecurityConfig(
    custom_error_responses={
        429: "Rate limit exceeded. You can make 100 requests per hour. Current window resets at {reset_time}.",
    }
)

Monitoring and Analytics

Track rate limiting effectiveness:

# Enable detailed logging for rate limiting analysis
config = SecurityConfig(
    log_request_level="INFO",       # Log all requests
    log_suspicious_level="WARNING",  # Log rate limit violations
)

# Logs will show:
# "Rate limit exceeded for IP: 203.0.113.1"
# "Rate limit window reset for endpoint: /api/data"

Testing Rate Limits

Test your rate limiting decorators with AsyncHTTPTestCase:

import time

from tornado.testing import AsyncHTTPTestCase


class RateLimitTests(AsyncHTTPTestCase):
    def get_app(self):
        return build_application()

    def test_rate_limit(self):
        for _ in range(5):
            response = self.fetch("/api/limited")
            self.assertEqual(response.code, 200)

        response = self.fetch("/api/limited")
        self.assertEqual(response.code, 429)

        time.sleep(301)
        response = self.fetch("/api/limited")
        self.assertEqual(response.code, 200)

Next Steps

Now that you understand rate limiting decorators, explore other security features:

For complete API reference, see the Rate Limiting API Documentation.