HOME


PHPのちょっとしたTIPS

Last Modified : 2003/03/18 (Tue)
No.3 HTTPクライアント - fsockopen() fputs() fgets() fclose() parse_url() urlencode()

PHPで簡易HTTPクライアントを作ってみます。
とりあえず、TELNETを使って実際にWEBサーバと通信してみます。
基本は

$ telnet ホスト名 ポート番号

です。
Windowsのtelnet.exeでも同じようにできます。
ローカルエコーがオン、改行コードは<CRLF>である必要があります。
TeraTermを使う時もそこら辺を設定しておく必要があります。
以下はLinux上で行っています。改行は<CRLF>と書いてます。
ローカルでApacheを起動しておいて、
$ telnet localhost 80
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'
GET / HTTP/1.0<CRLF>
<CRLF>
HTTP/1.1 200 OK
Date: Mon, 18 Jun 2001 12:46:47 GMT
Server: Apache/1.3.20 (Unix) PHP/4.0.5 PHP/3.0.18-i18n-ja-2
X-Powered-By: PHP/4.0.5
Connection: close
Content-Type: text/html; charset=shift_jis

(省略)

Connection closed by foreign host.
という感じです。
GET / HTTP/1.0
という一行は Request-Line というやつで、
Method Request-URI HTTP-Version<CRLF>
というフォーマットです。
Methodはいろいろありますが、GET、POST、HEADぐらいは知っておきましょう。
一般にWEBサーバへのRequestは、
Request-Line<CRLF>
Request-Header<CRLF>
<CRLF>
Message-Body
という形です。
Request-HeaderにはRefererとかUser-Agentとかのヘッダを記入します。
ヘッダ名:値<CRLF>
という感じでコロンで区切ります。
Message-BodyはPOSTメソッドのようにサーバへデータを送るときに使います。

というわけでfsockopen()関数を使って作ってみました。(笑)
HEAD, GET, POSTで動きます。Basic認証もいけます。
<?php
/*
    $url     : http://から始まるURL( http://user:pass@host:port/path?query )
    $method  : GET, POST, HEADのいずれか(デフォルトはGET)
    $headers : 任意の追加ヘッダ
    $post    : POSTの時に送信するデータを格納した配列("変数名"=>"値")
*/
function http($url, $method="GET", $headers="", $post=array(""))
{
    /* URLを分解 */
    $URL = parse_url($url);

    /* クエリー */
    if (isset($URL['query'])) {
        $URL['query'] = "?".$URL['query'];
    } else {
        $URL['query'] = "";
    }

    /* デフォルトのポートは80 */
    if (!isset($URL['port'])) $URL['port'] = 80;

    /* リクエストライン */
    $request  = $method." ".$URL['path'].$URL['query']." HTTP/1.0\r\n";

    /* リクエストヘッダ */
    $request .= "Host: ".$URL['host']."\r\n";
    $request .= "User-Agent: PHP/".phpversion()."\r\n";

    /* Basic認証用のヘッダ */
    if (isset($URL['user']) && isset($URL['pass'])) {
        $request .= "Authorization: Basic ".base64_encode($URL['user'].":".$URL['pass'])."\r\n";
    }

    /* 追加ヘッダ */
    $request .= $headers;

    /* POSTの時はヘッダを追加して末尾にURLエンコードしたデータを添付 */
    if (strtoupper($method) == "POST") {
        while (list($name, $value) = each($post)) {
            $POST[] = $name."=".urlencode($value);
        }
        $postdata = implode("&", $POST);
        $request .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $request .= "Content-Length: ".strlen($postdata)."\r\n";
        $request .= "\r\n";
        $request .= $postdata;
    } else {
        $request .= "\r\n";
    }

    /* WEBサーバへ接続 */
    $fp = fsockopen($URL['host'], $URL['port']);

    /* 接続に失敗した時の処理 */
    if (!$fp) {
        die("ERROR\n");
    }

    /* 要求データ送信 */
    fputs($fp, $request);

    /* 応答データ受信 */
    $response = "";
    while (!feof($fp)) {
        $response .= fgets($fp, 4096);
    }

    /* 接続を終了 */
    fclose($fp);

    /* ヘッダ部分とボディ部分を分離 */
    $DATA = split("\r\n\r\n", $response, 2);

    /* リクエストヘッダをコメントアウトして出力 */
    echo "<!--\n".$request."\n-->\n";

    /* レスポンスヘッダをコメントアウトして出力 */
    echo "<!--\n".$DATA[0]."\n-->\n";

    /* メッセージボディを出力 */
    echo $DATA[1];
}
?>
改行コードは<CRLF>だからPHPでは"\r\n"です。

parse_url()関数は便利ですね。 URLを各要素に分解して連想配列を返します。ここではこれを利用しました。

Basic認証用のAuthorizationヘッダはユーザ名とパスワードをコロンで区切ってbase64_encode()するだけです。単純ですね。

POSTメソッドでは、Content-TypeヘッダとContent-Lengthヘッダを付加してメッセージボディとしてurlencode()したデータを添付します。

サーバからのレスポンスはヘッダとメッセージボディが<CRLF><CRLF>で区切られているので、split()関数で分割しました。 (単に出力するだけならその必要もないですけど)

クッキーを与えたい時はCookieヘッダを作ってあげればOKです。その他任意のヘッダを引数に指定できます。デフォルトでHostヘッダとUser-Agentヘッダを送信します。

使い方は簡単です。普通にGETするだけなら、
<?php

http("http://www.spencernetwork.org/");

?>
です。
とはいえ、このままだと実用的ではないので実験用ですね(^^;