日本国内からのアクセスを識別するときに、IPv4の頃はリモートホスト名が/\.jp$/であるかや、nifty.comみたいな例外を個別で見てやる程度で何とかなっていたが、IPv6は逆引きができないので、それをどうにか解決する方法。
確認環境
| Env | Ver |
|---|---|
| PHP | 8.3.8 |
| geoip2/geoip2 | 3.2.0 |
やり方
- ComposerでGeoIp2を入れる
php composer.phar require geoip2/geoip2 - dbipからIP to City Lite databaseのMMDBを落としてくる。
https://download.db-ip.com/free/dbip-city-lite-2025-11.mmdb.gzのURL形式なのでYYYYとMMのとこをいじっても落とせる。 こんな感じにコードを書く
<?php require 'vendor/autoload.php'; use GeoIp2\Database\Reader; // 落としてきたMMDBを指定 $cityDbReader = new Reader('dbip-city-lite-2025-11.mmdb'); // IPを渡すと結果が取れる $record = $cityDbReader->city('2403:6200:8860:e103:a4b2:ff66:9e68:6436'); print($record->country->isoCode . "\n"); // 'US' print($record->country->name . "\n"); // 'United States' print($record->country->names['zh-CN'] . "\n"); // '美国' print($record->mostSpecificSubdivision->name . "\n"); // 'Minnesota' print($record->mostSpecificSubdivision->isoCode . "\n"); // 'MN' print($record->city->name . "\n"); // 'Minneapolis' print($record->postal->code . "\n"); // '55455' print($record->location->latitude . "\n"); // 44.9733 print($record->location->longitude . "\n"); // -93.2323 print($record->traits->network . "\n"); // '128.101.101.101/32'
参考
- GeoIP extension Introduction - www.php.net
- GeoIP2 PHP API - maxmind.github.io
あとがき
国情報を利用してアクセスを弾く必要があったので雑に調べて導入したが、dbipの無料DBは正確性が低いので、個人の小規模利用向けだと思う。
エンタープライズ用途などで制度が必要な場合は有料DBの契約を検討したほうがいいだろう。
しかしGeoIp2はPHPのマニュアルにあるからPHPに組み込みで存在してるのかと思ったら、まさかのComposerで驚いた。PHPも変わったなぁ…とかしみじみ。
でも昔からPearはあったような気もするし、今更か…?
あると何かと便利だよねというので。
確認環境
| Env | Ver |
|---|---|
| PHP | 8.0.29 |
サンプルコード
取り敢えずファイルをアップロードするだけのコード
<?php
if ($_FILES['image']) {
move_uploaded_file($_FILES['image']['tmp_name'], 'files/'. $_FILES['image']['name']);
} else {
?>
<html>
<body>
<form action="upload.php" enctype="multipart/form-data" method="POST">
<input type="file" name="image">
<button>upload</button>
</form>
</body>
</html>
<?php
}
"hoge", "piyo", "fuga"のように綺麗なCSVであるという前提。CSVファイルが巨大なので行読み込みする。
<?php
$db = new PDO('sqlite:./hoge.db');
$db->beginTransaction();
$fp = fopen("hoge.csv", "r");
$idx = 0;
if($fp){
while ($line = fgets($fp)) {
$row = createRow($line);
$db->exec(
'INSERT INTO hogehoge (`foo`, `bar`, `baz`)'
. ' VALUES '
.'(' . '"' . $row['foo'] .'", ' . '"' . $row['bar'] .'", ' . '"' . $row['baz'] .'"' . ')'
);
$idx++;
echo $idx . "\n";
}
}
fclose($fp);
$db->commit();
function createRow($text) {
$r1 = preg_replace('/"/', '', $text);
$row = explode(',', $r1);
return [
'foo' => $row[0],
'bar' => $row[1],
'baz' => $row[2],
];
}
トランザクションを張りっぱなしだが、exec毎にトランザクションを張りなおすと劇的に遅くなるのでやめたほうがいい(恐らく毎回同期処理でWriteが走っているのだと思う)
動機としてはcurlだと見えない部分があるので、自分でHTTPメッセージを手組みして送ってみたかった。
Node.jsとPHPのサーバーでリクエストが期待通り取得できたので、メッセージの実装としては問題ないと思われる。
検証用サーバー
以下のコードをNode.js v20.11.1を用いて検証
import http from 'node:http';
http
.createServer((req, res) => {
console.log(req.headers);
req.on('data', (chunk) => {
// body
console.log(Buffer.from(chunk).toString());
});
res.statusCode = 200;
res.end();
})
.listen(9999);
netcatコマンドによるリクエスト検証
GETリクエスト
echo -e 'GET / HTTP/1.1\r\nHost: localhost:9999\r\n\r\n' | nc localhost 9999
POSTリクエスト
echo -e 'POST / HTTP/1.1\r\nHost: localhost:9999\r\nContent-Length: 4\r\n\r\nhoge' | nc localhost 9999
備考
PHPで検証サーバーを作る場合
実際にAPサーバーでリクエストの中身をパース出来るかどうかの観点で見た場合にNode.jsよりPHPのが楽なので、PHPで作ってみた結果、軽くハマったので残しておく。
以下のコードを用いてPHP 8.0.29で検証サーバーを作る場合に、php -S 0.0.0.0:9999としてサーバーを起動すると、Content-Typeヘッダがない場合に正常な動作をしなかった。
<?php
var_dump($_SERVER);
var_dump($_REQUEST);
ncで検証サーバーを作る場合
デバッグ用。生のメッセージが見れるのでダンプしてdiffを取るなどでcurlとecho + ncの差分を見るのに使える。
nc -l 9999
HTTPメソッド名を非標準的なものにした場合の挙動
前述のNode.jsサーバーでは400 Bad Request、PHPサーバーでは501 Not Implementedが応答された。
確認環境
| Env | Ver |
|---|---|
| OS | Ubuntu 20.04.4 LTS |
| PHP | 8.0.29 |
| nginx | 1.18.0 |
手順
php-fpmの導入
sudo apt install php8.0-fpm
sudo sed -i -e 's/;listen.mode = 0660/listen.mode = 0666/' /etc/php/8.0/fpm/pool.d/www.conf
sudo service php8.0-fpm start
nginxの設定
設定ファイルを開きPHPを動かす設定を書く
location ~ ^/.*$ {
root /path/to/www;
fastcgi_pass unix:/run/php/php8.0-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}