Menu Close

48.5.2. タイムアウトおよびタイムアウトハンドラー

概要

非同期処理モデルでは、REST 呼び出しにタイムアウトを課すサポートも提供します。デフォルトでは、タイムアウトにより、HTTP エラー応答がクライアントに返信されます。しかし、タイムアウトハンドラーコールバックを登録するオプションもあります。これにより、タイムアウトイベントへの応答をカスタマイズできます。

ハンドラーなしでタイムアウトを設定する例

タイムアウトハンドラーを指定せずに単純な呼び出しタイムアウトを定義するには、以下の例のように AsyncResponse オブジェクトで setTimeout メソッドを呼び出します。

// Java
// Java
...
import java.util.concurrent.TimeUnit;
...
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.container.TimeoutHandler;

@Path("/bookstore")
public class BookContinuationStore {
    ...
    @GET
    @Path("/books/defaulttimeout")
    public void getBookDescriptionWithTimeout(@Suspended AsyncResponse async) {
        async.setTimeout(2000, TimeUnit.MILLISECONDS);
        // Optionally, send request to executor queue for processing
        // ...
    }
    ...
}

java.util.concurrent.TimeUnit クラスの任意の時間単位を使用して、タイムアウト値を指定できることに注意してください。前述の例では、エグゼキュータースレッドプールにリクエストを送信するためのコードは示していません。タイムアウトの動作をテストするだけであれば、リソースメソッド本文に async.SetTimeout への呼び出しのみを含めると、タイムアウトは呼び出しごとにトリガーされます。

AsyncResponse.NO_TIMEOUT の値は無限のタイムアウトを表します。

デフォルトのタイムアウト動作

デフォルトでは、呼び出しタイムアウトがトリガーされると、JAX-RS ランタイムが ServiceUnavailableException 例外を発生させ、ステータス 503 で HTTP エラーの応答を返します。

TimeoutHandler インターフェース

タイムアウトの動作をカスタマイズする場合は、TimeoutHandler インターフェースを実装してタイムアウトハンドラーを定義する必要があります。

// Java
package javax.ws.rs.container;

public interface TimeoutHandler {
    public void handleTimeout(AsyncResponse asyncResponse);
}

実装クラスで handleTimeout メソッドを上書きする場合は、タイムアウトを処理する次の方法のいずれかを選択できます。

  • asyncResponse.cancel メソッドを呼び出すことで、レスポンスを取り消します。
  • レスポンス値で asyncResponse.resume メソッドを呼び出すことで、レスポンスを送信します。
  • asyncResponse.setTimeout メソッドを呼び出すことで、待機期間を延長します。たとえば、10 秒以上待機するには、asyncResponse.setTimeout(10, TimeUnit.SECONDS)を呼び出すことができます。

ハンドラーでタイムアウトを設定する例

タイムアウトハンドラーで呼び出しタイムアウトを定義するには、以下の例のように AsyncResponse オブジェクトの setTimeout メソッドと setTimeoutHandler メソッドの両方を呼び出します。

// Java
...
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.container.TimeoutHandler;

@Path("/bookstore")
public class BookContinuationStore {
    ...
    @GET
    @Path("/books/cancel")
    public void getBookDescriptionWithCancel(@PathParam("id") String id,
                                             @Suspended AsyncResponse async) {
        async.setTimeout(2000, TimeUnit.MILLISECONDS);
        async.setTimeoutHandler(new CancelTimeoutHandlerImpl());
        // Optionally, send request to executor queue for processing
        // ...
    }
    ...
}

この例では、呼び出しタイムアウトを処理するために CancelTimeoutHandlerImpl タイムアウトハンドラーのインスタンスを登録します。

タイムアウトハンドラーによる応答のキャンセル

CancelTimeoutHandlerImpl タイムアウトハンドラーは以下のように定義されます。

// Java
...
import javax.ws.rs.container.AsyncResponse;
...
import javax.ws.rs.container.TimeoutHandler;

@Path("/bookstore")
public class BookContinuationStore {
    ...
    private class CancelTimeoutHandlerImpl implements TimeoutHandler {

        @Override
        public void handleTimeout(AsyncResponse asyncResponse) {
            asyncResponse.cancel();
        }

    }
    ...
}

AsyncResponse オブジェクト上で cancel を呼び出す効果は、クライアントに HTTP 503 (Service unavailable) エラーの応答を送信することです。任意で、cancel メソッド (int または java.util.Date の値) の引数を指定できます。これは応答メッセージで Retry-After: HTTP ヘッダーを設定するために使用されます。ただし、クライアントは多くの場合で Retry-After: ヘッダーを無視します。

Runnable インスタンスでのキャンセルされたレスポンスの処理

エグゼキュータースレッドプールで処理のためにキューに格納された Runnable インスタンスとして、一時停止されたリクエストをカプセル化した場合、スレッドプールがリクエストを処理するまでに AsyncResponse がキャンセルされる可能性があります。このため、Runnable インスタンスにコードを追加する必要があります。これにより、キャンセルされた AsyncResponse オブジェクトに対応できるようになります。以下に例を示します。

// Java
...
@Path("/bookstore")
public class BookContinuationStore {
    ...
    private void sendRequestToThreadPool(final String id, final AsyncResponse response) {

        executor.execute(new Runnable() {
            public void run() {
                if ( !response.isCancelled() ) {
                    // Process the suspended request ...
                    // ...
                }
            }
        });

    }
    ...
}