ตัวจำกัดอัตรา (Rate Limiter)
webman ตัวจำกัดอัตรา รองรับการจำกัดอัตราผ่านการอannotation
รองรับการใช้งานผ่าน apcu, redis, memory driver
ที่อยู่ซอร์ซโค้ด
https://github.com/webman-php/rate-limiter
การติดตั้ง
composer require webman/rate-limiter
การใช้งาน
<?php
namespace app\controller;
use RuntimeException;
use Webman\RateLimiter\Annotation\RateLimiter;
class UserController
{
#[RateLimiter(limit: 10)]
public function index(): string
{
// โดยค่าเริ่มต้นจะจำกัดอัตราตาม IP, เวลาหน่วยจะเป็น 1 วินาที
return 'แต่ละ IP สามารถส่งคำขอได้สูงสุด 10 ครั้งใน 1 วินาที';
}
#[RateLimiter(limit: 100, ttl: 60, key: RateLimiter::UID)]
public function search(): string
{
// key: RateLimiter::UID จำกัดอัตราโดยอิงจากหมายเลขผู้ใช้ โดยต้องการให้ session('user.id') ไม่เป็นค่าว่าง
return 'แต่ละผู้ใช้สามารถค้นหาได้สูงสุด 100 ครั้งใน 60 วินาที';
}
#[RateLimiter(limit: 1, ttl: 60, key: RateLimiter::SID, message: 'แต่ละคนสามารถส่งอีเมล์ได้เพียง 1 ครั้งต่อ 1 นาที')]
public function sendMail(): string
{
// key: RateLimiter::SID จำกัดอัตราโดยอิงจาก session_id
return 'ส่งอีเมล์สำเร็จ';
}
#[RateLimiter(limit: 100, ttl: 24*60*60, key: 'coupon', message: 'คูปองวันนี้หมดแล้ว กรุณากลับมาใหม่ในวันพรุ่งนี้')]
#[RateLimiter(limit: 1, ttl: 24*60*60, key: RateLimiter::UID, message: 'แต่ละผู้ใช้สามารถรับคูปองได้เพียง 1 ครั้งต่อวัน')]
public function coupon(): string
{
// key: 'coupon' คูปองที่นี่เป็นค่า key ที่กำหนดเอง ซึ่งจะจำกัดอัตราโดยใช้คูปองเป็น key ทั่วไป สูงสุด 100 คูปองต่อวัน
// ในขณะเดียวกันก็จำกัดอัตราโดยอิงจากหมายเลขผู้ใช้ โดยแต่ละคนสามารถรับคูปองได้เพียง 1 ครั้งต่อวัน
return 'ส่งคูปองสำเร็จ';
}
#[RateLimiter(limit: 5, ttl: 24*60*60, key: [UserController::class, 'getMobile'], message: 'แต่ละหมายเลขโทรศัพท์มือถือสามารถรับ SMS ได้สูงสุด 5 ข้อความต่อวัน')]
public function sendSms2(): string
{
// เมื่อ key เป็นตัวแปร สามารถใช้วิธี [คลาส, เมธอดแบบสเตติก] เพื่อเข้าถึง key เช่น [UserController::class, 'getMobile'] จะเรียกใช้คืนค่าจากรุ่น getMobile() ของ UserController เป็น key
return 'ส่ง SMS สำเร็จ';
}
/**
* กำหนดค่า key เองสำหรับการรับหมายเลขโทรศัพท์ จำเป็นต้องเป็นเมธอดแบบสเตติก
* @return string
*/
public static function getMobile(): string
{
return request()->get('mobile');
}
#[RateLimiter(limit: 1, ttl: 10, key: RateLimiter::IP, message: 'ความถี่ถูกจำกัด', exception: RuntimeException::class)]
public function testException(): string
{
// เมื่อเกินขอบเขต ข้อผิดพลาดเริ่มต้นคือ Webman\RateLimiter\RateLimitException สามารถเปลี่ยนแปลงได้ผ่านพารามิเตอร์ exception
return 'ok';
}
}
คำอธิบาย
- ค่าเริ่มต้นของช่วงเวลาหน่วยคือ 1 วินาที
- สามารถตั้งค่า ttl เพื่อตั้งช่วงเวลาหน่วย เช่น
ttl:60
จะเป็น 60 วินาที - ข้อกำหนดการจำกัดอัตราเริ่มต้นคือการจำกัดอัตราแบบ IP (ค่าเริ่มต้น
127.0.0.1
จะไม่ถูกจำกัด) - รองรับการจำกัดอัตรา IP, UID (จำเป็นต้องมี
session('user.id')
ที่ไม่ว่าง), SID (จำกัดตามsession_id
) - หากใช้ Nginx proxy ต้องส่ง
X-Forwarded-For
header ในการจำกัดอัตรา IP ดูรายละเอียดเพิ่มเติมที่ Nginx Proxy - เมื่อเกินขอบเขตจะเกิดข้อยกเว้น
Webman\RateLimiter\RateLimitException
สามารถกำหนดคลาสข้อยกเว้นผ่านexception:xx
- ข้อความข้อผิดพลาดเริ่มต้นคือ
Too Many Requests
สามารถกำหนดข้อความได้ผ่านmessage:xx
- ข้อความผิดพลาดเริ่มต้นสามารถเปลี่ยนแปลงได้ผ่าน การแปลหลายภาษา ใน Linux สามารถใช้คำสั่งด้านล่างได้
composer require symfony/translation mkdir resource/translations/zh_CN/ -p echo "<?php return [ 'Too Many Requests' => 'ความถี่ถูกจำกัด' ];" > resource/translations/zh_CN/messages.php php start.php restart
API
บางครั้งนักพัฒนาต้องการเรียกใช้ตัวจำกัดอัตราโดยตรงในโค้ด สามารถดูตัวอย่างโค้ดได้ดังนี้
<?php
namespace app\controller;
use RuntimeException;
use Webman\RateLimiter\Limiter;
class UserController {
public function sendSms(string $mobile): string
{
// ที่นี่ mobile เป็น key
Limiter::check($mobile, 5, 24*60*60, 'แต่ละหมายเลขโทรศัพท์มือถือสามารถรับ SMS ได้สูงสุด 5 ข้อความต่อวัน');
return 'ส่ง SMS สำเร็จ';
}
}
การตั้งค่า
config/plugin/webman/rate-limiter/app.php
<?php
return [
'enable' => true,
'driver' => 'auto', // auto, apcu, memory, redis
'stores' => [
'redis' => [
'connection' => 'default',
]
],
// คำขอดังกล่าวจะไม่ถูกจำกัดความถี่ (ใช้ได้เฉพาะเมื่อ key เป็น RateLimiter::IP)
'ip_whitelist' => [
'127.0.0.1',
],
];
- enable: เปิดใช้งานการจำกัดอัตราหรือไม่
- driver: ค่าหนึ่งใน
auto
,apcu
,memory
,redis
เมื่อเลือกauto
จะทำการเลือกค่าจากapcu
และmemory
โดยอัตโนมัติ - stores: การตั้งค่า
redis
,connection
จะตรงกับkey
ในconfig/redis.php
- ip_whitelist: IP ที่อยู่ในรายชื่อขาวจะไม่ถูกจำกัด (ใช้ได้เฉพาะเมื่อ key เป็น
RateLimiter::IP
)
การเลือก driver
memory
-
คำอธิบาย
ไม่ต้องติดตั้งส่วนขยายใดๆ และมีประสิทธิภาพดีที่สุด -
ข้อจำกัดการใช้งาน
การจำกัดอัตรามีผลเฉพาะในกระบวนการปัจจุบัน ข้อมูลการจำกัดอัตราจะไม่แบ่งปันระหว่างหลายกระบวนการและไม่รองรับการจำกัดอัตราในคลัสเตอร์ -
สถานการณ์ที่เหมาะสม
สภาพแวดล้อมการพัฒนาบน Windows; ธุรกิจที่ไม่ต้องการการจำกัดอัตราอย่างเข้มงวด; ป้องกันการโจมตีแบบ CC
apcu
-
การติดตั้งส่วนขยาย
ต้องติดตั้งส่วนขยาย apcu และกำหนดค่าใน php.iniapc.enabled=1 apc.enable_cli=1
หากไม่แน่ใจว่า php.ini อยู่ที่ไหน สามารถหาได้จากคำสั่ง
php --ini
-
คำอธิบาย
ประสิทธิภาพต่ำกว่า memory เล็กน้อย สนับสนุนการแบ่งปันข้อมูลการจำกัดอัตราระหว่างกระบวนการ -
ข้อจำกัดการใช้งาน
ไม่รองรับการจำกัดอัตราในคลัสเตอร์ -
สถานการณ์ที่เหมาะสม
สภาพแวดล้อมการพัฒนาทุกประเภท; สถานการณ์การจำกัดอัตราในเครื่องเซิร์ฟเวอร์เดียว; สถานการณ์ในคลัสเตอร์ที่ไม่ต้องการการจำกัดอัตราที่เข้มงวด; ป้องกันการโจมตีแบบ CC
redis
-
ข้อกำหนด
ต้องติดตั้งส่วนขยาย redis และติดตั้ง Redis component โดยใช้คำสั่งติดตั้งcomposer require -W illuminate/redis illuminate/events
-
คำอธิบาย
ประสิทธิภาพต่ำกว่า apcu รองรับการจำกัดอัตราที่แม่นยำทั้งในแบบเครื่องเซิร์ฟเวอร์เดียวและแบบคลัสเตอร์ -
สถานการณ์ที่เหมาะสม
สภาพแวดล้อมการพัฒนา; สภาพแวดล้อมการใช้เครื่องเซิร์ฟเวอร์เดียวในสภาพแวดล้อมออนไลน์; สภาพแวดล้อมคลัสเตอร์