keter-rate-limiting-plugin: Simple Keter rate limiting plugin.

[ library, mit, network, rate-limiter ] [ Propose Tags ] [ Report a vulnerability ]

A modern, high-performance, and highly customisable rate limiting plugin for keter. Provides four window algorithms (Fixed Window, Sliding Window, Token Bucket, Leaky Bucket), as well as TinyLRU cache approach, IP zone support, and convenient and customisable API. See README.md and homepage for usage and details.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0, 0.1.0.1, 0.1.0.2, 0.1.1.0, 0.1.2.0, 0.2.0.0 (info)
Change log CHANGELOG.md
Dependencies aeson (>=1.4), base (>=4.7 && <5), bytestring (>=0.10), cache (>=0.1), case-insensitive (>=1.2.1.0), clock (>=0.8.3 && <1), cookie, deepseq, directory (>=1.3.4.0 && <1.4), focus (>=1.0.3), hashable (>=1.4.2.0 && <2), http-types (>=0.12.3 && <0.13), iproute (>=1.7.10), list-t (>=1.0.5 && <2), network (>=3.1.2 && <3.2 || >=3.2.0 && <3.3), stm (>=2.5.0 && <2.6), stm-containers (>=1.2 && <2), text (>=1.2), time (>=1.9), unordered-containers (>=0.2.17 && <0.3), wai (>=3.2.3 && <3.3) [details]
Tested with ghc ==9.2.8, ghc ==9.4.8, ghc ==9.6.5, ghc ==9.8.4
License MIT
Copyright 2025 Oleksandr Zhabenko
Author Oleksandr Zhabenko
Maintainer oleksandr.zhabenko@yahoo.com
Category Network
Home page https://github.com/Oleksandr-Zhabenko/keter-rate-limiting-plugin
Uploaded by OleksandrZhabenko at 2025-08-18T20:44:55Z
Distributions
Downloads 18 total (18 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2025-08-18 [all 1 reports]

Readme for keter-rate-limiting-plugin-0.1.2.0

[back to package description]

keter-rate-limiting-plugin

keter-rate-limiting-plugin is a modern, high-performance, and highly customizable rate-limiting plugin for Keter. It addresses issue #301 and brings robust, production-grade request throttling to Haskell web applications, featuring efficient in-memory caching with HashMap-based lookups and IP zone isolation.

This library is inspired by rack-attack and Ruby on Rails (for Keter.RateLimiter.Notifications) and provides a powerful middleware for Keter-managed applications, though it can be integrated with any WAI-compatible Haskell web stack.

Features

  • Five window algorithms:
    • Fixed Window
    • Sliding Window
    • Token Bucket
    • Leaky Bucket
    • TinyLRU (Least Recently Used)
  • IP Zone Support: Isolate caches and throttling policies per IP zone, customer segment, or any other logical grouping with efficient HashMap-based zone lookups.
  • Declarative Configuration: Define throttling rules using JSON/YAML configuration with automatic serialization support.
  • Flexible Client Identification: Multiple strategies for identifying clients (IP, headers, cookies, combinations).
  • Configurable Zone Derivation: Flexible strategies for deriving IP zones from requests.
  • WAI Middleware: Integrates seamlessly as a middleware into any WAI application.
  • Convenient and Customizable API:
    • Use declarative configuration for common scenarios with automatic setup.
    • Or, for advanced use, fully control cache key structure and throttling logic.
  • Memory-efficient: Designed for large-scale, high-traffic deployments with automatic cleanup of expired entries and HashMap-based O(1) average-case lookups.
  • Easy Integration: Minimal code changes are required to get started.

Why Use This Plugin?

  • Scalability: Per-zone caches with HashMap-based storage and flexible throttling allow you to scale from single-user apps to multi-tenant platforms.
  • Performance: The in-memory backend is built on efficient STM-based containers with HashMap optimizations for high-concurrency workloads.
  • Security: Protects your application from abusive clients and denial-of-service attacks.
  • Flexibility: Choose between declarative configuration and full programmatic customization.
  • Production-Ready: Inspired by industry-standard tools, thoroughly documented, and designed for reliability with efficient data structures.
  • Open Source: MIT licensed and community-friendly.

Installation

Add the package to your build-depends in your project's .cabal file or package.yaml.

For Cabal:

build-depends:
  , keter-rate-limiting-plugin

For Stack (package.yaml):

dependencies:
- keter-rate-limiting-plugin

Then, rebuild your project. No external C libraries are required.

Quick Start

The recommended approach uses declarative configuration that can be loaded from JSON or YAML files:

{-# LANGUAGE OverloadedStrings #-}

import Keter.RateLimiter.WAI
import Keter.RateLimiter.Cache (Algorithm(..))
import Network.Wai (responseLBS, Application)
import Network.HTTP.Types (status200)
import Network.Wai.Handler.Warp (run)

-- A simple application that runs behind the middleware.
myApp :: Application
myApp _ respond = respond $ responseLBS status200 [] "Hello, you are not rate limited!"

main :: IO ()
main = do
  -- 1. Define declarative configuration
  let config = RateLimiterConfig
        { rlZoneBy = ZoneIP  -- Separate zones by client IP
        , rlThrottles = 
            [ RLThrottle "api"   100 3600 FixedWindow IdIP Nothing        -- 100 requests/hour by IP
            , RLThrottle "login" 5   300  TokenBucket  IdIP (Just 600)    -- 5 login attempts/5min by IP with 10min idle timeout
            ]
        }

  -- 2. Build middleware from configuration
  middleware <- buildRateLimiter config

  -- 3. Apply middleware to your application
  let appWithMiddleware = middleware myApp

  putStrLn "Server starting on port 8080..."
  run 8080 appWithMiddleware

JSON Configuration

You can also load configuration from JSON files:

{
  "zone_by": "ip",
  "throttles": [
    {
      "name": "api",
      "limit": 100,
      "period": 3600,
      "algorithm": "fixed_window",
      "identifier_by": "ip"
    },
    {
      "name": "login",
      "limit": 5,
      "period": 300,
      "algorithm": "token_bucket",
      "identifier_by": "ip",
      "token_bucket_ttl": 600
    }
  ]
}

Advanced Programmatic Configuration

For more control, you can build the environment programmatically:

import Keter.RateLimiter.WAI
import Keter.RateLimiter.Cache (Algorithm(..))
import Keter.RateLimiter.IPZones (defaultIPZone)
import Data.Text.Encoding (encodeUtf8)

main :: IO ()
main = do
  -- 1. Initialize environment with custom zone logic
  env <- initConfig $ \req -> 
    case requestHeaderHost req of
      Just "api.example.com" -> "api_zone"
      Just "admin.example.com" -> "admin_zone"
      _ -> defaultIPZone

  -- 2. Add throttle configurations
  let apiThrottle = ThrottleConfig
        { throttleLimit      = 1000
        , throttlePeriod     = 3600
        , throttleAlgorithm  = FixedWindow
        , throttleIdentifier = \req -> fmap (encodeUtf8 . show) (remoteHost req)
        , throttleTokenBucketTTL = Nothing
        }

  let loginThrottle = ThrottleConfig
        { throttleLimit      = 5
        , throttlePeriod     = 300
        , throttleAlgorithm  = TokenBucket
        , throttleIdentifier = \req -> fmap (encodeUtf8 . show) (remoteHost req)
        , throttleTokenBucketTTL = Just 600
        }

  env' <- addThrottle env "api" apiThrottle
  env'' <- addThrottle env' "login" loginThrottle

  -- 3. Create middleware
  let middleware = buildRateLimiterWithEnv env''
      appWithMiddleware = middleware myApp

  putStrLn "Server starting on port 8080..."
  run 8080 appWithMiddleware

Configuration Reference

Client Identification Strategies (IdentifierBy)

  • "ip" - Identify by client IP address
  • "ip+path" - Identify by IP address and request path
  • "ip+ua" - Identify by IP address and User-Agent header
  • {"header": "X-API-Key"} - Identify by custom header value
  • {"cookie": "session_id"} - Identify by cookie value
  • {"header+ip": "X-User-ID"} - Identify by header value combined with IP

Zone Derivation Strategies (ZoneBy)

  • "default" - All requests use the same cache (no zone separation)
  • "ip" - Separate zones by client IP address
  • {"header": "X-Tenant-ID"} - Separate zones by custom header value

Rate Limiting Algorithms

  • FixedWindow - Traditional fixed-window counting
  • SlidingWindow - Precise sliding-window with timestamp tracking
  • TokenBucket - Allow bursts up to capacity, refill over time
  • LeakyBucket - Smooth rate limiting with configurable leak rate
  • TinyLRU - Least-recently-used eviction for memory efficiency

Example Usage

Using the Convenient API

The CacheWithZone module provides helpers that automatically compose cache keys from the algorithm, zone, and user key, simplifying common use cases while leveraging efficient HashMap-based zone lookups.

import Keter.RateLimiter.Cache
import Keter.RateLimiter.CacheWithZone

-- Create a store and cache for the Fixed Window algorithm
fixedWindowStore <- createInMemoryStore @'FixedWindow
let cache = newCache FixedWindow fixedWindowStore

-- Increment a counter for a user in a specific zone.
-- The key "rate_limiter:zoneX:userX" is created automatically.
-- The request is allowed if the count is within the limit.
-- Zone lookup uses HashMap for O(1) average performance.
isAllowed <- allowFixedWindowRequest cache "zoneX" "userX" 100 3600 -- 100 requests per hour

Using the Customizable API

For more complex scenarios, you can manually construct cache keys and interact directly with the Cache module. This gives you full control over the key structure while still benefiting from HashMap-optimized storage.

import Keter.RateLimiter.Cache

-- Use the same cache from the previous example.
let customKey = "rate_limiter:fixed_window:logins:zoneY:userY"

-- Manually increment the counter for the custom key.
newCount <- incrementCache cache customKey 60 -- TTL of 60 seconds

-- Manually read the value.
mVal <- readCache cache customKey :: IO (Maybe Int)

Multi-Algorithm Configuration Example

let config = RateLimiterConfig
      { rlZoneBy = ZoneHeader "X-Tenant-ID"  -- Separate by tenant
      , rlThrottles = 
          [ RLThrottle "api_burst"     100  60   TokenBucket   IdIP              (Just 300)
          , RLThrottle "api_sustained" 1000 3600 FixedWindow   IdIP              Nothing
          , RLThrottle "login"         5    300  LeakyBucket   IdIP              Nothing
          , RLThrottle "admin"         50   3600 SlidingWindow (IdHeader "X-Admin-Key") Nothing
          , RLThrottle "lru_cache"     1000 60   TinyLRU       IdIPAndPath       Nothing
          ]
      }

Performance Characteristics

This library is optimized for high-performance scenarios:

  • HashMap-based zone caches: O(1) average-case lookup for IP zone cache resolution
  • HashMap-based throttle storage: O(1) average-case retrieval of throttle configurations
  • STM-based concurrent access: Thread-safe operations with minimal contention
  • Memory-efficient algorithms: Automatic cleanup of expired entries across all rate limiting algorithms
  • Scalable architecture: Designed to handle thousands of concurrent requests with minimal overhead

Testing

This package includes an extensive test suite covering all supported rate-limiting algorithms, IP zone isolation, cache management, and HashMap-based performance optimizations.

To run the tests:

cabal test

or

stack test

When to Use This Library

  • You need robust and efficient request throttling for your Haskell web application.
  • You want to protect your service from abuse and DoS attacks.
  • You require per-zone or per-user isolation of throttling policies with efficient lookups.
  • You value both declarative configuration and the ability to customize behavior as needed.
  • You need high-performance rate limiting that can scale to handle large numbers of concurrent requests and zones.

Migration from Earlier Versions

If you're upgrading from an earlier version that used the programmatic API, the declarative configuration approach is now recommended:

Old approach:

env <- initConfig getZoneFunction
env' <- addThrottle env "api" throttleConfig
let middleware = attackMiddleware env'

New recommended approach:

let config = RateLimiterConfig { ... }
middleware <- buildRateLimiter config

The old programmatic API is still fully supported for advanced use cases.

License

MIT License © 2025 Oleksandr Zhabenko

References