Raypx

限流

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 })

仅对写入请求限流

限流中间件默认跳过 GETHEAD 请求,只对写入请求(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 的分布式限流方案,支持多实例部署场景。

On this page