{"id":21647416,"url":"https://github.com/fitomad/checkpoint","last_synced_at":"2025-05-07T19:26:21.496Z","repository":{"id":245589646,"uuid":"818569596","full_name":"fitomad/Checkpoint","owner":"fitomad","description":"A Rate-Limit middleware for Vapor server","archived":false,"fork":false,"pushed_at":"2024-06-25T20:30:08.000Z","size":45,"stargazers_count":12,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-06T02:48:47.412Z","etag":null,"topics":["rate-limit-redis","rate-limiting","swift","swift-server","vapor-4","vapor-swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fitomad.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-22T07:47:19.000Z","updated_at":"2025-04-11T23:27:02.000Z","dependencies_parsed_at":null,"dependency_job_id":"57cb5a7e-cd37-4a3e-af48-426c27ad333f","html_url":"https://github.com/fitomad/Checkpoint","commit_stats":null,"previous_names":["fitomad/checkpoint"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitomad%2FCheckpoint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitomad%2FCheckpoint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitomad%2FCheckpoint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitomad%2FCheckpoint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fitomad","download_url":"https://codeload.github.com/fitomad/Checkpoint/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252942633,"owners_count":21829109,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rate-limit-redis","rate-limiting","swift","swift-server","vapor-4","vapor-swift"],"created_at":"2024-11-25T06:49:52.383Z","updated_at":"2025-05-07T19:26:21.471Z","avatar_url":"https://github.com/fitomad.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Checkpoint 💧\n\nA Rate-Limit middleware implementation for Vapor servers using Redis database.\n\n```swift\n...\n\nlet tokenBucket = TokenBucket {\n\tTokenBucketConfiguration(bucketSize: 25,\n\t\t\t\t\t\t\t refillRate: 5,\n\t\t\t\t\t\t\t refillTimeInterval: .seconds(count: 45),\n\t\t\t\t\t\t\t appliedField: .header(key: \"X-ApiKey\"),\n\t\t\t\t\t\t\t scope: .endpoint)\n} storage: {\n\tapplication.redis(\"rate\")\n} logging: {\n\tapplication.logger\n}\n\n\nlet checkpoint = Checkpoint(using: tokenBucket)\n\n// 🚨 Modify response HTTP header and body response when rate limit exceed\ncheckpoint.didFailWithTooManyRequest = { (request, response, metadata) in\n\tmetadata.headers = [\n\t\t\"X-RateLimit\" : \"Failure for request \\(request.id).\"\n\t]\n\t\n\tmetadata.reason = \"Rate limit for your api key exceeded\"\n}\n\n// 💧 Vapor Middleware\napp.middleware.use(checkpoint)\n```\n\n## Supported algorythims\n\nCurrently **Checkpoint** supports 4 rate-limit algorithims.\n\n### Token Bucket\n\nThe Token Bucket rate-limiting algorithm is a widely-used and flexible approach that controls the rate of requests to a service while allowing for some bursts of traffic. Here’s an explanation of how it works:\n\nThe configuration for the Token Bucket is setted using the `TokenBucketConfiguration` type\n\n```swift\nlet tokenbucketAlgorithm = TokenBucket {\n\tTokenBucketConfiguration(bucketSize: 10,\n\t\t\t\t\t\t\t refillRate: 0,\n\t\t\t\t\t\t\t refillTimeInterval: .seconds(count: 20),\n\t\t\t\t\t\t\t appliedTo: .header(key: \"X-ApiKey\"),\n\t\t\t\t\t\t\t inside: .endpoint)\n} storage: {\n\t// Rate limit database in Redis\n\tapp.redis(\"rate\").configuration = try? RedisConfiguration(hostname: \"localhost\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t port: 9090,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t database: 0)\n\t\n\treturn app.redis(\"rate\")\n} logging: {\n\tapp.logger\n}\n```\n\nHow the Token Bucket Algorithm Works:\n\n1. Initialize the Bucket:\n\n- The bucket has a fixed capacity, which represents the maximum number of tokens it can hold.\n- Tokens are added to the bucket at a fixed rate, up to the bucket's capacity.\n\n2. Handle Incoming Requests:\n\n- When a new request arrives, check if there are enough tokens in the bucket.\n- If there is at least one token, allow the request and remove a token from the bucket.\n- If there are no tokens available, deny the request (rate limit exceeded).\n\n3. Add Tokens:\n\n- Tokens are added to the bucket at a steady rate, which determines the average rate of allowed requests.\n- The bucket never holds more than its fixed capacity of tokens.\n\n### Leaking Bucket\n\nThe Leaking Bucket rate-limit algorithm is an effective approach to rate limiting that ensures a smooth, steady flow of requests. It works similarly to a physical bucket with a hole in it, where water (requests) drips out at a constant rate. Here’s a detailed explanation of how it works:\n\nThe configuration for Leaking Bucket is the `LeakingBucketConfiguration` object\n\n```swift\nlet leakingBucketAlgorithm = LeakingBucket {\n\tLeakingBucketConfiguration(bucketSize: 10,\n\t\t\t\t\t\t\t   removingRate: 5,\n\t\t\t\t\t\t\t   removingTimeInterval: .minutes(count: 1),\n\t\t\t\t\t\t\t   appliedTo: .header(key: \"X-ApiKey\"),\n\t\t\t\t\t\t\t   inside :.endpoint)\n} storage: {\n\t// Rate limit database in Redis\n\tapp.redis(\"rate\").configuration = try? RedisConfiguration(hostname: \"localhost\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t port: 9090,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t database: 0)\n\t\n\treturn app.redis(\"rate\")\n} logging: {\n\tapp.logger\n}\n```\n\nHow the Leaking Bucket Algorithm Works:\n\n1. Initialize the Bucket:\n\n- The bucket has a fixed capacity, representing the maximum number of requests that can be stored in the bucket at any given time.\n- The bucket leaks at a fixed rate, representing the maximum rate at which requests are processed.\n\n2. Handle Incoming Requests:\n\n- When a new request arrives, check the current level of the bucket.\n- If the bucket is not full (i.e., the number of stored requests is less than the bucket's capacity), add the request to the bucket.\n- If the bucket is full, deny the request (rate limit exceeded).\n\n3. Process Requests:\n\n- Requests in the bucket are processed (leaked) at a constant rate.\n- This ensures a steady flow of requests, preventing sudden bursts.\n\n### Fixed Window Counter\n\nThe Fixed Window Counter rate-limit algorithm is a straightforward and easy-to-implement approach for rate limiting, used to control the number of requests a client can make to a service within a specified time period. Here’s an explanation of how it works:\n\nTo set the configuration you must use the `FixedWindowCounterConfiguration` type\n\n```swift\nlet fixedWindowAlgorithm = FixedWindowCounter {\n\tFixedWindowCounterConfiguration(requestPerWindow: 10,\n\t\t\t\t\t\t\t\t\ttimeWindowDuration: .minutes(count: 2),\n\t\t\t\t\t\t\t\t\tappliedTo: .header(key: \"X-ApiKey\"),\n\t\t\t\t\t\t\t\t\tinside: .endpoint)\n} storage: {\n\t// Rate limit database in Redis\n\tapp.redis(\"rate\").configuration = try? RedisConfiguration(hostname: \"localhost\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t port: 9090,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t database: 0)\n\t\n\treturn app.redis(\"rate\")\n} logging: {\n\tapp.logger\n}\n```\n\nHow the Fixed Window Counter Algorithm Works:\n\n1. Define a Time Window\nChoose a fixed duration (e.g., 1 minute, 1 hour) which will serve as the time window for counting requests.\n\n2. Initialize a Counter:\nMaintain a counter for each client (or each resource being accessed) that tracks the number of requests made within the current time window.\n\n3. Track Request Timestamps:\nEach time a request is made, check the current timestamp and determine which time window it falls into.\nIncrement the Counter:\n\n- If the request falls within the current window, increment the counter.\n- If the request falls outside the current window, reset the counter and start a new window.\n\n4. Enforce Limits:\n\n- If the counter exceeds the predefined limit within the current window, the request is denied (or throttled).\n- If the counter is within the limit, the request is allowed.\n \n \n ```swift\n \n ```\n\n### Sliding Window Log\n\nThe Sliding Window Log rate-limit algorithm is a more refined approach to rate limiting compared to the Fixed Window Counter. It offers smoother control over request rates by maintaining a log of individual request timestamps, allowing for a more granular and accurate rate-limiting mechanism. Here’s a detailed explanation of how it works:\n\nTo set the configuration for this rate-limit algorithim use the `` type\n\n```swift\nlet slidingWindowLogAlgorith = SlidingWindowLog {\n\tSlidingWindowLogConfiguration(requestPerWindow: 10,\n\t\t\t\t\t\t\t\t  windowDuration: .minutes(count: 2),\n\t\t\t\t\t\t\t\t  appliedTo: .header(key: \"X-ApiKey\"),\n\t\t\t\t\t\t\t\t  inside: .endpoint)\n} storage: {\n\t// Rate limit database in Redis\n\tapp.redis(\"rate\").configuration = try? RedisConfiguration(hostname: \"localhost\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t port: 9090,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t database: 0)\n\t\n\treturn app.redis(\"rate\")\n} logging: {\n\tapp.logger\n}\n```\n\nHow the Sliding Window Log Algorithm Works:\n\n1. Define a Time Window:\nChoose a time window duration (e.g., 1 minute) within which you want to limit the number of requests.\n\n2. Log Requests:\nMaintain a log (typically a list or queue) for each client that stores the timestamps of each request.\n\n3. Handle Incoming Requests:\nWhen a new request arrives, do the following:\n\n- Remove timestamps from the log that fall outside the current time window.\n- Check the number of timestamps remaining in the log.\n- If the number of requests (timestamps) within the window is below the limit, add the new request’s timestamp to the log and allow the request.\n- If the number of requests meets or exceeds the limit, deny the request.\n\n\n## Modify server response\n\nSometimes we need to modify the response sent to the client by adding a custom HTTP header or setting a failure reason text in the JSON payload.\n\nIn that case, you can use one of the closures defined in the `Checkpoint` class, one per Rate-Limit processing stage.\n\n### Before performing Rate-Limit checking\n\nThis closure is invoked just before the Checkpoint middleware checking operation for a given request will be performed, and receive a Request object as a parameter.\n\n```swift\npublic var willCheck: CheckpointAction?\n```\n\n### After perform Rate-Limit checking\n\nIf Rate-Limit checking goes well, this closure is invoked, and you know that the Request continues to be processed by the Vapor server.\n\n```swift\npublic var willCheck: CheckpointAction?\n```\n\n### Rate-Limit reached\nIt's sure you want to know when a request reaches the rate limit you set when initializing Checkpoint.\n\nIn this case, Checkpoint will notify a rate-limit reached using the didFailWithTooManyRequest closure.\n\n```swift\npublic var didFailWithTooManyRequest: CheckpointErrorAction?\n```\n\nThis closure contains 3 parameter \n\n- `requests`. It's a [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) object type representing the user request that reaches the limit.\n- `response`. It's the server response ([`Response`](https://api.vapor.codes/vapor/documentation/vapor/response) type) returned by Vapor.\n- `metadata`. It's an object designed to set custom HTTP headers and a reason text that will be attached to the object payload returned by the response.\n\nFor example, if you want to add a custom HTTP header and a reason text to inform a user that he reaches the limit you will do something like this\n\n```swift\n// 👮‍♀️ Modify response HTTP header and body response when rate limit exceed\ncheckpoint.didFailWithTooManyRequest = { (request, response, metadata) in\n\tmetadata.headers = [\n\t\t\"X-RateLimit\" : \"Failure for request \\(request.id).\"\n\t]\n\t\n\tmetadata.reason = \"Rate limit for your api key exceeded\"\n}\n```\n\n### Error throwed while process a request  \n\nIf an error different from an HTTP 429 code (rate-limit) comes from Checkpoint, you will be reported in the following closure\n\n```swift\n// 🚨 Modify response HTTP header and body response when error occurs\ncheckpoint.didFail = { (request, response, abort, metadata) in\n\tmetadata.headers = [\n\t\t\"X-ApiError\" : \"Error for request \\(request.id).\"\n\t]\n\t\n\tmetadata.reason = \"Error code \\(abort.status) for your api key exceeded\"\n}\n```\n\nThe parameters used in this closure are the same as the ones received in the closure, you can add a custom HTTP header and/or a reason message.\n\n## Redis\n\nTo work with Checkpoint you must install and configure a Redis database in your system. Thanks to Docker it's really easy to deploy a Redis installation. \n\nWe recommend to install the [**redis-stack-server**](https://hub.docker.com/r/redis/redis-stack-server) image from the Docker Hub.\n\n## History\n\n### 0.1.0\n\nAlpha version, a *Friends \u0026 Family* release 😜\n\n- Support for Redis Database\n- Logging system based on the Vapor `Logger` type\n- Four rate-limit algorithims support\n\t- Fixed Window Counter\n\t- Leaking Bucket\n\t- Sliding Window Log\n\t- Token Bucket\n\t\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffitomad%2Fcheckpoint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffitomad%2Fcheckpoint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffitomad%2Fcheckpoint/lists"}