Help us measure site usage?

With your permission, we use self-hosted analytics to understand page usage. It is optional and does not affect the Service. Learn more.

RubyadvanceddevopsAI generated

Custom Rack Middleware Rate Limiter

Build a Rack middleware in Ruby that limits how many requests each client can make, for example 100 per minute per IP address, and that works in any Ruby web app built on Rack, including Rails and Sinatra. When a client goes over the limit, it answers 429 Too Many Requests with headers telling them when to try again. It uses a sliding window, so a client can't cheat by bursting at the edge of each minute, it's safe when many requests arrive at the same moment, and the storage is pluggable: in memory for a single server, or Redis when several servers share the limits. You'll learn how Rack middleware works, rate limiting algorithms, thread safety in Ruby, designing a configurable library, and testing with Rack::Test.

Estimate
~10h
Steps
6
Completed by
0
Proposed by
codeseed.app

Rack

Project roadmap

  1. 01

    Write the middleware skeleton

    ~1h

    Create a gem skeleton with bundle gem rack_throttle. Write a middleware class with initialize(app, options) and call(env) that simply calls the next app and returns its response. Mount it in a tiny config.ru app and run it with rackup. Done when: requests pass through the middleware and reach the app.

  2. 02

    Implement the sliding window

    ~2h

    Implement a sliding window counter: keep counts for the current and previous fixed windows per client, and estimate the count as current + previous × (the share of the previous window still inside the sliding window). Store counts in an in-memory store protected by a Mutex, and remove old entries so memory doesn't grow forever. Use an injectable clock for testing. Done when: unit tests show the estimate behaves correctly across window boundaries.

  3. 03

    Return proper 429 responses

    ~1h

    When a client is over the limit, return 429 with a Retry-After header in seconds, and a short JSON or text body. On every response, add RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers. Done when: going over the limit returns 429 with correct headers, and normal responses carry the remaining count.

  4. 04

    Make it configurable

    ~2h

    Support options: limit and period, a block that identifies the client (default: the IP from Rack::Request#ip, which respects trusted proxies), rules per path or method (for example stricter limits on /login), a safelist block, and a custom response. Validate options when the middleware is created. Done when: different paths get different limits, and safelisted clients are never limited.

  5. 05

    Add a Redis store

    ~2h

    Define a small store interface (increment and get counts with expiry) and add a Redis store that updates counts atomically with INCR and EXPIRE inside a MULTI block (or a Lua script), so several app servers share the same limits. When Redis is unavailable, allow requests and log a warning (make this configurable). Done when: two app processes using the Redis store share one limit.

  6. 06

    Test it

    ~2h

    Write tests with Rack::Test and Minitest or RSpec against a sample app: under and over the limit, headers, the window sliding over time (with the fake clock), per-path rules, safelists, the client identifier, and concurrent requests from many threads never exceeding the limit with the memory store. Mount it in a small Sinatra app as an example in README.md. Done when: all tests pass.

Ready to build this?

Get a GitHub repo and start building. Your AI reviewer checks each step as you go.

~10h · 6 steps

Tech stack

Rack