限流
API 和认证端点的内存限流机制。
Raypx 内置了基于内存的限流机制,用于防止 API 和认证端点被滥用。限流中间件在 src/lib/rate-limit.ts 中实现。
实现原理
限流使用固定窗口计数器算法,基于 Map 存储每个客户端的请求计数:
interface RateLimitEntry {
count: number
resetAt: number
}
const store = new Map<string, RateLimitEntry>()每个请求根据客户端 IP 和请求路径生成一个唯一的 key,在时间窗口内对请求数量进行计数。超过限制的请求会被拒绝,返回 429 Too Many Responses 状态码。
配置参数
export interface RateLimitOptions {
/** 时间窗口内的最大请求数 */
limit: number
/** 时间窗口大小(秒) */
window: number
}当前设置
Raypx 对不同的端点使用不同的限流配置:
| 端点 | 限制 | 窗口 | 说明 |
|---|---|---|---|
/api/auth/$ | 30 次 | 60 秒 | 认证端点(登录、注册等) |
/api/rpc/$ | 100 次 | 60 秒 | RPC API 端点 |
// 认证端点
const checkRateLimit = createRateLimitMiddleware({ limit: 30, window: 60 })
// RPC 端点
const checkRateLimit = createRateLimitMiddleware({ limit: 100, window: 60 })仅对写入请求限流
限流中间件默认跳过 GET 和 HEAD 请求,只对写入请求(POST、PUT、PATCH、DELETE)进行限流:
export function createRateLimitMiddleware(options: RateLimitOptions) {
return (request: Request): RateLimitResult | null => {
// 只对变更请求限流,GET 和 HEAD 直接通过
if (request.method === "GET" || request.method === "HEAD") return null
const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "unknown"
const key = `${ip}:${new URL(request.url).pathname}`
return rateLimit(key, options)
}
}返回 null 表示不限流(即允许请求通过)。这使得读取操作不受限流影响,只保护写入操作。
键格式
限流 key 的格式为 {IP}:{路径},例如:
192.168.1.100:/api/auth/$
192.168.1.100:/api/rpc/$IP 地址从 X-Forwarded-For 请求头中提取(取第一个值),适用于反向代理(如 Nginx、Cloudflare)场景。如果无法获取 IP,则使用 "unknown" 作为回退值。
自动清理
为了避免内存泄漏,限流存储会定期清理过期的条目:
// 每 60 秒清理一次过期条目
setInterval(() => {
const now = Date.now()
for (const [key, entry] of store) {
if (entry.resetAt <= now) store.delete(key)
}
}, 60_000)限流响应
当请求超过限制时,返回以下响应:
HTTP/1.1 429 Too Many Requests
Retry-After: 45
X-RateLimit-Remaining: 0- Retry-After:告诉客户端多久后可以重试(秒)
- X-RateLimit-Remaining:剩余可用请求数
if (result && !result.success) {
return new Response("Too Many Requests", {
status: 429,
headers: {
"Retry-After": String(Math.ceil((result.resetAt - Date.now()) / 1000)),
"X-RateLimit-Remaining": "0",
},
})
}使用方式
在 TanStack Start 文件路由中使用限流中间件:
import { createRateLimitMiddleware } from "@/lib/rate-limit"
const checkRateLimit = createRateLimitMiddleware({ limit: 100, window: 60 })
export const Route = createFileRoute("/api/rpc/$")({
server: {
handlers: {
POST: ({ request }) => {
const result = checkRateLimit(request)
if (result && !result.success) {
return new Response("Too Many Requests", { status: 429 })
}
// 处理正常请求...
},
},
},
})局限性与未来规划
当前限流基于进程内 Map 实现,存在以下局限:
- 在多实例部署中,每个实例独立计数,总限制实际上是
实例数 x 限制 - 进程重启后计数器丢失
- 不支持分布式限流
未来计划迁移到基于 Redis 的分布式限流方案,支持多实例部署场景。