How Tomcat Works その1のおまけ

で、「勘で自作」をやってみてちょっとひっかかったのがHTTPを受けるinputStreamの読み方。

※この種のプリミティブなストリーム処理はReaderを使わない(InputStream.read()でbyteにコピー)のが普通です((本物のTomcatもbyteをコリコリ読んでいます))。エンコードが分からないからですね(つっても分からないときはUS-ASCII前提なんだけど)。US-ASCIIだとエンコーディングの効率も関係ないのですが、コードが見やすくなるので以下BufferedReaderを使って書いています。

BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "US-ASCII"));
while(reader.ready()) {
	String line = reader.readLine();
	buf.append(line + "\n");
}

ファイルを読むときはこんな感じで書きがちだし通常問題ないんだけど、reader.ready()のときにfalseだったらtrueになるまで待ってくれるわけではないので、ソケットの場合、しばしばストリームを読み込まないままスルーしてしまう。
Oracle Technology Network for Java Developers | Oracle Technology Network | Oracle

戻り値:
    次の read() が入力をブロックしないのが確実な場合は true、そうでない場合は false。false が返されても、次の読み込みが確実にブロックするというわけでない

とありますが、最初の読み込みはブロックしてもいいので、開始条件としてreader.ready() == trueの判定などせずにreadすればよいわけです。

do {
	String line = reader.readLine();
	buf.append(line + "\n");
} while(reader.ready());

でちゃんとHTTPヘッダの最後まで読み込んでくれます。reader.ready()は終了条件として読み込みの区切りをつけるには悪くないですね。(2008/4/14追記:この「区切り」はいったん読み込みの区切りを付ける、という意味です。reader.ready() == false でも読み込みが終了したとは限らないので、後で続きを読むなどの処理が必要です)
以下の書き方はどうでしょう。

while(true) {
	String line = reader.readLine();
	if (line == null) {
		break;
	}
	buf.append(line + "\n");
}

HTTPClientでレスポンスを読むときにこういうコーディングをすることがありますが、今回はreadLine()でnullが返らずに待ち続けてしまいます。HTTPのGETでは「\r\n」が連続したら(=CRLFのみの空行)でHTTPヘッダが終了するのですが、EOFが来ないからreadが待ってしまうんですね。無限ブロックを避けたければ前述のreader.ready()を使うほうがよいです。
ただ、今回はプロトコルのパースなので、「どこまで読むか」を意識すべき。HTTPヘッダの終了までなら空行を判定すればよいし、、HTTPのリクエストライン(=1行目)だけ読みたいのなら

String line = reader.readLine();

でよかったのでした。