{"id":23508660,"url":"https://github.com/fitomad/hmcheckpoint","last_synced_at":"2025-09-12T02:43:43.061Z","repository":{"id":269029709,"uuid":"901426500","full_name":"fitomad/HMCheckpoint","owner":"fitomad","description":"A Rate-Limit middleware for Hummingbird 2 ","archived":false,"fork":false,"pushed_at":"2025-01-10T15:21:21.000Z","size":39,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-13T15:57:00.079Z","etag":null,"topics":["hummingbird","swift","swift-server"],"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-12-10T16:29:33.000Z","updated_at":"2025-03-29T06:03:43.000Z","dependencies_parsed_at":null,"dependency_job_id":"3ba4d665-bfe7-4fdf-9f6b-ae4725bd9901","html_url":"https://github.com/fitomad/HMCheckpoint","commit_stats":null,"previous_names":["fitomad/hmcheckpoint"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/fitomad/HMCheckpoint","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitomad%2FHMCheckpoint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitomad%2FHMCheckpoint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitomad%2FHMCheckpoint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitomad%2FHMCheckpoint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fitomad","download_url":"https://codeload.github.com/fitomad/HMCheckpoint/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fitomad%2FHMCheckpoint/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274744062,"owners_count":25341136,"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","status":"online","status_checked_at":"2025-09-12T02:00:09.324Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["hummingbird","swift","swift-server"],"created_at":"2024-12-25T11:30:24.748Z","updated_at":"2025-09-12T02:43:43.032Z","avatar_url":"https://github.com/fitomad.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Checkpoint 🦜\n\nA Rate-Limit middleware implementation for Hummingbird servers using Redis database.\n\n```swift\n...\n\t\t\t\t\t\t\t\t\t\t\t\nguard let redisConfiguration = try RedisConfiguration(hostname: \"localhost\", port: 6379) else {\n\t...\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\tlet redis = RedisConnectionPoolService(\n\t\tredisConfiguration,\n\t\tlogger: Logger(label: \"Redis.RateLimit.Checkpoint\")\n\t)\n\t\n\treturn RedisPersistDriver(redisConnectionPoolService: redis)\n} logging: {\n\tLogger(label: \"RateLimit.Checkpoint\")\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.\"\n\t]\n\t\n\tmetadata.reason = \"Rate limit for your api key exceeded\"\n}\n\n// 🦜 Hummingbird 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\nguard let redisConfiguration = try RedisConfiguration(hostname: \"localhost\", port: 6379) else {\n\t...\n}\n\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\tlet redis = RedisConnectionPoolService(\n\t\tredisConfiguration,\n\t\tlogger: Logger(label: \"Redis.RateLimit.Checkpoint\")\n\t)\n\t\n\treturn RedisPersistDriver(redisConnectionPoolService: redis)\n} logging: {\n\tLogger(label: \"RateLimit.Checkpoint\")\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\nguard let redisConfiguration = try RedisConfiguration(hostname: \"localhost\", port: 6379) else {\n\t...\n}\n\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\tlet redis = RedisConnectionPoolService(\n\t\tredisConfiguration,\n\t\tlogger: Logger(label: \"Redis.RateLimit.Checkpoint\")\n\t)\n\t\n\treturn RedisPersistDriver(redisConnectionPoolService: redis)\n} logging: {\n\tLogger(label: \"RateLimit.Checkpoint\")\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\nguard let redisConfiguration = try RedisConfiguration(hostname: \"localhost\", port: 6379) else {\n\t...\n}\n\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\tlet redis = RedisConnectionPoolService(\n\t\tredisConfiguration,\n\t\tlogger: Logger(label: \"Redis.RateLimit.Checkpoint\")\n\t)\n\t\n\treturn RedisPersistDriver(redisConnectionPoolService: redis)\n} logging: {\n\tLogger(label: \"RateLimit.Checkpoint\")\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### 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\nguard let redisConfiguration = try RedisConfiguration(hostname: \"localhost\", port: 6379) else {\n\t...\n}\n\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\tlet redis = RedisConnectionPoolService(\n\t\tredisConfiguration,\n\t\tlogger: Logger(label: \"Redis.RateLimit.Checkpoint\")\n\t)\n\t\n\treturn RedisPersistDriver(redisConnectionPoolService: redis)\n} logging: {\n\tLogger(label: \"RateLimit.Checkpoint\")\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 Hummingbird server.\n\n```swift\npublic var willCheck: CheckpointAction?\n```\n\n### Rate-Limit reached\n\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://docs.hummingbird.codes/2.0/documentation/hummingbirdcore/request) object type representing the user request that reaches the limit.\n- `response`. It's the server response ([`Response`](https://docs.hummingbird.codes/2.0/documentation/hummingbirdcore/response) type) returned by Hummingbird.\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.\"\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.\"\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## History\n\n### 0.3.0\n\n- Adopt the [Persistent Data](https://docs.hummingbird.codes/2.0/documentation/hummingbird/persistentdata) framework as storage system for the different rate-limit algorhitms. \n- `Checkpoint` can work with `RequestContext` context, so you can apply rate-limit for the current available Hummingbird context types or with your custom types.\n- Tests now adopts the `MemoryPersistDriver` storage driver instead of Redis driver. No need to run a Docker container with a Redis image to run tests.\n\n### 0.2.0\n\n- Removing `Combine` framework. Now Checkpoint is Linux ready.\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 Swift Logging package `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%2Fhmcheckpoint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffitomad%2Fhmcheckpoint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffitomad%2Fhmcheckpoint/lists"}