PHPのcURL関数 を使えば、外部アクセスが容易にできてデータを送信したり、スクレイピングするのが容易になりますが、特に社内環境などプロキシを通さないといけない環境でcURL関数を使う場合にはまったことのメモです。
cURL関数でプロキシを通す
一般的に、プロキシを使って外部にアクセスする場合のプログラムは下記のようになります。
<?php
// アクセスするURL
$url = "https://www.google.co.jp";
// プロキシを通すかどうかのフラグ
$proxyFlg = TRUE;
$ch = curl_init($url);
$defaultOption = array(CURLOPT_HEADER => FALSE, // ヘッダを出力しない
CURLOPT_RETURNTRANSFER => TRUE, // curl_exec()の戻り値を文字で受取る
CURLOPT_FAILONERROR => TRUE // HTTPステータスコード400 以上の場合に 処理失敗と判断
);
curl_setopt_array($ch, $defaultOption);
// プロキシを通さないといけないURLの場合
if ($proxyFlg === TRUE) {
$proxyOption = array(CURLOPT_PROXY => "http://proxy.example.com",
CURLOPT_PROXYPORT => 8080);
curl_setopt_array($ch, $proxyOption);
}
// サイトにアクセス、エラーの場合メッセージを出力
if (($response = curl_exec($ch)) === FALSE) {
echo curl_error($ch) . PHP_EOL;
}
echo $response;
curl_close($ch);
上記のプログラム内ではプロキシを通すかどうかは便宜上、先頭にフラグを立ててしまっていますが、実際にはそのURLがプロキシを経由する必要があるかどうかの判別が必要です。
で、本題のところですが自分の環境では上記のようにプログラム内部においてプロキシを通すかどうかの判別処理を入れていたのですが、どうもサイトからHTTPステータスコードの403が返ってきて悩んでいたわけです。
アクセス先のサイトにACLは切っていたものの、ログを見てもどうもそこまで到達していないらしく事前にアクセスを拒否されているようでした。
で、後述するデバッグ方法で通信情報を見てみるとプロキシが通るようになっており、プロキシ側で403を返されていました。
内部的なプロキシを通す/通さないの判別処理は正しかったのでなぜプロキシ経由になるんだって悩んでたわけですが、原因はApacheの環境変数にありました。
rootユーザーの.bash_profileに下記のように環境変数を埋め込んでおり、それがApache起動時に読み込まれていました。
export HTTP_PROXY=http://proxy.example.com:8080 export HTTPS_PROXY=http://proxy.example.com:8080
phpinfo(またはgetenv())で環境変数をみてみると、確かにプロキシの情報がセットされています。
var_dump(getenv("HTTPS_PROXY"));
string(29) http://proxy.example.com:8080
ってことで、プログラム内の処理以前に環境変数によって強制的にプロキシを通るようになっていたわけです。
コマンドラインでどうプログラムを実行すると実行ユーザーが違うためにこの環境変数の影響を受けないのに、ウェブアクセスだとうまくいかないからかなりはまったりしました。
先のプログラムの中でアクセス先のサンプルとしてGoogleサイトを指定していますが、ここではHTTPSでのアクセスとしています。
これにも理由があって、HTTPSでのアクセスだと環境変数内のHTTPS_PROXYが有効になるのですが、HTTPでのアクセスだと環境変数のHTTP_PROXYは読み取ってくれず、PHP内のCURLOPT_PROXYオプションをcurl_setopt()でセットしなくてはなりません。
なんで、HTTPSのときだけなのかわからないんですが、環境変数の影響を受けたくないなら、先のプログラムを下記のように意図的にプロキシを通さない場合はCURLOPT_PROXYオプションをクリアしてしまうほうがよいかもしれません。
if ($proxyFlg === TRUE) {
$proxyOption = array(CURLOPT_PROXY => "http://proxy.example.com",
CURLOPT_PROXYPORT => 8080);
} else {
$proxyOption = array(CURLOPT_PROXY => "",
CURLOPT_PROXYPORT => "");
}
curl_setopt_array($ch, $proxyOption);
cURL関数のデバッグ方法
他のサイトにアクセスするので、エラーがあった際にはcurl_error()の内容から受け取ったHTTPステータスコードなどである程度のデバッグができますが、403とか返ってくると今回のようにどこで蹴られているのか(プロキシからのエラーなのか、該当サイトのACLに引っかかっているのかなど)がわかりづらくなります。
ってことで、通信状況なども見たいのであれば、下記のようにデバッグのオプションを入れることで、cURL関数がどういう経路で通信しようとしているのか見ることができます。(先のプログラムの一部抜粋版です)
// 通信内容を保存するためのテンポラリファイル
$fp = tmpfile();
$ch = curl_init($url);
$defaultOption = array(CURLOPT_HEADER => FALSE, // ヘッダを出力しない
CURLOPT_RETURNTRANSFER => TRUE, // curl_exec()の戻り値を文字で受取る
CURLOPT_VERBOSE => TRUE, // デバッグを有効化
CURLOPT_STDERR => $fp, // デバッグ情報は標準エラー出力されるためファイルに書き出す
CURLOPT_FAILONERROR => TRUE // HTTPステータスコード400 以上の場合に 処理失敗と判断
);
curl_setopt_array( $ch, $defaultOption);
// サイトにアクセス
if (($response = curl_exec($ch)) === FALSE) {
echo curl_error($ch) . PHP_EOL;
}
// 記録した通信内容を読み出す
fseek($fp, 0);
while (($line = fgets($fp)) !== FALSE) {
$tmp .= $line . "<br />";
}
// 通信状況を出力
echo $tmp;
主な変更点は、CURLOPT_VERBOSEとCURLOPT_STDERRオプションを追加すること、そしてその内容を一時ファイルに書き出し、最後に出力させるというだけです。
以下のような結果が受け取れたりします(これはプロキシで拒否されて403を返されていることがわかる)
The requested URL returned error: 403 bool(false) * About to connect() to proxy proxy.example.com port 8080 * Trying 192.168.1.2... * connected * Connected to proxy.example.com (192.168.1.2) port 8080 * Establish HTTP proxy tunnel to www.example.com:443 > CONNECT www.example.com:443 HTTP/1.0 Host: www.example.com:443 Proxy-Connection: Keep-Alive < HTTP/1.1 403 Forbidden < Cache-Control: no-cache < Pragma: no-cache < Content-Type: text/html; charset=utf-8 < Proxy-Connection: close < Connection: close < Content-Length: 2943 < * The requested URL returned error: 403 * Received HTTP code 403 from proxy after CONNECT * Closing connection #0
[PR]
[PR]