サンプル・プログラムの実行例
HTTP レスポンス
- クライアントがURLを含むHTTPリクエストを送出する
- サーバ側がHTTPレスポンスを返す
fopen 関数を使ってインターネットにアクセスする際、表に見えてこないが、実際にはリクエスト・ヘッダとレスポンス・ヘッダがやり取りされている。そして、レスポンス・ヘッダの中には、リクエストしたコンテンツがどうなっているかを示すステータスが記録されている。
このステータスを解析することで、リンク切れなのかどうか判断できる。
HTTP レスポンスの詳細については、HTTP Status Codeを参照してほしい。
今回は、HTTP レスポンスがクライアントエラー(4xx番台)とサーバエラー(5xx番台)が「リンク切れ」であると定義し、プログラムを作っていくことにする。
サンプル・プログラム
解説:URLの検出
タグ "<a href~>" と タグ "<img src~>" のハイパーリンクを検出対象とするため、複数の正規表現をテーブルとして、配列変数 $PatTable に用意するというアレンジを加えている。
もし <object> タグなども検出対象にしたいのであれば、配列変数 $PatTable にあらたな正規表現を加えるだけでよい。
132: /**
133: * テキスト中からURLを取り出す
134: * @param string $str 解析するテキスト
135: * @param array $url 取り出したURLを格納する配列
136: * @return int 取り出したURL数 / FALSE(抽出に失敗)
137: */
138: function get_urls($str, &$urls) {
139: $PatTable = array(
140: "/<a(.*)href=\"?([\-_\.\!\~\*\'\(\)a-z0-9\;\/\?\:@&=\+\$\,\%\#]+)\"/i",
141: "/<img(.*)src=\"?([\-_\.\!\~\*\'\(\)a-z0-9\;\/\?\:@&=\+\$\,\%\#]+)\"/i"
142: );
143:
144: $i = 0;
145: foreach ($PatTable as $pat) {
146: //マッチするすべての部分文字列を取り出す
147: if (preg_match_all($pat, $str, $arr, PREG_SET_ORDER) > 0) {
148: foreach ($arr as $key=>$val) {
149: $urls[$i] = $arr[$key][2];
150: $i++;
151: }
152: }
153: }
154:
155: return $i;
156: }
解説:HTTPレスポンス
残念ながらPHP 4 にこの関数はないので、同等の関数をユーザー定義することにした。それがユーザー関数 get_http_header である。
ユーザー関数 get_http_header は、配列変数に HTTPバージョン、ステータス・コード、リーズンの3つを返す。
こうして、ユーザー関数 get_url とユーザー関数 get_http_header を呼び出すことで、リンク切れのチェックができる。
これを行うのがユーザー関数 check_links である。
あとは、押下されたボタンによって処理を分岐させるメイン・プログラム部分と、表示プログラムを用意すればいい。
210: /**
211: * HEADリクエストを行う
212: * @param string $uri
213: * @return array 'HTTP-Version', 'Status-Code', 'Reason-Phrase'
214: */
215: function get_http_header($uri) {
216: // URIから各情報を取得
217: $info = parse_url($uri);
218:
219: $scheme = $info['scheme'];
220: $host = $info['host'];
221: $port = isset($info['port']) ? $info['port'] : 80;
222: $path = isset($info['path']) ? $info['path'] : ''; //Ver.2.3
223:
224: //リクエストフィールド
225: $msg_req = "HEAD " . $path . " HTTP/1.0\r\n";
226: $msg_req .= "Host: $host\r\n";
227: $msg_req .= "User-Agent: H2C/1.0\r\n";
228: $msg_req .= "\r\n";
229:
230: // スキームがHTTP(S)の時のみ実行する
231: if (preg_match('/https?/i', $scheme) > 0) { //Ver.2.4
232: $status = array();
233:
234: // 指定ホストに接続
235: if ($handle = @fsockopen($host, $port, $errno, $errstr, 1)) {
236: fputs($handle, $msg_req);
237: if (socket_set_timeout($handle, 3)) {
238: $line = 0;
239: while(!feof($handle)) {
240: // 1行めはステータスライン
241: if ($line == 0) {
242: $temp_stat = explode(' ', fgets($handle, 4096));
243: $status['HTTP-Version'] = array_shift($temp_stat);
244: $status['Status-Code'] = array_shift($temp_stat);
245: $status['Reason-Phrase'] = implode(' ', $temp_stat);
246:
247: // 2行目以降はコロンで分割
248: } else {
249: $temp_stat = explode(':', fgets( $handle, 4096));
250: $name = array_shift( $temp_stat );
251: // 通常:の後に1文字半角スペースがあるので除去
252: $status[$name] = substr(implode(':', $temp_stat), 1);
253: }
254: $line++;
255: }
256:
257: } else {
258: $status['HTTP-Version'] = '---';
259: $status['Status-Code'] = '902';
260: $status['Reason-Phrase'] = "No Response";
261: }
262:
263: fclose($handle);
264:
265: } else {
266: $status['HTTP-Version'] = '---';
267: $status['Status-Code'] = '901';
268: $status['Reason-Phrase'] = "Unable To Connect";
269: }
270:
271: } else {
272: $status['HTTP-Version'] = '---';
273: $status['Status-Code'] = '903';
274: $status['Reason-Phrase'] = "Not HTTP Request";
275: }
276:
277: return $status;
278: }
参考サイト
- PHPでリンク切れを調べる:ぱふぅ家のホームページ
(2021年4月4日)PHP8対応,リファラチェック追加