限流類(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í)間為一秒。