PSI Labs RSS feed

file_get_contents を使った HTTPリクエストでタイムアウトが正しく動かない

こんにちは、tomita です。

file_get_contents を使った HTTPアクセスでタイムアウトを設定するには以下のように書くのですが、これ、正しく動きません。どうやら設定値の2倍待たされるようです…(以下の場合は6秒待たされる)。

<?php
// stream_context_create を使う場合
$sc = stream_context_create(["http" => ["timeout" => 3]]); // timeout まで 3秒
$src = @file_get_contents("http://example.com/test.php", false, $sc );

試しに file_get_contents, fsockopen, curl を使って検証してみました。以下ソースです。

<?php
// http://example.com/test.php は 10秒sleep するだけのスクリプト

// file_get_contents利用
function req1($timeout) {
  $sc = stream_context_create(["http" => ["timeout" => $timeout]]);
  return @file_get_contents( "http://example.com/test.php", false, $sc );
}

// fsockopen利用
function req2($timeout) {
  $fp = fsockopen("example.com", 80);
  stream_set_timeout($fp, $timeout);
  fwrite($fp, implode("\r\n", [
    "GET /test.php HTTP/1.1",
    "Host: example.com",
    "Connection: Close",
  ]) . "\r\n\r\n");
  $res = stream_get_contents($fp);
  $meta = stream_get_meta_data($fp);
  fclose($fp);
  if ( $meta["timed_out"] ) {
    return false;
  }
  list($header, $body) = explode("\r\n\r\n", $res, 2);
  return $body;
}

// curl利用
function req3($timeout) {
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, "http://example.com/test.php");
  curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  $src = curl_exec($ch);
  curl_close($ch);
  return $src;
}

//--------------------------

$timeout = 2;

echo "------file_get_contents\n";

$start = microtime(true);
$result = req1($timeout);
$end = microtime(true);
var_dump( $result, $end - $start );

echo "------fsockopen\n";

$start = microtime(true);
$result = req2($timeout);
$end = microtime(true);
var_dump( $result, $end - $start );

echo "------curl\n";

$start = microtime(true);
$result = req3($timeout);
$end = microtime(true);
var_dump( $result, $end - $start );

以下、実行結果です。fsockopen と curl は2秒でタイムアウトしますが、file_get_contents だけ 4秒かかっています…。

------file_get_contents
bool(false)
float(4.0042290687561)
------fsockopen
bool(false)
float(2.0011141300201)
------curl
bool(false)
float(2.0041151046753)

正確にタイムアウト処理を行うには fsockopen または curl を使う必要がありますね…

少しハマったのでメモしておきます。それでは~