はじめに
PHPでIOT機器の死活監視をするときに「file_get_contents」関数を使った時に、後で後悔することがあった・・という話です。
死活監視といっても難しいことはせず、監視対象のIOT機器がWEB設定画面を持っていたのでログイン画面のURLをたたいて応答有無で簡易的に死活監視したという程度のことですが。
file_get_contentsでいけると思ったけど
トラブル対応の一環でIOT機器の死活監視ツールを頼まれまた時のことです。
急いでほしい言われて、簡単に書ける「file_get_contents」を使いました。
PHP-マニュアル-file_get_contents
「file_get_contents」関数が遅いといわれているのは知ってましたが、ファイル全部ではなくて、先頭の10文字分だけ読み込むなどの工夫で、せいぜい30から40台程度のIOT機器を1分程度でチェックできればいいなら、タイムアウト設定も可能ですし問題ないだろうと判断しました。
その時のイメージがこんな感じです(エラー処理とか枝葉は省いてますが)。
$context = stream_context_create([
'http' => [
'timeout' => 5
]
]);
$ret = @file_get_contents($url, FALSE, $context, 0, 10);
//取得できたかどうかを判定
if($ret != false){
//取得できた
}else{
//取得できなかった
}
タイムアウト5秒で、「0, 10」の引数で先頭の10文字だけ読み込むように強制し、「@(エラー制御演算子)でエラーメッセージを抑制しています。
file_get_contents関数は、成功したら読み込んだデータを返し、失敗で false を返すので「false」でなければ応答があったと判定する・・という感じです。
問題発生:開発環境と本番でレスポンスが違う
その時の開発・テスト環境はWIndows10でした。
本番環境はUNIX系OS(FreeBSDと聞いています。バージョンは聞いていません。)です。
PHPのメジャーバージョンは、どちらも7.3です。
開発環境と本番環境でOSが異なる、いわゆるクロスプラットフォームです。
ですが、プロジェクトはずっとそれでやっていて、問題になったことはなかったのです。
ところが。
開発・テスト環境では問題なく動作したソースが、本番環境の動作確認で「NG」になりました。
確認していくと「file_get_contents」関数の戻り値が違います。
同じURLなのに、Windows環境だと文字列、本番環境ではNULLが返ってきていました。
Windows環境だと存在しないとfalseが返り、存在すると文字列データが返ってくるのに、本番環境だと存在しないとfalseが返り、存在するとNULLが返ってきます。
エラーになっているわけではありません。
単にレスポンスがNULLなだけです。
この時は、本番環境の権限を開発用に最低限しかもらってなかったので、これ以上の調査ができず、結局、原因はわからずじまいでした。
まあ、PHPのcURLを使って「file_get_contentsの代替関数」を作って、なんとかなったのですが、時短のつもりで「file_get_contents」を使って、余計な手間をかけただけになった・・と後悔しました・・・やれやれです。
対応:cURLを使った代替関数
その時、僕が用意した「file_get_contentsの代替関数」は、こんな感じでした。
public function curl_file_get_contents($url,$timeout){
$curl = curl_init($url);
$options = array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_SSL_VERIFYPEER => false,
);
curl_setopt_array($curl, $options);
$result = curl_exec($curl);
//$errno = curl_errno($curl);
//$error = curl_error($curl);
curl_close($curl);
if($result === FALSE){
return false;
}else{
return true;
}
}
実際は「curl_errno()」や「curl_error()」の情報をログに書いたり、もう少しゴチャゴチャしてますけど、枝葉の部分は割愛してます。
これを以下のように使ってURL存在チェックをするように変更しました。
$ret = curl_file_get_contents($url,5);
//取得できたかどうかを判定
if($ret != false){
//取得できた
}else{
//取得できなかった
}
本番環境での問題は、この方法で解決できました。
開発環境と本番環境でまったく同じ動きになります。
その後、他のLinuxディストリビューションとかで同じようなことを頼まれて流用しましたが、今のところ、この方法で問題がでたことはありません。
たぶん、僕は同じ要件の依頼がきても「file_get_contents」はもう使わないです(笑)。
ではでは。