packages feed

keter-rate-limiting-plugin-0.1.1.0: README.md

# keter-rate-limiting-plugin

**keter-rate-limiting-plugin** is a modern, high-performance, and highly customizable rate-limiting plugin for [Keter](https://github.com/snoyberg/keter). It addresses [issue \#301](https://github.com/snoyberg/keter/issues/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](https://github.com/rack/rack-attack) and and [Ruby on Rails](https://github.com/rails/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.
  - **Flexible Throttle Configuration**: Set limits, periods, algorithms, and unique identifiers on a per-throttle basis, stored in optimized HashMap structures.
  - **WAI Middleware**: Integrates seamlessly as a middleware into any WAI application.
  - **Convenient and Customizable API**:
      - Use simple wrappers for common scenarios with automatic key composition.
      - 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 the convenience of wrappers and the full customizability of manual key management.
  - **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:**

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

**For Stack (`package.yaml`):**

```yaml
dependencies:
- keter-rate-limiting-plugin
```

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

## Quick Start

The following example sets up a simple WAI application with a single rate-limiting rule: 10 requests per 60 seconds from a given IP address. It also demonstrates assigning requests to different IP zones using efficient HashMap-based zone resolution.

```haskell
{-# LANGUAGE OverloadedStrings #-}

import Keter.RateLimiter.WAI
import Keter.RateLimiter.Cache (Algorithm(..))
import Keter.RateLimiter.IPZones (defaultIPZone)
import Network.Wai (Request, responseLBS, Application)
import Network.HTTP.Types (status200)
import Network.Wai.Handler.Warp (run)
import Data.Text.Encoding (encodeUtf8)

-- 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. Initialize the rate limiter configuration.
  -- This function determines the IP Zone for each request.
  -- Here, we route traffic from "127.0.0.1" to "local_zone" and all other traffic to the default zone.
  -- The environment uses HashMap-based storage for efficient zone cache lookups.
  env <- initConfig (\req -> if requestHeaderHost req == Just "127.0.0.1" then "local_zone" else defaultIPZone)

  -- 2. Define a throttle rule.
  let ipThrottle = ThrottleConfig
        { throttleLimit      = 10
        , throttlePeriod     = 60
        , throttleAlgorithm  = FixedWindow
        , throttleIdentifier = \req -> fmap (encodeUtf8 . show) (remoteHost req) -- Identify requests by IP address
        , throttleTokenBucketTTL = Nothing -- Not used for FixedWindow
        }

  -- 3. Add the throttle rule to the environment.
  -- The throttle is stored in a HashMap for O(1) average-case retrieval.
  env' <- addThrottle env "req/ip" ipThrottle

  -- 4. Wrap your application with the middleware.
  let appWithMiddleware = attackMiddleware env' myApp

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

## 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.

```haskell
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.

```haskell
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)
```

### Token Bucket Example (with TTL)

The Token Bucket algorithm allows for bursts of traffic. You can also specify a TTL for how long an idle bucket remains in memory. The throttle configuration is efficiently stored and retrieved using HashMap-based lookups.

```haskell
import Keter.RateLimiter.WAI
import Keter.RateLimiter.Cache (Algorithm(..))

let tokenBucketThrottle = ThrottleConfig
      { throttleLimit      = 100 -- Bucket capacity
      , throttlePeriod     = 60  -- Refills 100 tokens over 60 seconds
      , throttleAlgorithm  = TokenBucket
      , throttleIdentifier = \req -> getAuthToken req -- A function to get a user's API token
      , throttleTokenBucketTTL = Just 7200 -- Purge idle buckets after 2 hours
      }

-- env' <- addThrottle env "api/token" tokenBucketThrottle
-- The throttle will be stored in the environment's HashMap for efficient retrieval
```

## 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:

```bash
cabal test
```

or

```bash
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 convenience 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.

## License

MIT License © 2025 Oleksandr Zhabenko

## References

  - [rack-attack (Ruby)](https://github.com/rack/rack-attack)
  - [keter (Haskell)](https://github.com/snoyberg/keter)