HandlerベースのAPIで非同期HTTP通信を試す

前回は、非同期HTTP通信を行うためのJava用クライアントライブラリ「AsyncHttpClient」を紹介した。AsyncHttpClientには、非同期HTTP通信を行うための方法として2種類のAPIが用意されており、前回解説したのはそのうちのFutureベースのAPIの使い方である。今回は、もうひとつの方法であるHandlerベースのAPIについて紹介する。

HandlerベースのAPIでも、最初にAsyncHttpClientのインスタンスを生成し、prepareXXX()メソッドを用いてリクエストを生成する部分までは同様である。FutureベースのAPIの場合はこのprepareXXX()メソッドの戻り値に対して引数なしのexecute()メソッドを実行したが、HandlerベースのAPIでは、execute()の引数としてレスポンスを処理するためのハンドラオブジェクトを指定する。たとえば、ハンドラを定義したクラスがMyAsyncCompletionHandlerだとした場合は次のようになる。

リスト1

AsyncHttpClient httpClient = new AsyncHttpClient();
String url = "http://www.mycom.co.jp";
httpClient.prepareGet(url).execute(new MyAsyncCompletionHandler());

ハンドラの定義はcom.ning.http.client.AsyncHandler<T>インタフェースを用いて行う。このインタフェースは非同期HTTP通信のレスポンスを処理するための各種メソッドを宣言したものである。たとえばヘッダを受信した際に呼び出されるonHeadersReceived()や、処理が完了した際に呼び出されるonCompleted()などのメソッドがある。型パラメータTは、execute()メソッドが返すFutureオブジェクトに対してget()メソッドを呼び出した際の戻り値の型となる(後述)。

AsyncHttpClientには、AsyncHandlerの実装としてAsyncCompletionHandler<T>という抽象クラスが用意されている。このクラスの抽象メソッドはonCompleted(Response)で、このメソッドは処理が完了した際にResponseオブジェクトを伴って呼び出される。そして、Future.get()の戻り値となるオブジェクトを返す。すなわち、Future.get()の実行はこのメソッドの処理が完了するまでブロックされるということである。なお、その他の主要なメソッドは、デフォルトではステータスを返すだけで何も行わない。

以下に、AsyncCompletionHandlerの実装例を示す。onCompleted()はレスポンス元のURIを表示し、Responseオブジェクトそのものを返す。

リスト2

class MyAsyncCompletionHandler extends AsyncCompletionHandler<Response> {
    @Override
    public Response onCompleted(Response response) {
    try {
        // レスポンスの取得が完了したら、URLを表示
        System.out.println(response.getUri());
    } catch (MalformedURLException ex) {
        ex.printStackTrace();
    }
    return response;
    }

    @Override
    public void onThrowable(Throwable t) {
    t.printStackTrace();
    }
}

以下は、このMyAsyncCompletionHandlerを使って非同期通信行うプログラムの例である。

リスト3

public class AsyncHttpSample2 {
    public void connectWithHandler(String[] urls) {
        // AsyncHttpClientオブジェクトを生成
        AsyncHttpClient httpClient = new AsyncHttpClient();

        for (String url: urls) {
            try {
                // GETリクエストを送信
                httpClient.prepareGet(url).execute(new MyAsyncCompletionHandler());
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        AsyncHttpSample2 asyncHttp = new AsyncHttpSample2();
        String[] urls = {"http://www.google.co.jp",
                        "http://www.ongs.co.jp",
                        "http://www.mycom.co.jp"};
        asyncHttp.connectWithHandler(urls);
    }
}

このプログラムを実行すると次のように表示される。3つのURLに対してリクエストを送っているが、非同期に通信するので前のリクエストの返事を待たずに次のリクエストを送り、レスポンスが完了したらその都度ハンドラが呼び出される。したがって結果のURIは呼び出しの順番に関係なく表示されていることがわかる。

プロンプト1

http://www.ongs.co.jp/
http://www.mycom.co.jp/
http://www.google.co.jp/

HandlerとFutureを併用する

Handlerを利用した非同期通信の場合でも、execute()メソッドは戻り値としてFutureオブジェクトを返す。これを利用して、HandlerとFutureを併用した非同期処理もできるようになっている。その場合、ハンドラを定義したクラスではonComplete()メソッドの戻り値としてFutureオブジェクトのget()メソッドが返す値を設定する。次に示すMyAsyncCompletionHandlerクラスのような具合である。

この例の場合、onComplete()はResponseオブジェクトからステータスを表す文字列を取得して返すようにしてある。したがって型パラメータはStringとなる。onHeadersReceived()はヘッダを受信した際に呼び出されるメソッドで、ここではヘッダの値を列挙するように実装した。戻り値としてはSTATE.CONTINUEを返しているが、Bodyの内容を必要としない場合にはSTATE.ABORTを返すようにすればよい。

リスト4

class MyAsyncCompletionHandler extends AsyncCompletionHandler<String> {
    @Override
    public String onCompleted(Response response) {
    // レスポンスの取得が完了したら、ステータスを返す
    return response.getStatusText();
    }

    @Override
    public STATE onHeadersReceived(HttpResponseHeaders headers) throws Exception {
    // ヘッダを表示
    FluentCaseInsensitiveStringsMap headersMap = headers.getHeaders();
    Set<String> keySet = headersMap.keySet();
    for (String key: keySet) {
        System.out.println(key + " = " + headersMap.get(key));
    }
    return STATE.CONTINUE;
    }

    @Override
    public void onThrowable(Throwable t) {
    t.printStackTrace();
    }
}

このMyAsyncCompletionHandlerを利用して非同期HTTP通信を行う例を以下に示す。execute()の戻り値としてFuture<String>を受け取り、次の行でfuture.get()を呼び出して結果を表示している。このuture.get()はonCompleted()が完了するのを待ってその結果を返すので、ここでは通信完了時のステータスが表示されるはずである。

リスト5

public class AsyncHttpSample3 {
    public void connectWithFutureAndHandler(String url) {
        // AsyncHttpClientオブジェクトを生成
        AsyncHttpClient httpClient = new AsyncHttpClient();

        try {
            // GETリクエストを送信
            Future<String> future = httpClient.prepareGet(url).execute(new MyAsyncCompletionHandler());

            // 結果を表示
            System.out.println(future.get());

            // 切断
            httpClient.close();
        } catch (InterruptedException ex) {
           ex.printStackTrace();
        } catch (ExecutionException ex) {
           ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public static void main(String[] args) {
        AsyncHttpSample3 asyncHttp = new AsyncHttpSample3();
        String url = "http://www.mycom.co.jp";
        asyncHttp.connectWithFutureAndHandler(url);
    }
}

このプログラムの実行結果は次のようになる。ヘッダの値が列挙され、最後にステータスとして「OK」が表示されていることが確認できる。

プロンプト2

Date = [Tue, 04 Jan 2011 14:35:34 GMT]
Vary = [Accept-Encoding, User-Agent]
Transfer-Encoding = [chunked]
Expires = [Tue, 04 Jan 2011 15:35:49 GMT]
Accept-Ranges = [bytes]
Content-Type = [text/html]
Connection = [close]
Server = [Apache]
Cache-Control = [max-age=3600]
OK