# Throttler 类

Throttler 类提供了一种非常简单的方式，用于限制在指定时间段内执行某项操作的次数。此功能最常用于 API 速率限制，或限制表单提交次数以防范暴力破解攻击。实际上，对于任何需要根据特定时间间隔限制操作频率的场景，都可以使用该类。

## 概述

节流器实现了简化版的 令牌桶 算法。其基本原理是将每一项需要限制的操作视为一个“桶”。调用 `check()` 方法时， 需指定桶的容量（即能容纳的令牌数）以及时间间隔。默认情况下，每次调用 `check()` 会消耗 1 个可用令牌。下面通过示例进行说明。

假设我们希望某个动作每秒执行一次。首次调用节流器时，代码如下所示。 第一个参数是桶的名称，第二个参数是桶的容量（令牌数）， 第三个参数是桶重新填充所需的时间：

    <?php

    $throttler = service('throttler');
    $throttler->check($name, 60, MINUTE);

这里使用了 全局常量 之一来表示时间，使代码更具可读性。这表示桶允许每分钟 60 次动作，即每秒 1 次动作。

假设有第三方脚本试图反复访问某个 URL。最初，该脚本可以在不到一秒的时间内耗尽所有 60 个令牌。但此后节流器将只允许每秒执行一次动作， 从而将请求速度降低到攻击不再划算的程度。

备注

要使 Throttler 类正常工作，必须将缓存库配置为使用 Dummy 以外的处理程序。 为获得最佳性能，推荐使用 Redis 或 Memcached 等内存缓存。

## 速率限制

Throttler 类本身不会执行任何速率限制或请求节流，但它是实现这些功能的关键。 项目提供了一个示例 过滤器，实现了简单的速率限制： 每个 IP 地址每秒一次请求。下面将说明其工作原理，以及如何在应用中配置和使用。

### 代码

可在 app/Filters/Throttle.php 创建节流器过滤器， 代码如下：

    <?php

    namespace App\Filters;

    use CodeIgniter\Filters\FilterInterface;
    use CodeIgniter\HTTP\RequestInterface;
    use CodeIgniter\HTTP\ResponseInterface;

    class Throttle implements FilterInterface
    {
        /**
         * This is a demo implementation of using the Throttler class
         * to implement rate limiting for your application.
         *
         * @param list<string>|null $arguments
         *
         * @return ResponseInterface|void
         */
        public function before(RequestInterface $request, $arguments = null)
        {
            $throttler = service('throttler');

            // Restrict an IP address to no more than 1 request
            // per second across the entire site.
            if ($throttler->check(md5($request->getIPAddress()), 60, MINUTE) === false) {
                return service('response')->setStatusCode(429);
            }
        }

        /**
         * We don't have anything to do here.
         *
         * @param list<string>|null $arguments
         *
         * @return void
         */
        public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
        {
            // ...
        }
    }

运行此方法时，首先获取节流器实例。然后使用 IP 地址作为桶名称， 将限制设置为每秒一次请求。如果节流器拒绝检查（返回 false）， 则返回状态码为 429（Too Many Attempts）的响应，脚本执行将终止， 不会到达控制器。此示例基于单个 IP 地址对所有站点请求进行节流， 而非针对每个页面。

### 应用过滤器

并非站点的每个页面都需要节流。对于许多 Web 应用，最合理的做法是仅应用于 POST 请求， 而 API 可能需要限制用户的每个请求。要将节流应用于传入请求， 需要编辑 app/Config/Filters.php，首先为过滤器添加别名：

    <?php

    namespace Config;

    use CodeIgniter\Config\BaseConfig;

    class Filters extends BaseConfig
    {
        public $aliases = [
            // ...
            'throttle' => \App\Filters\Throttle::class,
        ];

        // ...
    }

然后将其应用到站点的所有 POST 请求：

    <?php

    namespace Config;

    use CodeIgniter\Config\BaseConfig;

    class Filters extends BaseConfig
    {
        public $methods = [
            'POST' => ['throttle'],
        ];

        // ...
    }

警告

如果使用了 `$methods` 过滤器，应 禁用自动路由（传统版） 因为 自动路由（传统版） 允许任何 HTTP 方法访问控制器。 使用非预期方法访问控制器可能会绕过过滤器。

设置完成后，站点上的所有 POST 请求都将受到速率限制。

## 类参考

check(string $key, int $capacity, int $seconds[, int $cost = 1])
  参数:

    - $key (`string`) -- 桶的名称

    - $capacity (`int`) -- 桶可容纳的令牌数量

    - $seconds (`int`) -- 桶完全重新填充所需的秒数

    - $cost (`int`) -- 此动作消耗的令牌数量

  返回:

    动作可执行时返回 true，否则返回 false

  返回类型:

    `bool`

  检查桶内是否还有可用令牌，或在指定时间限制内是否已使用过多令牌。 每次检查时，如果通过，可用令牌将减少 $cost 个。

getTokentime()
  返回:

    下一个令牌可用前应等待的秒数。

  返回类型:

    `integer`

  在 `check()` 返回 false 后，可使用此方法确定 到新令牌可用、可以再次尝试动作之前需要等待的时间。 此时，强制的最小等待时间为 1 秒。

remove(string $key) → self
  参数:

    - $key (`string`) -- 桶的名称

  返回:

    $this

  返回类型:

    `self`

  移除并重置桶。 桶不存在时也不会失败。