fsockopen()とHTTP/1.1
キーワード密度やタグ解析をするSEOツールっぽいものを作ろうと思いHTTPクライアントを作ってみました。
function createRequest($url, $method = 'GET', $header = '', $posts = array()) { $uri = parse_url($url); if (isset($uri['query'])) { $uri['query'] = '?' . $uri['query']; } else { $uri['query'] = ''; } //デフォルト $out = $method . ' ' . $uri['path'] . $uri['query'] . " HTTP/1.1\r\n"; $out .= "Host: " . $uri['host'] . "\r\n"; $out .= "User-Agent: PHP/" . phpversion() . "\r\n"; $out .= "Connection: Close\r\n"; $out .= $header; //POSTの場合 if ($method == 'POST') { foreach ($posts as $key => $val) { $post[] = $key . '=' . $val; } $postdata = implode('&', $post); $out .= "Content-Type: application/x-www-form-urlencoded\r\n"; $out .= "Content-Length: " . strlen($postdata) . "\r\n\r\n"; $out .= $postdata; } else { $out .= "\r\n"; } return $out; }
リクエストヘッダーの作成
HTTP/1.1で通信するとチャンク形式になってしまいところどころにchunk-sizeが書き込まれてしまいます。
デコード書くの面倒だしどこかにないかと検索したけど見つからず。
HTTP/1.0でいいかなぁ、と思っていたら
ちゃんとfsockopen()のマニュアルのところにデコード方法がコメントされていました。(後述)
function getResponse($url, $out) { $uri = parse_url($url); if (!isset($uri['port'])) $uri['port'] = 80; //WEBサーバへ接続 $fp = @fsockopen($uri['host'], $uri['port'], $errno, $errstr, 30) or die('[' . $errno . ']' . $errstr); //リクエストの送信 fwrite($fp, $out); //レスポンスヘッダーの受信 $in = ''; //<CRLF>が2つ続くまでループ do { $in .= fgets($fp, 4096); } while (strpos($in, "\r\n\r\n") === false); //ヘッダーを配列に格納 $info = decode_header($in); //コンテンツ部分の受信 $in = ''; while (!feof($fp)) { $in .= fgets($fp, 4096); } fclose($fp); //デコード $body = decode_body($info, $in); return array($info, $body); }
WEBサーバとの通信
function decode_header($str) { //<CRLF>ごとに分割 $part = preg_split("/\r\n/", $str, -1, PREG_SPLIT_NO_EMPTY); $out = array (); for ($h = 0; $h < sizeof($part); $h++) { if ($h != 0) { // :で区切ってkeyとvalueを作成 $pos = strpos($part[$h], ':'); $k = strtolower(str_replace(' ', '', substr($part[$h], 0, $pos))); $v = trim(substr($part[$h], ($pos + 1))); } else { //1行目ステータスコード $k = 'status'; $v = explode (' ', $part[$h]); $v = $v[1]; } //keyとvalueを配列に格納 if ($k == 'set-cookie') { $out['cookies'][] = $v; } elseif ($k == 'content-type') { if (($cs = strpos($v, ';')) !== false) { //目的が解析なのでサブタイプは切り捨てない //$out[$k] = substr($v, 0, $cs); $out[$k] = $v; } else { $out[$k] = $v; } } else { $out[$k] = $v; } } return $out; }
ヘッダーを配列に格納マニュアルのjbr at ya-right dot comさん
これでchunkedかどうか解りやすくなりました。
function decode_body ($info, $str, $eol = "\r\n") { $tmp = $str; $add = strlen($eol); $str = ''; //チャンク形式の判定 if (isset($info['transfer-encoding']) && $info['transfer-encoding'] == 'chunked') { do { //チャンクサイズ取得してを10進数に変換 $tmp = ltrim($tmp); $pos = strpos($tmp, $eol); $len = hexdec(substr($tmp, 0, $pos)); //圧縮転送されている場合解凍する if (isset($info['content-encoding'])) { $str .= gzinflate(substr($tmp, ($pos + $add + 10), $len)); } else { $str .= substr($tmp, ($pos + $add), $len); } $tmp = substr($tmp, ($len + $pos + $add)); $check = trim($tmp); } while (!empty($check)); } elseif (isset($info['content-encoding'])) { //圧縮転送されている場合解凍する $str = gzinflate(substr ($tmp, 10)); } return $str; }
コンテンツ部分のデコード同じくマニュアルのjbr at ya-right dot comさん
これで簡単なHTTPクライアントとして機能します。
指定URLのページの解析以外にもGETやPOST埋め込んで検索結果取り出したりできるかな
でも、Googleとかツールを使ってのアクセスとか規約的にどうなんだろう?
何よりもまずマニュアルを読む、これが大事ですね^^;