先週はCookieを扱うためにJ2SE 5.0でCookieHandlerクラスが導入されたことを紹介しました。また、Java SE 6でCookieHandlerクラスを派生させた実装クラスjava.net.CookieManagerが提供されたことも述べました。

今週は、そのCookieManagerクラスを使って、実際にCookieを扱ってみましょう。

CookieManagerクラスはCookieを保持する際のポリシーをjava.net.CookiePolicyインタフェース、Cookieの保持をjava.net.CookieStoreインタフェースに委譲します。

CookiePolicyインタフェースには以下に示す3つの定数が定義されているので、通常はこれらのいずれかを使用します。

定数 説明
ACCEPT_ALL すべてのCookieを受け入れる
ACCEPT_NONE Cookieをまったく受け入れない
ACCEPT_ORIGINAL_SERVER 元のサーバからのCookieのみを受け入れる

CookieManagerクラスはデフォルトでACCEPT_ORIGINAL_SERVERを使用します。

また、CookieStoreインタフェースはデフォルトではメモリ内にCookieを保持するsun.net.www.protocol.http.InMemoryCookieStoreクラスが使用されます。

もし、ファイルなどにCookieをシリアライズしたい場合は、CookieStoreインタフェースを実装したクラスを自作する必要があります。

CookieStoreインタフェースはjava.net.HttpCookieクラスを使用してCookieを保持します。

この関係をUMLで示したのが、図1です。

Cookie関連のクラス図
図1 Cookie関連のクラス図

それでは、サンプルでCookiManagerクラスの動作を確かめてみましょう。

ここではサーブレットでサーバを、Java SEでクライアントを作成しました。

サンプルのソース TestServlet.java, CookieManagerSample.java

サーブレットはCookieをセットするだけの単純なものです。

public class TestServlet extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest request, 
                       HttpServletResponse response)
                           throws ServletException, IOException {
      response.setContentType("text/html;charset=UTF-8");

      Cookie cookie = new Cookie("user", "sakuraba");
      cookie.setDomain(".local");
      cookie.setPath("/cookietest/TestServlet");
      response.addCookie(cookie);

      cookie = new Cookie("id", "123456789");
      cookie.setDomain(".local");
      cookie.setPath("/cookietest/TestServlet");
      response.addCookie(cookie);
      
      PrintWriter out = response.getWriter();
      out.println("<html><body><p>Cookie Test</p></body></html>");
      out.close();
  }
}

次にクライアントです。

CookieManagerオブジェクトを生成したら、CookieHandlerクラスのsetDefaultメソッドをコールしてデフォルトの設定をします。デフォルト設定を行なうことでHttpURLConnectionクラスがCookieManagerクラスを使用できるようになることは、先週解説したとおりです。

        // CookieManager の生成
        CookieManager manager = new CookieManager();
 
        // CookieHandler のデフォルトに設定
        CookieHandler.setDefault(manager);

CookieManagerクラスのデフォルトコンストラクタでオブジェクトを生成すると、前述したようにCookiePolicyオブジェクトにACCEPT_ORIGINAL_SERVER、CookieStoreオブジェクトにInMemoryCookieStoreクラスを使用します。

コンストラクタの引数でCookiePolicyオブジェクトと、CookieStoreオブジェクトを指定することも可能です。また、CookiePolicyオブジェクトはsetCookiePolicyメソッドでも設定することができます。

たとえば、ACCEPT_ALLにしたい場合は次のようにします。

        // CookieManager の生成
        CookieManager manager = new CookieManager();
  
        // すべての Cookie を受けいれる
        manager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
 
        // CookieHandler のデフォルトに設定
        CookieHandler.setDefault(manager);

Cookieの受けいれや、送信はHttpURLConnectionクラスが行ないます。したがって、Cookieの受け入れ、送信を行なうための記述をする必要はありません。

後は、必要に応じて、CookieStoreオブジェクトからCookieを取得、設定します。

CookieManagerSampleクラスではHttpURLConnectionクラスでサーバと通信した後、受け入れたCookieを表示しています。

    public CookieManagerSample() {
        // CookieManager の生成
        CookieManager manager = new CookieManager();
 
        // CookieHandler のデフォルトに設定
        CookieHandler.setDefault(manager);
 
        // サーバに接続
        try {
            URL url = new URL(
                "http://localhost:8084/cookietest/TestServlet");
            HttpURLConnection http = 
                (HttpURLConnection)url.openConnection();
            int response = http.getResponseCode();
            System.out.println("Response: " + response);
        } catch (IOException ex) {
            // 例外処理
        }
 
        // Cookie の表示
        CookieStore store = manager.getCookieStore();
        List<HttpCookie> cookies = store.getCookies();
        for (int i = 0; i < cookies.size(); i++) {
            HttpCookie cookie = cookies.get(i);
            System.out.println("Cookie[" + i + "]: " + cookie);
        }
    }

CookieManagerオブジェクトからCookieStoreオブジェクトを取得するには、赤字で示したようにgetCookieStoreメソッドを使用します。CookieStoreオブジェクトからは、青字で示したようにgetCookiesメソッドでCookie、つまりHttpCookieオブジェクトを取得できます。

ここではCookieStoreオブジェクトが保持しているすべてのCookieを表示しています。

それでは、CookieManagerSampleを実行してみます。サーバは上記のソースにも記述されているように、localhostの8084番ポートで動作しており、サーブレットはcookietest/TestServletに配置しました。

CookieManagerSampleを実行した結果を以下に示します。

C:\>java  CookieManagerSample
Response: 200
Cookie[0]: id=123456789
Cookie[1]: user=sakuraba

正しくCookieが取得できていることが確認できます。

ここで、サーブレットでCookieの有効期限を変更することにより、Cookieの受け入れが変化するか試してみます。

サーブレットでCookieの有効期限はCookie#setMaxAgeメソッドで設定します。引数は秒数です。引数がマイナスの場合クライアントが終了するまで、また0の場合は保存されません。デフォルトでは-1が使用されます。

ここでは、setMaxAgeの引数を0にしてみます。

        Cookie cookie = new Cookie("user", "sakuraba");
        cookie.setMaxAge(0);
        response.addCookie(cookie);

実行結果は次のようになりました。

C:\>java  CookieManagerSample
Response: 200
Cookie[0]: id=123456789

userが保持されていないことが確認できます。

Cookieの更新

Cookieを利用したカウンタ
図2 Cookieを利用したカウンタ

Cookieを取得することができたので、次はクライアントからCookieを送信してみましょう。

サーバから取得したCookieを変更することなく、再びサーバに送信するのであれば、CookieManagerクラスがすべて行なってくれます。せっかくですから、Cookieを変更させてみましょう。

ここでは、カウンタをCookieを利用して作ってみます。サンプルを実行するとカウンタを表示するラベルと、カウンタを増加させるボタンが表示されます(図2)。カウンタはCookieで表すことにしました。

ボタンをクリックすると、カウンタの値を増加させます。そして、新しい値を保持したCookieを要求ヘッダに付加してサーバに送信します。サーバは受けとったCookieをそのまま応答ヘッダに付加してクライアントに戻します。

サンプルのソース TestServlet2.java, CookieManagerSample2.java

サーブレットのソースを以下に示します。

public class TestServlet2 extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest request, 
                       HttpServletResponse response)
                         throws ServletException, IOException {
    response.setContentType("text/html;charset=UTF-8");

    Cookie[] cookies = request.getCookies();
    boolean flag = false;
    if (cookies != null) {
      for (Cookie cookie: cookies) {
        response.addCookie(cookie);
        flag = true;
        }
    }
     
    if (!flag) {
      Cookie cookie = new Cookie("count", "0");
      cookie.setDomain(".local");
      cookie.setPath("/cookietest/TestServlet2");
      response.addCookie(cookie);
    }
    
    PrintWriter out = response.getWriter();
    out.println("<html><body><h1>Cookie Test</h1></body></html>");
    out.close();
  } 
}

次にクライアントです。GUIを構築する部分は本題には関係ないので、省略します。

ボタンをクリックされた時の処理を以下に示します。

        JButton button = new JButton("Increase Count");
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                // カウンタを増加させる
                increaseCounter();
                 
                // 通信を行い、値を更新する
                update();
            }
        });

カウンタを増加させているのがincreaeCounterメソッド、通信を行なって表示の更新をおこなうのがupdateメソッドです。

increaseCounterメソッドを次に示します。

    private void increaseCounter() {
        // Cookie の値を増加させる
        CookieStore store = manager.getCookieStore();
        List<HttpCookie> cookies = store.getCookies();
        for (HttpCookie cookie: cookies) {
            if (cookie.getName().equals("count")) {
                String countText = counter.getText();
                int count = Integer.parseInt(countText);
                count++;
                cookie.setValue(String.valueOf(count));
            }
        }
    }

CookieStoreオブジェクトは前述したとおり、CookieManager#getCookieStoreメソッドで取得できます。

後は、Cookieの中でcountという名前を持つものがあれば、値を取りだし、増加させます。

次にupdateメソッドです。

    private void update() { 
        // 通信は非同期に行なうため、SwingWorkerを使用する
        SwingWorker worker = new SwingWorker<Object, Object>() {
            @Override
            public Object doInBackground() {
                // 通信処理
                communicate();
                return null;
            }
 
            @Override
            protected void done() {
                // 表示を更新
                updateCounter();
            }
        };
        worker.execute();
    }

通信には時間がかかるため、SwingWorkerクラスを使用して非同期に行ないます。SwingWorkerクラスに関してはSwingでマルチスレッド - SwingWorker その1その2をご覧ください。

通信を行なっているのがcommunicateメソッド、表示の更新がupdateCounterメソッドです。communicateメソッドはHttpURLConnectionクラスを使用して通信を行なっているだけです。

updateCounterメソッドはincreaseCounterメソッドと同じような処理を行ないます。

    private void updateCounter() {
        // Cookie の値をラベルに表示する 
        CookieStore store = manager.getCookieStore();
        List<HttpCookie> cookies = store.getCookies();
        for (HttpCookie cookie: cookies) {
            if (cookie.getName().equals("count")) {
                String count = cookie.getValue();
                counter.setText(count);
                return;
            }
        }
    }

変数counterはカウンタを表示するJLabelオブジェクトを表しています。

実行結果
図3 実行結果

それでは、さっそく実行してみましょう。

ボタンがクリックされると、カウンタの値も更新されることが確認できるはずです。図3は5回クリックした後の結果を示しています。

今週はCookieManagerクラスがデフォルトで使用するInMemoryCookieStoreクラスを使ってCookieを保持させました。

しかし、メモリ内にCookieを保持するため、アプリケーションを終了させてしまうと、Cookieも消滅してしまいます。

そこで、来週はCookieStoreインタフェースを実装するクラスを作成して、Cookieをファイルにシリアライズしてみます。お楽しみに。

 

著者紹介 櫻庭祐一

横河電機 ネットワーク開発センタ所属。Java in the Box 主筆

今月の櫻庭

ここ数年、夏になると沖縄にいっています。去年までは雨に降られたことがなかったのですが、今年はとうとう台風に遭遇してしまいました。

沖縄のJavaコミュニティであるJava Kücheの一周年記念講演会で講演するため、先月の12日に沖縄に向かいました。ところが、同じく沖縄に来ていたのが台風4号。せっかくの、講演会も中止の憂き目にあってしまいました。

それでも、懇親会だけは開かれました。居酒屋にプロジェクターを持ち込み、講演の内容についてざっくばらんにお話しすることができました。

それにしても、沖縄の台風はすごいとは聞いていましたが、本当にすごかったです。

泊まっていたホテルは建物全体が風で揺れ、窓のサッシからも雨がしみこんできます。倒れているヤシの木も多数。

沖縄でも、今回のような大型台風の直撃は久しぶりだそうです。

なお、中止になってしまった講演会ですが、 仕切り直して8/8に講演会が行なわれました。残念ながら櫻庭は参加できなかったのですが、盛況であったようです。

Presentiment of Typhoon, Okinawa 台風, 沖縄 台風, 沖縄
台風の予感が... 砂浜は海に飲みこまれました 視界はほとんどありません
Java Kuche 懇親会, おもろ殿内, 沖縄 Java Kuche 懇親会, おもろ殿内, 沖縄
こんな感じで話をしました。話しているのは鈴木雄介さん ゴーヤチャンプルを食べながら