2007/02/03
XML
カテゴリ: ホームページ作成

というわけで、今回もラジオねたではなく、前回の PerlのCGIからWebサービスにアクセスする の続きで「素のPerlでWebサーバーにアクセスするにはどうするのか?」です。コードはめんどいので正常系のみでいきます。もし実装するときはちゃんとエラー処理もいれてください。

まずは、最初に注意事項として申し上げますが、CGIから他のサーバーにアクセスするようにしていると、そのCGIが大量に呼び出された場合でも、呼び出すサーバーに負荷がかからないようにすべきでしょう。さて、サンプルコードですが、connect()までは、世に存在するこの手のコードと一緒なので、詳細な説明は省きます。基本的にこういうものだと思ってもらって問題ないと思いますが…。

$AF_INET = 2;
$SOCK_STREAM = 1;
$SockAddr = 'S n a4 x8';

($name,$aliases,$proto) = getprotobyname('tcp');
($name,$aliases,$type,$len,$localAddr) = gethostbyname('localhost');
($name,$aliases,$type,$len,$serverAddr) = gethostbyname($host);

$this = pack($SockAddr, $AF_INET, 0, $localAddr);
$server = pack($SockAddr, $AF_INET, $port, $serverAddr);

socket(S, $AF_INET, $SOCK_STREAM, $proto);
bind(S, $this);
connect(S, $server);

ここで、$host、$portはアクセスするサーバーのホスト名(ドメイン表記でも可)とポート番号です。httpの場合は、普通$portは80になるでしょう。次はサーバーへリクエストを送ります。要するにHTTPプロトコルのGETメソッドを使ってコンテンツを持ってくる方法ということになります。

binmode(S);
select(S); $| = 1;

$request = "GET $uri HTTP/1.1";
$header = "Host: $host";

print "$request\r\n";
print "$header\r\n\r\n";
select(STDOUT);

binmode()はUNIX上では意味はないですが念のため。「$| = 1;」はこれを指定しておかないとバッファに溜まったままで、サーバーに送り出してくれません。最後の「select(STDOUT);」は、デフォルト出力先を元に戻しています。
ここでは、$urlと$hostを指定しなければならないのですが、$hostは前のコードの$hostと同じで基本的にはOKです。$uriは、ここではサーバー内のパス(すなわちURLでhttp://hostname以降に書いてあるもの)を指定します(例えば、「/cgi-bin/test.cgi?param=value」)。
ヘッダーの「Host:」はHTTP/1.1では必須なので省略不可で、あとお好みでヘッダーを追加してください。
$header .= "\r\nUser-Agent: hogehoge";
と、書いてみたりとか。
これで、リクエストは送られましたので、あとは結果を読み込むわけですが、失敗しなければ、「ステータス」と「ヘッダー」と「リクエストの結果(コンテンツ)」が返ってきます。とりあえず、ヘッダーの解析までです。

<S> =~ /^HTTP\/[.0-9]+ ([0-9]{3})/;
if ($1 ne '200') {
#どうやらエラー
}
while(<S>) {
last if $_ eq "\r\n";
$headers{$1} = $2 if /^(.+): (.+)$/;
}

というような、ちょっと強引なコードですが…。まずステータスコード「200」以外はエラーとみなしていますが、このあとヘッダー、場合によってはコンテンツも送られてきますので、ここはそのまま突っ走ります。たぶん、コードを保存しておいて、最後に処理するのが正しい方法かと思われます。
ヘッダーの内容は後から使うので、連想配列の中に入れておきます。
次にリクエストの結果を取得するのですが、サイズの指定され方によって基本的に3つの方法があります。まずはコード。

$content = '';
if($contentLength = $headers{'Content-Length'}) {
read(S, $content, $contentLength);
}
elsif ($headers{'Transfer-Encoding'} =~ /^chunked/) {
while(<S>){
last unless /^([0-9a-fA-F]+)/ && $chunkLength = hex($1);
read(S, $buf, $chunkLength);
$content .= $buf;
last unless <S> eq "\r\n";
}
}
Content-Lengthでサイズを指定
Content-Lengthは10進数の数値で指定されてきますので、このサイズ分だけ読み込んだら終了するという処理になります。
Transfer-Encodingがchunked
この場合は、それ以後に「16進数の数値CRLF、数値のサイズ分のデーターCRLF」というのを繰り返しますが、サイズが「0」の場合にすべてのデーターが送られてきたということで終了となります。
いずれの指定もなし
コネクションが切れるまでがデーターとみなして送られてくるのですが、HTTP/1.1ではKeepAliveがあるためあり得ないはず。HTTP/1.0以前の処理用なので実装なし。

このコードもエラーチェックなしなので、このままではどうかと思います。<S>やreadはエラーが起きるとundefを返すので、それをチェックしたほうがよいと思います。最後は、ソケットを閉じます。

close(S);
shutdown(S,2);

できる限り、close(),shutdown()は、connect()成功後、いかなる場合においても実行しておくべきです。
と、いうわけでこれで終わりです。ただし、1つ気になることがあって、それはタイムアウトをどうするのかということです。
思いついた対処の方法としては…。

何もしない
これをCGIから呼ぶとすれば、WebサーバーがCGIを終了させたり、ソケットがタイムアウトするので、それに期待する。あまりよいとは思えないけど。
setsockopt()でタイムアウトを設定
一応、そういうオプション(SO_RCVTIMEO)がある。デフォルトよりも短く設定することで対応する。ただし、システム依存する可能性あり。
監視用のプロセスを使って、ソケットを閉じる
connect()と同時に子プロセスをforkして、一定時間sleepさせる。もしsleepを抜けたら、ソケットをクローズ。そうすると、read()とかがエラーで読み込みのブロックから抜け出す(はず)。これもシステムに依存しそうな感じ。
ちなみにうまくいったときは、子プロセスとkillする。
監視用のプロセスを使って、シグナルを送る
これと同じようにconnect()と同時に子プロセスをforkして、一定時間sleepさせるが、抜けたら親プロセスにkill()でシグナル(INT)を送る。親プロセスはシグナルハンドラで適切な処理をして終了。ちょっと強引っぽいが。

ブログランキング ドット ネット
説明が詳しくない割には長いなぁ
ランクバーナー





お気に入りの記事を「いいね!」で応援しよう

最終更新日  2007/02/03 07:55:23 PM
コメント(0) | コメントを書く


【毎日開催】
15記事にいいね!で1ポイント
10秒滞在
いいね! -- / --
おめでとうございます!
ミッションを達成しました。
※「ポイントを獲得する」ボタンを押すと広告が表示されます。
x

PR

カレンダー

バックナンバー

2024/06
2024/05
2024/04
2024/03
2024/02

プロフィール

finky

finky


© Rakuten Group, Inc.
Design a Mobile Website
スマートフォン版を閲覧 | PC版を閲覧
Share by: