PHPのcurlやfile_get_contentsでリダイレクトに対応する方法

最近、「curl_multiは確かに爆速だった(がcurl_multi_selectのバグでハマった)」の記事へのアクセス数が増えていたので別のトラブル事例も紹介しておきます。

curl_multiの基本

サンプルコード

//対象のURL
$urls = array (
    'http://example.com/1.jpg',
    'http://example.com/2.jpg',
    'http://example.com/3.jpg'
);
//マルチハンドル初期化
$mh = curl_multi_init();

//後で使うため個別ハンドル保管用の配列を準備
$ch_array = array();

//URLのセット
foreach($urls as $url) {
	$ch = curl_init();
	$ch_array[] = $ch;
	curl_setopt_array($ch, array(
		CURLOPT_URL            => $url,
		CURLOPT_HEADER         => false,
		CURLOPT_RETURNTRANSFER => true,
		CURLOPT_SSL_VERIFYPEER => false,
		CURLOPT_TIMEOUT        => 30
	));
	curl_multi_add_handle($mh, $ch);
}

//リクエスト処理
do {
    curl_multi_exec($mh, $running);
    curl_multi_select($mh);
} while ($running > 0);

//HTML取得
foreach ($ch_array as $ch) {
	$html = curl_multi_getcontent($ch);
	//ここで$htmlを好きにする
	curl_multi_remove_handle($mh, $ch);
	curl_close($ch);
}
//終了処理
curl_multi_close($mh);

解説

過去のcurl_multiの記事で紹介したサンプルとほぼ同じですが、イメージしやすいように対象のURLを1.jpg、2.jpg、3.jpgと変更しています。

SSL用のURLへ誘導されるケースが多くなっている

ぼくは先述のサンプルを応用し、登録した複数のRSSを巡回→そのRSSの中に含まれる画像ファイルをキャッシュ→後で一覧表示する、というプログラムを組んでいます。

しかし、ある日を境に画像ファイルのキャッシュを失敗するようになりました。

試しにブラウザで画像URLを開いても問題なく開けます。

でも同じURLをcurlやfile_get_contentsなどで開くとエラーになる。なんでやねん。

もしかして、User-Agentを指定しないとbotとして弾かれてるのかなぁ、と思いcurlにChromeのふりをさせても失敗してしまいました。けっこう長いこと悩みましたが、わかってみれば単純な話でURLリダイレクトに対応してなかっただけの話でした。

ほら、httpd.confや.htaccessに指定する301 Redirectってヤツです。ブラウザで開いた場合はステータス301を受け取ったらブラウザのほうで適切に判断して、リダイレクトしてくれますが、PHPのcurlやfile_get_contentsではリダイレクト先まで読みに行ってくれなかったんですね。

特に最近は暗号化なしのhttp要求があったら、httpsへリダイレクトさせているパターンが多いため、最近になって自作プログラムがエラーを出すようになった、というワケです。

原因がわかれば後は対応するだけ。

curlで301リダイレクトに対応する

curlでリダイレクトに対応するのはとっても単純で、オプションを追加するだけ。

↓この部分を

	curl_setopt_array($ch, array(
		CURLOPT_URL            => $url,
		CURLOPT_HEADER         => false,
		CURLOPT_RETURNTRANSFER => true,
		CURLOPT_SSL_VERIFYPEER => false,
		CURLOPT_TIMEOUT        => 30
	));

↓こうします。

	curl_setopt_array($ch, array(
		CURLOPT_URL            => $url,
		CURLOPT_HEADER         => false,
		CURLOPT_RETURNTRANSFER => true,
		CURLOPT_SSL_VERIFYPEER => false,
		CURLOPT_TIMEOUT        => 30,
		CURLOPT_FOLLOWLOCATION => true
	));

CURLOPT_FOLLOWLOCATIONをtrueにしただけですね。

ただ、ちゃんとやるなら、CURLOPT_MAXREDIRSでリダイレクトの最大数も指定しておいたほうが無難。

設定を間違えているサーバーのURLなんか開いちゃったらリダイレクトループに陥って帰ってこなくなっちゃいますから。

file_get_contentsでリダイレクトに対応する

一応、オマケでfile_get_contentsの場合も書いておきます。こちらもまぁオプションを追加するだけといえば追加するだけだけど、ちょっと書き方がわかりづらいかも。

$context = stream_context_create(
	array(
		'http' => array(
			'follow_location' => true
		)
	)
);
$html = file_get_contents('http://example.com/1.jpg', false, $context);

file_get_contentsの場合、何かしらオプションを付ける場合には必ずstream_context_createを使ってコンテキストリソースを作る感じです。

この方法を使うと単にオプション指定だけではなく、POST処理やクッキーを持たせた状態でのアクセスなんかもできちゃうので、ちょっとしたブラウザ操作みたいなことも可能です。

このfile_get_contentsによる自動操作も結構面白いので機会があれば記事にしてみたいですね。

まとめ

  • ブラウザでは開けるのにcurlやfile_get_contentsでは開けないURLがある場合、301リダイレクトなどの指定がされていないか確認する。
  • curlでリダイレクトに対応するためにはオプションでCURLOPT_FOLLOWLOCATIONを指定する。
  • file_get_contentsでリダイレクトに対応するためにはオプションでfollow_locationを指定する。

といったところでしょうか。

わかってしまえば単純だし、なんでこんなことで悩んでいたんだろうと思いますが、こういう単純な原因こそハマると長いんですよねぇ。

curlやfile_get_contentsでのリダイレクト対応でハマっている方へこの記事が届くことを願います。

adsbygoogle

フォロー