一种提供基于 IP 的冷却时间的便携方式?

A portable way of providing an IP-based cooldown period?

我在网络服务器上有一个 PHP API 前端 运行。这个特定的 PHP 程序需要分发,因此它应该尽可能便携。

我要实现的功能是IP冷却时间,也就是说同一个IP每秒最多只能请求API两次,也就是说至少有500ms的延迟。

我想到的方法是将 IP 连同最新的请求时间戳一起存储在 MySQL 数据库中。我通过以下方式获取 IP:

if (getenv('REMOTE_ADDR'))
    $ipaddress = getenv('REMOTE_ADDR');

但某些服务器可能没有 MySQL 数据库,或者安装此数据库的用户无权访问。另一个问题是数据库的清理。

是否有更便携的临时存储 IP 的方法(记住 IPv6)?

我如何提供对早于 500 毫秒的 IP 的自动清理,同时尽可能减少对性能的影响?

另外:我没有兴趣查看存储的 IP,这只是延迟问题。

这就是我现在使用文件解决它的方法。

程序

  1. 获取客户​​端 IP 并对其进行哈希处理(以防止文件被读出)。
  2. 打开 IP 文件并扫描每一行
  3. 比较当前记录的时间与当前时间
  4. 如果差异大于设置超时转到 5.,否则 7.
  5. 如果 IP 匹配客户端,则创建更新记录,否则
  6. 删除记录。
  7. 如果IP匹配客户端,提供失败消息,否则复制记录。

示例代码

<?php

$sIPHash    = md5($_SERVER[REMOTE_ADDR]);
$iSecDelay  = 10;
$sPath      = "bucket.cache";
$bReqAllow  = false;
$iWait      = -1;
$sContent   = "";

if ($nFileHandle = fopen($sPath, "c+")) {
    flock($nFileHandle, LOCK_EX);
    $iCurLine = 0;
    while (($sCurLine = fgets($nFileHandle, 4096)) !== FALSE) {
        $iCurLine++;
        $bIsIPRec = strpos($sCurLine, $sIPHash);
        $iLastReq = strtok($sCurLine, '|');
        // this record expired anyway:
        if ( (time() - $iLastReq) > $iSecDelay ) {
            // is it also our IP?
            if ($bIsIPRec !== FALSE) {
                $sContent .= time()."|".$sIPHash.PHP_EOL;
                $bReqAllow = true;
            }
        } else {
            if ($bIsIPRec !== FALSE) $iWait = ($iSecDelay-(time()-$iLastReq));
            $sContent .= $sCurLine.PHP_EOL;
        }
    }
}

if ($iWait == -1 && $bReqAllow == false) {
    // no record yet, create one
    $sContent .= time()."|".$sIPHash.PHP_EOL;
    echo "Request from new user successful!";
} elseif ($bReqAllow == true) {
    echo "Request from old user successful!";
} else {
    echo "Request failed! Wait " . $iWait . " seconds!";
}

ftruncate($nFileHandle, 0);
rewind($nFileHandle);
fwrite($nFileHandle, $sContent);
flock($nFileHandle, LOCK_UN);
fclose($nFileHandle);
?>

备注

新用户

如果 IP 散列与任何记录都不匹配,则会创建一条新记录。注意:如果您没有权限,访问可能会失败。

内存

如果您预计流量很大,请一起切换到 this 等数据库解决方案。

冗余代码

"But minxomat",你可能会说,"now each client loops through the whole file!"。是的,确实如此,这就是我想要的解决方案。这样,每个客户端都负责清理整个文件。即便如此,对性能的影响仍然很低,因为如果每个客户端都在清理,文件大小将保持在绝对最小值。如果这种方式不适合您,请更改它。