ตัวจำกัดอัตรา (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.ini

    apc.enabled=1
    apc.enable_cli=1

    หากไม่แน่ใจว่า php.ini อยู่ที่ไหน สามารถหาได้จากคำสั่ง php --ini

  • คำอธิบาย
    ประสิทธิภาพต่ำกว่า memory เล็กน้อย สนับสนุนการแบ่งปันข้อมูลการจำกัดอัตราระหว่างกระบวนการ

  • ข้อจำกัดการใช้งาน
    ไม่รองรับการจำกัดอัตราในคลัสเตอร์

  • สถานการณ์ที่เหมาะสม
    สภาพแวดล้อมการพัฒนาทุกประเภท; สถานการณ์การจำกัดอัตราในเครื่องเซิร์ฟเวอร์เดียว; สถานการณ์ในคลัสเตอร์ที่ไม่ต้องการการจำกัดอัตราที่เข้มงวด; ป้องกันการโจมตีแบบ CC

redis

  • ข้อกำหนด
    ต้องติดตั้งส่วนขยาย redis และติดตั้ง Redis component โดยใช้คำสั่งติดตั้ง

    composer require -W illuminate/redis illuminate/events
  • คำอธิบาย
    ประสิทธิภาพต่ำกว่า apcu รองรับการจำกัดอัตราที่แม่นยำทั้งในแบบเครื่องเซิร์ฟเวอร์เดียวและแบบคลัสเตอร์

  • สถานการณ์ที่เหมาะสม
    สภาพแวดล้อมการพัฒนา; สภาพแวดล้อมการใช้เครื่องเซิร์ฟเวอร์เดียวในสภาพแวดล้อมออนไลน์; สภาพแวดล้อมคลัสเตอร์