限流類(lèi)?

限流類(lèi)(Throttler)提供了一種非常簡(jiǎn)單的方法,可以將用戶(hù)要執(zhí)行的活動(dòng)限制為在設(shè)定的時(shí)間段內(nèi)只能進(jìn)行一定次數(shù)的嘗試。 這最常用于對(duì) API 進(jìn)行速率限制,或限制用戶(hù)針對(duì)表單進(jìn)行的嘗試次數(shù),以幫助防止暴力攻擊。 該類(lèi)可用于你根據(jù)設(shè)置的時(shí)間來(lái)進(jìn)行限制的操作。

總覽?

Throttler 實(shí)現(xiàn)了 Token Bucket (令牌桶) 算法的一個(gè)簡(jiǎn)化版本。一般,會(huì)將你要執(zhí)行的每個(gè)操作都視為一個(gè)存儲(chǔ)桶。調(diào)用該 check() 方法時(shí),你要告訴它存儲(chǔ)桶的大小, 可以容納多少令牌以及時(shí)間間隔。在默認(rèn)情況下,每個(gè) check() 的調(diào)用請(qǐng)求將會(huì)使用1個(gè)可用令牌。讓我們通過(guò)一個(gè)例子來(lái)闡明這一點(diǎn)。 (譯注:國(guó)內(nèi)用戶(hù)可參考 令牌桶

假設(shè)我們希望某動(dòng)作每秒發(fā)生一次。對(duì) Throttler 的第一次呼叫將如下所示。第一個(gè)參數(shù)是存儲(chǔ)桶名稱(chēng),第二個(gè)參數(shù)是存儲(chǔ)桶持有的令牌數(shù)量, 第三個(gè)參數(shù)是存儲(chǔ)桶重新填充所需的時(shí)間:

$throttler = \Config\Services::throttler();
$throttler->check($name, 60, MINUTE);

我們暫時(shí)使用 全局常量 </general/common_functions> 的其中一個(gè),以使其更具可讀性。也就是說(shuō),這個(gè)存儲(chǔ)桶每分鐘允許執(zhí)行60次操作, 或者每秒允許執(zhí)行1次操作。

假設(shè)某個(gè)第三方腳本試圖重復(fù)訪問(wèn) URL 。最初,它能夠在不到一秒鐘的時(shí)間內(nèi)使用完所有60個(gè)令牌。但是,在那之后, Throttler 將僅允許每秒執(zhí)行一次操作,從而有可能減慢請(qǐng)求的速度,以至于讓攻擊不再有價(jià)值。

注解

為了使 Throttler 類(lèi)可以正常工作,必須將 Cache 庫(kù)設(shè)置為實(shí)際可用的緩存對(duì)象處理程序。為了獲得最佳性能, 建議使用像 Redis 或 Memcached 那樣的內(nèi)存緩存。

速率限制?

Throttler 類(lèi)不會(huì)自發(fā)地做任何的請(qǐng)求速率限制或?qū)φ?qǐng)求進(jìn)行限流,但卻是上述功能得以實(shí)現(xiàn)的關(guān)鍵。這里提供了一個(gè)示例 過(guò)濾器 , 該過(guò)濾器以每個(gè)IP地址每秒一個(gè)請(qǐng)求的速率限制實(shí)現(xiàn)了非常簡(jiǎn)單的速率限制。我們將介紹它的工作原理,以及如何設(shè)置它并開(kāi)始在應(yīng)用程序中使用它。

實(shí)現(xiàn)代碼?

你可以在 app/Filters/Throttle.php 上創(chuàng)建自己的Throttler過(guò)濾器,大致如下:

<?php namespace App\Filters;

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

class Throttle implements FilterInterface
{
        /**
         * 這是一個(gè)為應(yīng)用程序使用 Trottler 類(lèi)來(lái)實(shí)現(xiàn)速率限制的實(shí)例
         *
         * @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
         *
         * @return mixed
         */
        public function before(RequestInterface $request)
        {
            $throttler = Services::throttler();

                    // 在整個(gè)站點(diǎn)上將IP地址限制為每秒不超過(guò)1個(gè)請(qǐng)求
                    if ($throttler->check($request->getIPAddress(), 60, MINUTE) === false)
            {
                return Services::response()->setStatusCode(429);
                    }
        }

        //--------------------------------------------------------------------

        /**
         * 暫時(shí)無(wú)事可做
         *
         * @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
         * @param ResponseInterface|\CodeIgniter\HTTP\Response       $response
         *
         * @return mixed
         */
        public function after(RequestInterface $request, ResponseInterface $response)
        {
        }
}

運(yùn)行時(shí),此方法首先獲取節(jié)流閥的實(shí)例。接下來(lái),它將IP地址用作存儲(chǔ)桶名稱(chēng),并進(jìn)行設(shè)置以將其限制為每秒一個(gè)請(qǐng)求。 如果節(jié)流閥拒絕檢查,返回false,則我們返回一個(gè)狀態(tài)碼為429(太多嘗試的 HTTP Response)的響應(yīng), 并且腳本執(zhí)行在調(diào)用控制器之前就結(jié)束了。本示例將基于對(duì)站點(diǎn)的所有請(qǐng)求(而不是每頁(yè))中的單個(gè)IP地址進(jìn)行限制。

應(yīng)用過(guò)濾器?

我們不一定需要限制網(wǎng)站上的每個(gè)頁(yè)面。對(duì)于許多Web應(yīng)用程序,最有意義的是僅將其應(yīng)用于POST請(qǐng)求,盡管API可能希望限制用戶(hù)發(fā)出的每個(gè)請(qǐng)求。 為了將此應(yīng)用到傳入請(qǐng)求,你需要編輯 /app/Config/Filters.php 并首先向過(guò)濾器添加別名:

public $aliases = [
        ...
        'throttle' => \App\Filters\Throttle::class
];

接下來(lái),我們將其分配給網(wǎng)站上的所有POST請(qǐng)求:

public $methods = [
    'post' => ['throttle', 'CSRF']
];

這就是全部。現(xiàn)在,會(huì)對(duì)網(wǎng)站上發(fā)出的所有POST請(qǐng)求進(jìn)行速率限制。

類(lèi)參考?

check(string $key, int $capacity, int $seconds[, int $cost = 1])?
參數(shù):
  • $key (string) – 儲(chǔ)存桶的名稱(chēng)
  • $capacity (int) – 儲(chǔ)存桶中持有的令牌數(shù)量
  • $seconds (int) – 儲(chǔ)存桶完全填滿(mǎn)的秒數(shù)
  • $cost (int) – 此操作將會(huì)花費(fèi)的令牌數(shù)量
返回:

如果可以執(zhí)行此操作則為 TRUE,否則為 FALSE

返回類(lèi)型:

bool

檢查存儲(chǔ)桶中是否還有令牌,或者是否在分配的時(shí)間限制內(nèi)使用了太多令牌。在每次檢查期間,如果成功,將根據(jù) $cost 參數(shù)來(lái)減少可用令牌的數(shù)量 。

getTokentime()?
返回:直到下一次令牌可用的秒數(shù)
返回類(lèi)型:int

check() 運(yùn)行并返回 FALSE 之后,可以使用此方法確定直到新令牌可用并可以再次嘗試操作之前的時(shí)間。 在這種情況下,最小強(qiáng)制等待時(shí)間為一秒。