PHPで圧縮したバイナリデータをPOSTする方法
先日投稿した「PHP7.4でjson_encodeとserializeの速度比較」の続きみたいなものですが、今度はシリアライズしたデータをPOSTで転送する方法について、備忘録として残しておきたいと思います。
ローカルPC側でアレコレ集計処理や計算などの重い処理をした結果がデータベースにあり、それをリモートのWebサイトへ送るような使い方を想定。
シリアライズしたデータはそこそこ大きい
データベースから10,000行くらいまとめてPOSTするケースを想定した例です。そんなにあるならサーバー側にFTPで送ってインポートせいや! …というご意見もあるかもですし、そのとおりだと思いますが、面白そうだから作ってみた! そして実際使ってみたら意外と使えた! それだけのことです。
PHPサンプル
<?php
$rows = array();
$row = array();
$row['id'] = 1;
$row['model'] = 'ABC-12345';
$row['name'] = 'Ryzen 5 4500U搭載14.1インチノートパソコン';
$row['category'] = 'ノートパソコン';
$row['weight'] = 1.29;
$row['price'] = 106000;
for ($i = 0; $i < 10000; $i++) {
$rows[] = $row;
}
$json = json_encode($rows, JSON_UNESCAPED_UNICODE);
echo strlen($json).'<br>';
$json_gz = gzcompress($json);
echo strlen($json_gz).'<br>';
?>
サンプルのデータサイズ
上記の例は10,000行くらいあるデータを連想配列にまとめてjsonに変換した例ですが、サイズ的には1,560,001バイト。つまり約1.5MBほどあります。
1行あたりだと156バイト程度なので、たまに数行送るくらいならjsonのまま送っても良いのですが、このくらいのサイズになってくると圧縮を検討したくなります。
そこで便利なのがgzcompress関数。
zipコマンドだと環境によってパスを変えないといけないかも知れないし、一時的とはいえファイルの保存場所を考えるのも面倒です。
その点、gzcompressはPHP標準関数だからすぐ使えるしメモリ中で全ての処理が済むし、圧縮率高いし、ちょー素敵。
ちなみに上記の例では1,560,001バイトが6,219バイトにまで圧縮されます。1.5MBが6KB。元データの250分の1です。
ただ、コイツはバイナリデータなので、どうやってPOSTするかっていう話。
バイナリデータをbase64変換してPOSTする例
PHPサンプル(送信側)
<?php
$rows = array();
$row = array();
$row['id'] = 1;
$row['model'] = 'ABC-12345';
$row['name'] = 'Ryzen 5 4500U搭載14.1インチノートパソコン';
$row['category'] = 'ノートパソコン';
$row['weight'] = 1.29;
$row['price'] = 106000;
for ($i = 0; $i < 10000; $i++) {
$rows[] = $row;
}
$json = json_encode($rows, JSON_UNESCAPED_UNICODE);
$json_gz = gzcompress($json);
$base64 = base64_encode($json_gz);
$postdata = array('base64' => $base64);
$opts = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-Type:application/x-www-form-urlencoded',
'content' => http_build_query($postdata)
)
);
file_get_contents('http://example.com/receive.php', false, stream_context_create($opts));
?>
PHPサンプル(受信側)
$json_gzip = base64_decode($_POST['base64']);
$json = gzuncompress($json_gzip);
$rows = json_decode($json, true);
print_r($rows);
解説
- 連想配列をJSONでシリアライズする
- シリアライズしたデータをgzcompressで圧縮する
- 圧縮データ(バイナリ)をbase64でテキストにする
- そのテキストをPOSTする
という力技感あふれる仕組み。
せっかく圧縮したのにbase64エンコードすることによって30%くらい増えちゃうけど気にしない。
だって、元が1.5MBのデータで、圧縮して6KBになったデータです。そこから30%増えたって8KB程度。実際、ちゃんと計測したら8,292バイトでした。
なんかすごく無駄っぽいけど、既存のシステムに無理やり組み込むときとか意外と重宝するんじゃないかなー…。
ほら、受信側はPOSTデータをテキストとして受け取る作りになっているからあまり変えたくないときとか。あとでテキストのパラメータをたくさん増やす予定があるときとか。
半分ネタで作ったんだけど、わりと使える場面ありそうな気がしてきました。
バイナリデータをバイナリのままPOSTする例
PHPサンプル(送信側)
<?php
$rows = array();
$row = array();
$row['id'] = 1;
$row['model'] = 'ABC-12345';
$row['name'] = 'Ryzen 5 4500U搭載14.1インチノートパソコン';
$row['category'] = 'ノートパソコン';
$row['weight'] = 1.29;
$row['price'] = 106000;
for ($i = 0; $i < 10000; $i++) {
$rows[] = $row;
}
$json = json_encode($rows, JSON_UNESCAPED_UNICODE);
$json_gz = gzcompress($json);
$opts = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-Type:application/octet-stream',
'content' => $json_gz
)
);
file_get_contents('http://example.com/receive.php', false, stream_context_create($opts));
?>
PHPサンプル(受信側)
$json_gzip = file_get_contents('php://input');
$json = gzuncompress($json_gzip);
$rows = json_decode($json, true);
print_r($rows);
解説
Content-Typeにoctet-streamを指定してバイナリデータをそのまま送信する例です。効率で言ったらこれが一番良いでしょう。
ただ、個人的にはあんまり好きじゃないんですよねー。
file_get_contents('php://input');
とあるとおり、生のPOSTデータを受け取る仕組みなので、バイナリデータのほかにちょっとテキストも送りたい、なんてなったときに面倒くさい。
先頭の5文字はモード用のパラメータにする、とか通信ルール決めても絶対後で忘れる自信あるし…w
Content-Type:multipart/form-dataにしてboundary決めてテキストとバイナリを一緒に送るほうが汎用的ですが、今回はせっかく連想配列もデータ圧縮もメモリ中で済ませたので、ファイル用意するのイヤだなぁーというよくわからない理由で見送りました。
まとめ
- ローカルでアレコレ集計したデータを転送するためにデータベースをJSON化してみた
- データ量増えると転送量とリモートの負荷が怖いのでgzcompressで圧縮してみた
- データ量が250分の1になったけれどバイナリデータなのでbase64化してPOSTすることにした
- 一応、octet-streamでバイナリそのまま送る例も作ってみた
ということで、ぼくにしか需要がなさそうな気がする備忘録でしたが、ローカルのcronタスクで動かしてるPHPからリモートのWebサーバーへPOSTする方法がわからない…とか、データを圧縮転送する方法はないものか、と悩んでいる方の参考にでもなれば幸いです。