HTTPキープアライブとも呼ばれるHTTPの持続接続またはHTTPの接続再利用では、それぞれの要求と応答の単一のペアについて新しい接続を開く場合とは対照的に、HTTP要求/応答の複数のペアを送信または受信する際に同一のTCP接続を使用しています。持続接続を使用することは、HTTPパフォーマンスを向上させるうえで非常に重要な点です。
持続接続を使用するメリットには、次のものがあります。
SSL/TLS経由でHTTPSまたはHTTPを使用することにより、メリットがさらに明確となります。そのような状況では、持続接続により、TCP接続の初期設定に加えて、セキュリティの関連付けを確立するための損失の大きいSSL/TLSハンドシェークの回数が減少します。
HTTP/1.1では、どの接続においても持続接続がデフォルトの動作となります。つまり、指示がないかぎり、サーバーからのエラー応答のあとであっても、クライアントはサーバーが持続接続を保持するものとみなします。ただし、プロトコルは、クライアントとサーバーがTCP接続を閉じることを信号で知らせるようにする手段を提供します。
TCPはその性質上、ストリーム・ベースのプロトコルであるため、既存の接続を再利用するには、HTTPプロトコルに前の応答の最後と次の応答の始めを示す手法を設定する必要があります。つまり、これは、接続中に送られるすべてのメッセージは、メッセージの長さ(接続終了時に定義されない)をそれぞれ独自に定義する必要があるということです。自己の境界設定は、Content-Lengthヘッダーを設定するか、エンティティ本体をエンコードしてチャンク単位に区切って転送することにより達成されます。なお各チャンクは特定のサイズで開始され、応答本体は指定の最後のチャンクで終了します。
持続接続は1つのトランスポート・リンクのみに適用されるため、プロキシ・サーバーで、そのクライアントおよびオリジン・サーバー(または別のプロキシ・サーバーに対して)との永続的/非永続的な接続を個別に正しく合図することが重要です。HTTPクライアントまたはサーバーの観点から見ると、持続接続に関するかぎり、プロキシ・サーバーの存在の有無は透過的となります。
JDKでは、HTTP/1.1とHTTP/1.0の両方の持続接続をサポートします。
アプリケーションで応答本体の読取りが終了したとき、またはURLConnection.getInputStream()
により返されるInputStream
のclose()がアプリケーションによって呼び出されるときは、JDKのHTTPプロトコル・ハンドラでは、接続をクリーンアップしようとし、成功した場合は、将来のHTTP要求による再利用を見越してその接続を接続キャッシュに送ります。
HTTPキープアライブのサポートは透過的に行われます。ただし、その制御は、システム・プロパティhttp.keepAlive
、http.maxConnections
のほか、HTTP/1.1で規定されている要求ヘッダーや応答ヘッダーによって行うことができます。
キープアライブの動作を制御するシステム・プロパティは次のとおりです。
http.keepAlive=<boolean>
default: true
キープアライブ(持続)接続をサポートすべきかどうかを示します。
http.maxConnections=<int>
default: 5
任意の時点で接続先ごとにキープアライブを実行できる最大接続数を示します。
接続の持続性に影響を与えるHTTPヘッダーは次のとおりです。
Connection: close
要求ヘッダーまたは応答ヘッダーのどちらかのフィールドで値「close」を使用して「Connection」ヘッダーの指定を行なった場合、現在の要求/応答が完了したあとは、接続が「持続的」だとみなされるべきではないことを示しています。
現在の実装では、応答本体はバッファに格納されません。これは、接続の再利用を目的として、アプリケーションで応答本体の読取りを終了する必要があるか、またはclose()を呼び出して応答本体の残りを破棄する必要性があることを意味しています。さらに、現在の実装では、接続がクリーンアップされる場合にブロック単位の読取りを試みません。これは、応答本体の全体が利用できない場合は接続が再利用されないことを意味します。
アプリケーションはHTTP 400または500応答を検出したときに、IOException
を無視して、別のHTTP要求を発行することがあります。この場合、ベースとなるTCP接続はキープアライブされません。これは、応答本体が、消費されるように引き続き存在し続けるからです。このため、ソケット接続はクリアされず、再利用不可能となります。アプリケーションでは、IOException
をキャッチしたらHttpURLConnection.getErrorStream()
を呼び出して応答本体を読み取り、その後にストリームを閉じる必要があります。ただし、既存のアプリケーションによってはこの処理を行わないものもあります。このため、このようなアプリケーションは持続接続からメリットが得られません。この問題に対処するための回避方法を導入しました。
この回避方法では、応答が400以上の場合に制限時間付きで一定量の応答本体をバッファに入れます。このようにすることで、配下のソケット接続を再利用のために解放できます。この背景となっている根拠は、サーバーが400以上のエラーで応答するとき(クライアント・エラーまたはサーバー・エラー、一例は「404: File Not Found」エラー)、サーバーは、通常、コンタクト先と復元に必要な処理を通知するために小規模な応答本体を送信します。
サーバーからのエラー応答後に 接続のクリーンアップに役立つよう、Oracle JDKの実装に固有の新しいプロパティがいくつか導入されています。
主なプロパティは次のとおりです。
sun.net.http.errorstream.enableBuffering=<boolean>
default: false
上記のシステム・プロパティにtrue (デフォルトはfalse)を設定している状態で、応答コードが400以上の場合、HTTPハンドラでは、応答本体のバッファリングを試みます。その結果、配下のソケット接続が解放されて、再利用できるになります。したがって、アプリケーションでgetErrorStream()
を呼び出さずに応答本体を読み取り、そのあとにclose()を呼び出す場合でも、配下のソケット接続はキープアライブが有効になり再利用できるようになります。
次の2つのシステム・プロパティで、エラー・ストリームのバッファリングの動作について詳細な制御を行います。
sun.net.http.errorstream.timeout=<int> in millisecond
default: 300 millisecond
sun.net.http.errorstream.bufferSize=<int> in bytes
default: 4096 bytes
応答本体を無視して接続を破棄しないでください。TCP接続はそのためにアイドル状態になる場合があります。この接続は参照されなくなったときに、ガベージ・コレクトする必要があります。
getInputStream()
が正常に返される場合は、応答本体の全体を読み取ります。
HttpURLConnection
からgetInputStream()
を呼び出してIOException
が発生した場合は、例外をキャッチし、getErrorStream()
を呼び出して応答本体を取得します(応答本体が存在する場合)。
応答の内容自体を必要としない場合でも、応答本体を読み取ると接続がクリーンアップされます。ただし、応答本体が長く、先頭部分を確認したあとに残り部分が必要なくなった場合はInputStreamを閉じることができます。ただし、データがさらに続いていることを認識しておく必要があります。そのために、接続はクリアされず、再利用できない場合があります。
次に、上記の推奨事項に準拠したコード例を示します。
try { URL a = new URL(args[0]); URLConnection urlc = a.openConnection(); is = conn.getInputStream(); int ret = 0; while ((ret = is.read(buf)) > 0) { processBuf(buf); } // close the inputstream is.close(); } catch (IOException e) { try { respCode = ((HttpURLConnection)conn).getResponseCode(); es = ((HttpURLConnection)conn).getErrorStream(); int ret = 0; // read the response body while ((ret = es.read(buf)) > 0) { processBuf(buf); } // close the errorstream es.close(); } catch(IOException ex) { // deal with the exception } }
応答本体を必要としないことが前もってわかっている場合は、GET要求の代わりにHEAD要求を発行するようにしてください。たとえば、Webリソースのメタ情報のみが必要な場合や、有効期間、アクセス可能性、最新の変更などを確認する場合などです。次にコード例を示します。
URL a = new URL(args[0]); URLConnection urlc = a.openConnection(); HttpURLConnection httpc = (HttpURLConnection)urlc; // only interested in the length of the resource httpc.setRequestMethod("HEAD"); int len = httpc.getContentLength();
http.KeepAlive.remainingData
システム・プロパティを使用します。