Javaのネットワーク関連のお話

[next : ExceptionのStacktTraceの文字列を取得したいとき〜] [prev : SwingTips] [up : 技術日記]

URLConnectionとHttpリクエストのタイムアウト

javaのApplicationやAppletでサーバ通信を行う場合にjava.net.URLConnectionクラスを使用しますが、以下の点に注意が必要です。
sunのjavaVMではサーバからタイムアウトが帰ってくると無条件にリトライしてしまう。
リトライしてもさらにタイムアウトになるとクライアント側がSocketExceptionをthrowする。
上記の現象を確認した環境は以下のとおりです。
クライアント
OS: Windows2000 ServicePack2
JDK: 1.3.1_01
サーバ
OS: SunOS 5.8 (Solaris8)
Webサーバ: IBM HTTP Server 1.3.12.3
APサーバ: WebSphere 3.5

現象の詳細

サーバにデータを送信し結果を取得するJavaアプリケーションにおいて、膨大なデータの送信テストにて発生。
シリアライズしたオブジェクトをサーバに投げ、サーバは受けとったオブジェクトの内容をDBに書き込む。
データ通信自体はさほど時間がかからないが、サーバでの処理に時間がかかってしまう場合に問題が発生する。
5分経過してもサーバからのレスポンスが得られないとクライアント側通信クラス(sun.net.www.http.HttpClientか?)がリトライを実行する。
10分経過してもサーバからレスポンスが得られないとsun.net.www.http.HttpClientクラスがSocketExceptionをthrowする。
通信部分のソースとエラーログは以下の通り。
通信部ソース(一部簡略化しています。)
    /**
     * 通信メソッド。
     * 引数でわたされたオブジェクトをサーバへ送信し、サーバからのレスポンスを返却する。
     */
    public Object foo( String serviceName,
                       Serializable data )
        throws Exception {
        
        URLConnection connection = null;

        connection = new URL(url).openConnection();
        
        connection.setDoOutput( true );
        connection.setDoInput( true );
        connection.setUseCaches( false );

        // サーバへオブジェクトの送信        
        ObjectOutputStream output = null;
        try{
            output = new ObjectOutputStream(conn.getOutputStream());
            output.writeObject( serviceName );
            output.writeObject( data );
            output.flush();
        } finally {
            try {
                if(output != null)
                    output.close();
            } catch (IOException ignore) {}
        }

        // サーバからオブジェクトを受け取る。
        ObjectInputStream input   = null;
        Object result             = null;
        try{        
            input  = new ObjectInputStream( conn.getInputStream() );
            result = ( Object )input.readObject();
        } finally {
            try {
                if(input != null)
                    input.close();
            } catch (IOException ignore) {}
        }
        //受け取ったオブジェクトを送信
        return result ;
        
    }
	    

エラーログ
java.net.SocketException: Unexpected end of file from server
	at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:708)
	at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:613)
	at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:706)
	at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:613)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:506)
---以下略---
言い換えればクライアントが待ちきれなくなってエラーを吐いた。状態。
※サーバロジックは正常に処理が実行され、データはすべてDBに格納されていた。
ここで問題になるのは2点。
・サーバ側処理が正常に終了しても、クライアントはエラーと認識してしまう場合がある。
・リトライによって、まったく同じサーバ処理が実行されてしまう場合がある。
なかなか厄介ですな。

傾向と対策

クライアント側が5分でリトライをかけてしまう理由は以下の2点。

・5分でサーバからタイムアウトが通知された。
・sunのVMの実装で一度目のタイムアウト時にはリトライしてしまうようだ。なお、プロパティなどによってこの動きを制御することは出来ないらしい。

よってこの問題の対策としては、

1.サーバのタイムアウト時間を長めに設定してあげる。
2.リトライの制御はjavaの通信部分を自作する。

の二つが必要と思われます。
2については次回に持ち越し。ここでは1の対象方法を記述する。
サーバのタイムアウト時間はWebサーバの設定により調節できる。
apacheやIBM HTTP Serverの場合、httpd.confにタイムアウト時間を設定記述する箇所があります。
デフォルト設定は300秒。(つまり5分)。
httpd.confの編集例(ここではタイムアウト時間を30分に変更しています。)
# Timeout: The number of seconds before receives and sends time out

#Timeout 300  デフォルトは300秒。これを1800秒に変更しました。nikunoki
Timeout 1800

っつーか、こんな小細工するよりサーバにそんな重たい処理をさせないような設計をしろと言いたい。

#これJavaTipsなんだろうか・・・

Last modified: Fri Jan 17 13:13:16 LMT 2003