Rate Limit

Keywords: rate limiter, prevent brute force, client limit, rate limit key, request throttling, slide window, redis rate limit

Protect your application from brute-force attacks and abuse with Asok’s built-in rate limiter.

Quick Start (Decorator)

The easiest way to protect a specific page is using the @rate_limit decorator in your page.py:

# src/pages/protected/page.py
from asok import rate_limit

@rate_limit("5/m")  # Limit to 5 requests per minute
def render(request):
    return "This page is protected!"

Limit Syntax

Asok supports human-readable strings for defining limits:

  • "10/s": 10 requests per second

  • "60/m": 60 requests per minute

  • "1000/h": 1000 requests per hour

  • "5000/d": 5000 requests per day

Global Middleware

To apply a global rate limit, create src/middlewares/ratelimit.py:

from asok import RateLimit

# Use a string limit for simplicity
limiter = RateLimit("100/m")

def handle(request, next):
    return limiter(request, next)

4. Production Storage (Shared Limit)

By default, the rate limiter works in-memory (per Gunicorn worker) for maximum performance and security against memory leaks when using the default memory cache.

For production systems with multiple workers or multi-node servers, Asok automatically scales the rate limiter when a distributed cache backend is configured:

  • If ASOK_CACHE_BACKEND is set to redis or file, the rate limiter automatically resolves and uses the global distributed cache (default_cache).

  • To prevent collisions between different routes sharing the same cache backend, the @rate_limit decorator automatically prefixes keys using the module and function name of the decorated view (e.g. rl:src.pages.home.render:<ip>).

Manual Storage & Prefixes

You can also explicitly pass a custom cache instance or prefix:

from asok import RateLimit, Cache

# Explicitly use a custom cache instance and custom prefix prefix
custom_cache = Cache(backend="redis")
limiter = RateLimit("60/m", storage=custom_cache, prefix="my_custom_limit")

Or using the decorator:

@rate_limit("10/m", prefix="sensitive_api")
def render(request):
    return "Custom Prefix applied"

Programmatic Rate Limiting (Custom Responses)

If you want to perform custom actions (like flashing an error message) instead of rendering the default 429 page when a limit is exceeded, you can check the rate limit programmatically inside your view function.

To do this, call request.rate_limit() inside a try/except block and catch RateLimitExceeded:

from asok import Request, RateLimitExceeded
from asok.forms import Form
from my_app.models import Contact

def post(request: Request):
    form = Form.from_model(Contact, request)
    try:
        # Check limit programmatically
        request.rate_limit("3/day")
        
        if form.validate():
            Contact.create(**form.data)
            return request.block("page.html", "main", form=form.reset())
    except RateLimitExceeded:
        # Catch and handle it, e.g. using a flash message
        request.flash("error", "You have exceeded the rate limit. Please try again later.")
        
    return request.html('page.html', form=form)

[!NOTE]

  • When using request.rate_limit() without a prefix, Asok automatically generates a unique prefix based on the calling module and function name (e.g. rl:src.pages.contact.post).

  • If RateLimitExceeded is raised but not caught inside the view, it bubbles up and automatically triggers the framework’s default 429 error page rendering.

5. Options

Option

Type

Description

limit

str/int

Maximum requests (e.g., "60/m" or 60).

window

int

Window duration in seconds (if limit is an int).

key_func

func

Custom function to identify clients (default: IP address).

storage

Cache

Optional Cache instance for cross-process persistence (defaults to global cache if redis or file is active).

prefix

str

Optional custom prefix for the rate-limit cache keys (defaults to "rl" for middleware, or automatically generated based on route function for the @rate_limit decorator).