swoole如何對ip限制訪問頻率

  • 2019 年 12 月 15 日
  • 筆記

swoole如何對ip限制訪問頻率

在我們開發api的過程中,有的時候我們還需要考慮單個用戶(ip)訪問頻率控制,避免被惡意調用。

歸根到底也就只有兩個步驟:

  • 用戶訪問要統計次數
  • 執行操作邏輯之前要判斷次數頻率是否過高,過高則不執行

easyswoole中實現Ip訪問頻率限制

本文章舉例的是在easyswoole框架中實現的程式碼,在swoole原生中實現方式是一樣的。

只要在對應的回調事件做判斷攔截處理即可。

  • 使用swooleTable,儲存用戶訪問情況(也可以使用其他組件、方式儲存)
  • 使用定時器,將前一周期的訪問情況清空,統計下一周期

如以下IpList類,實現了初始化Table、統計IP訪問次數、獲取一個周期內次數超過一定值的記錄

<?php  /**   * Ip訪問次數統計   * User: Siam   * Date: 2019/7/8 0008   * Time: 下午 9:53   */    namespace App;      use EasySwooleComponentSingleton;  use EasySwooleComponentTableManager;  use SwooleTable;    class IpList  {      use Singleton;        /** @var Table */      protected $table;        public  function __construct()      {          TableManager::getInstance()->add('ipList', [              'ip' => [                  'type' => Table::TYPE_STRING,                  'size' => 16              ],              'count' => [                  'type' => Table::TYPE_INT,                  'size' => 8              ],              'lastAccessTime' => [                  'type' => Table::TYPE_INT,                  'size' => 8              ]          ], 1024*128);          $this->table = TableManager::getInstance()->get('ipList');      }        function access(string $ip):int      {          $key  = substr(md5($ip), 8,16);          $info = $this->table->get($key);            if ($info) {              $this->table->set($key, [                  'lastAccessTime' => time(),                  'count'          => $info['count'] + 1,              ]);              return $info['count'] + 1;          }else{              $this->table->set($key, [                  'ip'             => $ip,                  'lastAccessTime' => time(),                  'count'          => $info['count'] + 1,              ]);              return 1;          }      }        function clear()      {          foreach ($this->table as $key => $item){              $this->table->del($key);          }      }        function accessList($count = 10):array      {          $ret = [];          foreach ($this->table as $key => $item){              if ($item['count'] >= $count){                  $ret[] = $item;              }          }          return $ret;      }    }

封裝完IP統計的操作之後

我們可以在EasySwooleEvent.php的mainServerCreate回調事件中初始化IpList和定時器

<?php    public static function mainServerCreate(EventRegister $register)  {      // 開啟IP限流      IpList::getInstance();      $class = new class('IpAccessCount') extends AbstractProcess{          protected function run($arg)          {              $this->addTick(5*1000, function (){                  /**                   * 正常用戶不會有一秒超過6次的api請求                   * 做列表記錄並清空                   */                  $list = IpList::getInstance()->accessList(30);                  // var_dump($list);                  IpList::getInstance()->clear();              });          }      };  }

接著我們在OnRequest回調中,判斷和統計Ip的訪問

<?php    public static function onRequest(Request $request, Response $response): bool  {      $fd = $request->getSwooleRequest()->fd;      $ip = ServerManager::getInstance()->getSwooleServer()->getClientInfo($fd)['remote_ip'];        // 如果當前周期的訪問頻率已經超過設置的值,則攔截      // 測試的時候可以將30改小,比如3      if (IpList::getInstance()->access($ip) > 30) {          /**           * 直接強制關閉連接           */          ServerManager::getInstance()->getSwooleServer()->close($fd);          // 調試輸出 可以做邏輯處理          echo '被攔截'.PHP_EOL;          return false;      }      // 調試輸出 可以做邏輯處理      echo '正常訪問'.PHP_EOL;  }

以上就實現了對同一IP訪問頻率的限制操作。

具體還可以根據自身需求進行擴展,如對具體的某個介面再進行限流。