出来るだけ高速&手軽にIPアクセス制限するPHPスニペット

いま作っているWEBサービス(数日後にリリースします!iPhoneのスクリーンショットにフレームを合成するサービスです)で、負荷対策で1人1日500アクセスとか制限をすることにした。普通に使って制限に引っかかる事はなく業者やイタズラ防止の為だ。

可能な限り高速で、DBは使わず、手軽に流用できるような感じで。ファイル読み込み→IP検索→書き出しの流れは絶対必要なので、そこで如何に負荷を減らすか。ポイントはIP変換と検索法です。

スポンサーリンク

全体

define ( 'IPLIST' , 'ip.txt' );
define ( 'ACCESSLIMIT' , 10 );
checkIP();

function checkIP(){
    $now = time();
    if ( @filemtime(IPLIST) < mktime(0, 0, 0, date('n',$now), date('j',$now), date('Y',$now)) ){
        @unlink(IPLIST);
    } else {
        foreach( file(IPLIST, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $val ){
            list( $ip , $num ) = explode(',',$val);
            $ips[$ip] = $num;
        }
    }

    $ip10 = ip2long($_SERVER['REMOTE_ADDR']);
    $ips[$ip10] += 1;

    foreach( $ips as $key => $val ){
        $out .= "{$key},{$val}\n";
    }
    file_put_contents(IPLIST,$out,LOCK_EX);
    
    if ( $ips[$ip10] > ACCESSLIMIT ){
        exit;
    }
}

1日分のIPリスト

$now = time();
if ( @filemtime(IPLIST) < mktime(0, 0, 0, date('n',$now), date('j',$now), date('Y',$now)) ){
    @unlink(IPLIST);
} else {
    foreach( file(IPLIST, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $val ){
        list( $ip , $num ) = explode(',',$val);
        $ips[$ip] = $num;
    }
}
[/code]
<p>ここで1日分のIPが記録されたファイルの削除と、ログをファイルからメモリに読み込んでる。ファイル更新日時が本日0時0分0秒より前、おそらく昨日だったらファイルを消去してログをクリア。</p>
<p>この mktime なんですが、$nowなんて定義しないで mktime(0, 0, 0, date('n'), date('j'), date('Y')) でいーのでは?と思うんだけど、 処理がdate('n')、date('j')、date('Y')と移っていく間に生じる僅かな時間のズレを回避してます。</p>
<p><a href="http://blog.poyo.jp/archives.php/categ-1/year-2006/month-7/id-1151979701" target="_blank">間違った日付の取得方法 - よくきたblog</a></p>
<h2>IP変換と検索</h2>
[code]
$ip10 = ip2long($_SERVER['REMOTE_ADDR']);
$ips[$ip10] += 1;

$_SERVER['REMOTE_ADDR']に入っているアクセス元のIPアドレスは、192.168.0.1という形式ですが、ip2long で数値にして負荷を軽くします。

次に配列に既にIPがあるかを検索しますが、最初に思いつくのは array_search ですが、ここでは数値化したIPアドレスをキーに直接インクリメントします。

PHP内部で連想配列のキーを検索してくれる訳ですが、array_searchよりも高速なハッシュ法を用いた検索が行われるので高速です。

ウノウラボ by Zynga Japan: PHPのちょっとしたコツ

IPリスト保存

foreach( $ips as $key => $val ){
    $out .= "{$key},{$val}\n";
}
file_put_contents(IPLIST,$out,LOCK_EX);

最後にメモリに展開したIPリストをファイルに書き出し。排他ロックのLOCK_EXオプションはPHP5.2.5以前は動かないので気をつけましょう。

php-5.2.5以前のfile_put_contentsではLOCK_EXによる排他ロックは動かない(5.2.6でFix) - ぼっち勉強会

 

 

スポンサーリンク