T

HTTP通信のきほんてきなおはなし

サーバ間通信にHTTPを使うことはそんなに珍しくないので、何度も実装したことはあるのだけれど、その際にHTTPのバージョン指定に注意した方がいいよ、という話は見かけないのでちょっと書いておく。

HTTP通信のレイヤ7のやり取り、というとなんだか難しいので、よく実装するようなアプリケーションから見たHTTP通信のリクエストの中身は、まあだいたいこんな感じになっている:

GET /users/1 HTTP/1.1
Host: example.com

相手方にこれを送ってレスポンスを得るなら、愚直にPHPで実装すると

$host    = 'example.com';
$port    = 80;
$timeout = 30;
$content = '';
$request = "GET /users/1 HTTP/1.1\r\n";
$request .= "Host:" . $host . "\r\n";
$request .= "Connection: Close\r\n";
$request .= "\r\n";

$fp = @fsockopen($host, $port, $error, $errorno, $timeout);
if($fp){
  fputs($fp, $request);
  do{
    $data = fgets($fp);
    if(strlen($data) == 0){break;}
    $content .= $data;
  }while(true);
  fclose($fp);
}else{
  //なんかエラー処理
}

てなもんだろう。

でも、このコードにはとっても問題のある間違いが含まれている。たぶん、実行すると、通信相手先の状況にもよるが、一番困るのはループから出てこなくて制御が戻ってこない。

そこで、あれやこれやとデバッグが始まるのだが、User-Agentを指定してみたり、Content-Lengthを追加してみたり、悪戦苦闘の果てに、さんざんGoogleで調べて、遂に問題になっている箇所を(2つ)発見することになる。すなわち、

$request = "GET /users/1 HTTP/1.1\r\n";

ここを

$request = "GET /users/1 HTTP/1.0\r\n";

に直すか、

if(strlen($data) == 0){break;}

ここを

if(substr($data, -9) == "\r\n\r\n0\r\n\r\n"){break;}

にする。ようするに、何をやっているかというと、上の例ではHTTP/1.1で通信してしまうとKeep-Aliveが有効になってしまうので、相手方の仕様によっては通信したいやり取りが完了してもコネクションが切断されず、タイムアウトするまで制御が戻ってこない。そんな質の悪い環境を見つけたので今日こうしてこんなことを書いているわけですが、そこで1.0で通信することでKeep-Aliveを無効にしてしまうとうまくいった。泣けた。もうひとつの方は、単純にレスポンスの終わりを検知して強制的にループを抜けるようにしている。

と、まあ当たり前の話でしかないのだが、どこにもあんまり記述がないので、メモしておく。

Posted by on 11月 15, 2010 in PHP

コメントを残す