- PR -

java.net.SocketでHTTP POSTのリクエストを受信したい

1
投稿者投稿内容
d@
会議室デビュー日: 2007/01/20
投稿数: 10
投稿日時: 2008-09-02 01:06
お世話になります。

HTTPプロトコル / ネットワークプログラミングの勉強のため、
Javaで簡単なHTTPサーバを自作しているのですが、
HTTPリクエスト電文を受信する箇所で行き詰ってしまいました。

具体的には、
電文のストリームを受信して文字列に変換する箇所で、
ストリームの終端を検出できず、無限ループが発生していまいます。

●処理フロー
(1)クライアントからのソケットを引数にしてHTTPリクエストを表現するクラスのコンストラクタ起動
(2)クライアントからのソケットからInputStreamを取得
(3)InputStreamを1文字ずつ読み込む
(4)InputStreamReader#read()の戻り値が-1になった場合(ストリーム終端の場合)、charの配列をStringに変換
(5)HTTPリクエスト文字列を解析して所定のレスポンスを生成
(6)クライアントのソケットからOutputStreamを取得
(7)所定のレスポンスをOutputStreamに流し込む

Javadocから予測して実装した処理フローは上記の通りなのですが、
Firefoxから適当なフォームデータをPOSTすると、受信はしているのですが、
(4)の箇所でいつまで待っても戻り値が-1にならず無限ループが発生し
最終的にOutOfMemoryErrorかArrayIndexOutOfBoundsExceptionで停止します。
また、無限ループ中にFirefox側で読み込み中止(「×」ボタン押下)すると、
入力ストリームが終端となり、HTTPリクエストを正常取得して
(5)の処理に移行するのですが、
Socketが閉じられるので当然(6)で落ちてしまいます。

というわけで、
・そもそも、処理・実装のフローの方向性は正しいでしょうか?
・HTTPリクエストの入力ストリームを文字列に変換する場合、終端を検出できる条件はありますか?

おそらく何か、Socket通信というものについて
変な思い込み or 勘違いをしているのかなと思うのですが・・・
考え方の誤っている箇所をご指摘していただけると助かります。

●実行環境
・JDK 1.5.13 (Windows)

●現状の実装コード
コード:
import ・・・

public class HttpRequest {

	private String requestHeader;
	
	private String requestBody;
	
	public HttpRequest(Socket socket) throws IOException{
		
		InputStream is = null;
		InputStreamReader isr = null;

		try{
			
			is = socket.getInputStream();
			isr = new InputStreamReader(is);
			
			String requestMessage = readHttpRequest(isr);
			divideHeaderAndBody(requestMessage);			
			
		}catch(IOException e){
			throw e;
		}finally{
			try{isr.close();}catch(Exception e){}
			try{is.close();}catch(Exception e){}
		}
	}
	
	private String readHttpRequest(InputStreamReader isr) throws IOException{
		
		// (3)〜(4)の処理
		char[] c = new char[10000000];	// 配列要素数は適当
		int i = 0;
		
		try{
			while((c[i] = (char)isr.read()) != (char)-1){
				i++;	
			}
			
			return new String(c);
			
		}catch(IOException e){
			throw e;
		}
		
	}
	
	private void divideHeaderAndBody(String requestMessage){
		・・・
	}
	
	public String getRequestHeader(){
		return this.requestHeader;
	}
	
	public String getRequestBody(){
		return this.requestBody;
	}
	
}

山本 裕介
ぬし
会議室デビュー日: 2003/05/22
投稿数: 2415
お住まい・勤務地: 恵比寿
投稿日時: 2008-09-02 02:33
リクエストの終わりを検出する方法はこちらに詳しく書いてあります。
お手本として Tomcat 等のソースを読むのもいいかもしれませんね。
http://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk/
スフレ
ぬし
会議室デビュー日: 2005/05/27
投稿数: 281
お住まい・勤務地: 東京
投稿日時: 2008-09-02 07:11
リクエスト内のContent-Lengthヘッダを見ることと、chunked-encodingを解釈する必要があります。けっこう面倒ではあるので、利用できるクラスがあるなら利用したほうが良いですね。

引用:

while((c[i] = (char)isr.read()) != (char)-1){


これだと、データに0xffが含まれるとそこで抜けてしまいませんか?
わたなべ
大ベテラン
会議室デビュー日: 2007/12/09
投稿数: 123
お住まい・勤務地: 札幌
投稿日時: 2008-09-02 09:40
「簡単な」Webサーバの実装ということであれば、プロトコルも単純にしてしまう方が良いと思います。
つまり、GETだけに対応して、リクエストラインの1行目だけを解釈すると。

厳密に実装していたらばかなりの難物です。
keep-aliveとかリクエストボディとかやってられないですが、勧告を読み込む覚悟があるならば試してみてもいいと思います。
d@
会議室デビュー日: 2007/01/20
投稿数: 10
投稿日時: 2008-09-03 01:04
RFCの仕様としては、
http://www.studyinghttp.net/cgi-bin/rfc.cgi?2616#Sec4.4
このあたりを参考に電文を解析する必要があるのかな、と思い、
「解析のため」InputStreamから電文を文字列で取り出そうとしたのですが・・・
うーん、やはりそう簡単にはいかないようですね・・・
Streamからbyteを取り出すroop内で全て解析してしまわなければならないのかな。

アドバイスしていただいた通り、
Tomcatのソースコードを解析してみることにします。
まだざっと眺めただけなのですが、
CoyoteReaderというBufferedReaderのサブクラスが電文を読んでいるようです。
このあたりに何か秘密があるのか???
時間はかかるかもしれませんが、
解析・実装に成功したらご報告します。

助言ありがとうございました。
さくらば
大ベテラン
会議室デビュー日: 2002/11/12
投稿数: 145
投稿日時: 2008-09-03 10:45
JDK の sample/nio/server ディレクトリに簡易 HTTP サーバーがありますよ。
NIO を使っていますけど、原理は同じだから参考になると思います。

少なくとも Tomcat のソースを読むよりは楽です。
nagise
ぬし
会議室デビュー日: 2006/05/19
投稿数: 1141
投稿日時: 2008-09-03 12:45
ストリームの終端が来ないのはkeep-aliveのせいでしょうねぇ。
ブラウザ側からリロードとかすると、同じストリームにもう一度リクエストが送られてくるんじゃないかな。

参考になりそうなURLを書いておきます。
http://www.studyinghttp.net/connections
1

スキルアップ/キャリアアップ(JOB@IT)