一个使用 Redis 数据库为 Vapor 服务器实现速率限制的中间件。
...
let tokenBucket = TokenBucket {
TokenBucketConfiguration(bucketSize: 25,
refillRate: 5,
refillTimeInterval: .seconds(count: 45),
appliedField: .header(key: "X-ApiKey"),
scope: .endpoint)
} storage: {
application.redis("rate")
} logging: {
application.logger
}
let checkpoint = Checkpoint(using: tokenBucket)
// 🚨 Modify response HTTP header and body response when rate limit exceed
checkpoint.didFailWithTooManyRequest = { (request, response, metadata) in
metadata.headers = [
"X-RateLimit" : "Failure for request \(request.id)."
]
metadata.reason = "Rate limit for your api key exceeded"
}
// 💧 Vapor Middleware
app.middleware.use(checkpoint)
目前 Checkpoint 支持 4 种速率限制算法。
令牌桶速率限制算法是一种被广泛使用的灵活方法,它控制对服务的请求速率,同时允许一些流量突发。 以下是对其工作原理的解释
令牌桶的配置使用 TokenBucketConfiguration
类型设置
let tokenbucketAlgorithm = TokenBucket {
TokenBucketConfiguration(bucketSize: 10,
refillRate: 0,
refillTimeInterval: .seconds(count: 20),
appliedTo: .header(key: "X-ApiKey"),
inside: .endpoint)
} storage: {
// Rate limit database in Redis
app.redis("rate").configuration = try? RedisConfiguration(hostname: "localhost",
port: 9090,
database: 0)
return app.redis("rate")
} logging: {
app.logger
}
令牌桶算法如何工作
漏桶速率限制算法是速率限制的有效方法,可确保平稳、稳定的请求流。 它的工作原理类似于底部有一个孔的物理桶,水(请求)以恒定速率滴出。 以下是对其工作原理的详细说明
漏桶的配置是 LeakingBucketConfiguration
对象
let leakingBucketAlgorithm = LeakingBucket {
LeakingBucketConfiguration(bucketSize: 10,
removingRate: 5,
removingTimeInterval: .minutes(count: 1),
appliedTo: .header(key: "X-ApiKey"),
inside :.endpoint)
} storage: {
// Rate limit database in Redis
app.redis("rate").configuration = try? RedisConfiguration(hostname: "localhost",
port: 9090,
database: 0)
return app.redis("rate")
} logging: {
app.logger
}
漏桶算法如何工作
固定窗口计数器速率限制算法是一种简单易于实现的速率限制方法,用于控制客户端在指定时间段内可以向服务发出的请求数量。 以下是对其工作原理的解释
要设置配置,您必须使用 FixedWindowCounterConfiguration
类型
let fixedWindowAlgorithm = FixedWindowCounter {
FixedWindowCounterConfiguration(requestPerWindow: 10,
timeWindowDuration: .minutes(count: 2),
appliedTo: .header(key: "X-ApiKey"),
inside: .endpoint)
} storage: {
// Rate limit database in Redis
app.redis("rate").configuration = try? RedisConfiguration(hostname: "localhost",
port: 9090,
database: 0)
return app.redis("rate")
} logging: {
app.logger
}
固定窗口计数器算法如何工作
定义时间窗口:选择一个固定的持续时间(例如,1 分钟、1 小时),它将作为计算请求的时间窗口。
初始化计数器:为每个客户端(或每个被访问的资源)维护一个计数器,以跟踪当前时间窗口内发出的请求数。
跟踪请求时间戳:每次发出请求时,检查当前时间戳并确定它属于哪个时间窗口。 增加计数器
与固定窗口计数器相比,滑动窗口日志速率限制算法是一种更精细的速率限制方法。 它通过维护单个请求时间戳的日志来提供对请求速率的更平滑控制,从而允许更精细和准确的速率限制机制。 以下是对其工作原理的详细说明
要为此速率限制算法设置配置,请使用 `` 类型
let slidingWindowLogAlgorith = SlidingWindowLog {
SlidingWindowLogConfiguration(requestPerWindow: 10,
windowDuration: .minutes(count: 2),
appliedTo: .header(key: "X-ApiKey"),
inside: .endpoint)
} storage: {
// Rate limit database in Redis
app.redis("rate").configuration = try? RedisConfiguration(hostname: "localhost",
port: 9090,
database: 0)
return app.redis("rate")
} logging: {
app.logger
}
滑动窗口日志算法如何工作
定义时间窗口:选择一个时间窗口持续时间(例如,1 分钟),您希望在该时间内限制请求数量。
记录请求:为每个客户端维护一个日志(通常是列表或队列),用于存储每个请求的时间戳。
处理传入请求:当新请求到达时,执行以下操作
有时我们需要通过添加自定义 HTTP 标头或在 JSON payload 中设置失败原因文本来修改发送给客户端的响应。
在这种情况下,您可以使用 Checkpoint
类中定义的闭包之一,每个速率限制处理阶段一个。
此闭包在 Checkpoint 中间件对给定请求执行检查操作之前被调用,并接收一个 Request 对象作为参数。
public var willCheck: CheckpointAction?
如果速率限制检查进行顺利,则调用此闭包,并且您知道该 Request 将继续由 Vapor 服务器处理。
public var willCheck: CheckpointAction?
您肯定想知道何时请求达到您在初始化 Checkpoint 时设置的速率限制。
在这种情况下,Checkpoint 将使用 didFailWithTooManyRequest 闭包通知达到速率限制。
public var didFailWithTooManyRequest: CheckpointErrorAction?
此闭包包含 3 个参数
requests
。 这是一个 Request
对象类型,表示达到限制的用户请求。response
。 这是 Vapor 返回的服务器响应 (Response
类型)。metadata
。 它是一个旨在设置自定义 HTTP 标头和一个原因文本的对象,该文本将附加到响应返回的对象 payload 中。例如,如果您想添加一个自定义 HTTP 标头和一个原因文本来通知用户他达到了限制,您将执行类似以下的操作
// 👮♀️ Modify response HTTP header and body response when rate limit exceed
checkpoint.didFailWithTooManyRequest = { (request, response, metadata) in
metadata.headers = [
"X-RateLimit" : "Failure for request \(request.id)."
]
metadata.reason = "Rate limit for your api key exceeded"
}
如果来自 Checkpoint 的错误不同于 HTTP 429 代码(速率限制),您将在以下闭包中获得报告
// 🚨 Modify response HTTP header and body response when error occurs
checkpoint.didFail = { (request, response, abort, metadata) in
metadata.headers = [
"X-ApiError" : "Error for request \(request.id)."
]
metadata.reason = "Error code \(abort.status) for your api key exceeded"
}
此闭包中使用的参数与闭包中接收的参数相同,您可以添加自定义 HTTP 标头和/或原因消息。
要使用 Checkpoint,您必须在系统中安装和配置 Redis 数据库。 借助 Docker,部署 Redis 安装非常容易。
我们建议从 Docker Hub 安装 redis-stack-server 镜像。
Alpha 版本,一个亲友版本😜
Logger
类型的日志记录系统