クッキーの発行と取得

今回は、クッキーを使用したサンプルサーブレットを作成していきます。
以前に作成したサンプルサーブレットを改良します。

まずは、作成していくサーブレットの内容を下記します。

適当な質問フォームを表示し、ユーザーに入力させます。
クッキーサンプル01

送信ボタンが押されたら、入力されたデータをサーブレット「RequestRecv」に渡します。

「RequestRecv」では、質問フォームで入力されたデータを表示させるとともに追加の質問フォームを表示するHTMLをHTTPレスポンスとして返します。その際、クッキーを発行し、入力データもクライアントへ渡します。

クッキーサンプル02

データ入力後、送信ボタンを押せば、入力されたデータがサーブレット「SampleGetCookie」に渡されるようにします。さらに、送信ボタンを押した際のHTTPリクエストの中に、クッキーにより、前回の入力データを格納し、「SampleGetCookie」に渡します。

「SampleGetCookie」は、質問フォームで入力されたデータを表示させるとともに、クッキーからデータを取得し、そのデータを表示するHTMLをHTTPレスポンスとして返します。

クッキーサンプル03

クッキー操作メソッド一覧

クッキーを操作するには、主に下記メソッドを使用します。

void addCookie(Cookie cookie)
クライアントにクッキーを保存する場合はHttpServletResponseインターフェースで定義されているaddCookieメソッドを使用します。なお、1つのドメインに対して保存できるCookieの数は20個まで、1つのCookieは名前と値を合わせて4096バイトまで、クライアントに保存できるCookieの数は全体で300個までに制限されます。

addCookieメソッドの引数にはjavax.servlet.http.Cookieオブジェクトを設定します。Cookieクラスの引数はString型の「名前」と「値」の2つです。コンストラクタの定義は以下になります。
Cookie(String name, String value)

void setMaxAge(int 有効期限)
クッキーの有効期限を秒単位で指定します。指定した有効期限を超えた場合、クライアントのWebブラウザの保存場所から破棄されます。負の値を指定するとWebブラウザが終了したときに削除されます。また0を指定すると直ちに削除されます。
Cookie[ ] getCookies()
クライアントに保存されているクッキーのテキストを取得するにはHttpServletRequestインターフェースに定義されているgetCookiesメソッドを使用します。戻り値にはCookieオブジェクトの配列が返されます。

クッキーの取得は保存したWebサーバーから読み出すことはできますが、他のWebサーバーが保存したクッキーを取得することはできません。

クライアントに保存したCookieオブジェクトを削除するには、コンストラクタで値を空にしたインスタンスを生成します。もしくは上記したsetMaxAge()メソッドの引数(有効期限)で0を設定します。

Cookie cookMemberName = new Cookie("MemberName", "");
cookMemberName.setMaxAge(0);

サンプルサーブレットのソースコード

以降に各サーブレットのソースコードを記載します。(冗長な部分は省略し、主要な部分のみ記載しています。)

まずは、フォームを作成します。フォーム(HTMLファイル)の詳細な作成方法はこちらを参照してください。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>サーブレットへのデータ送信</title>
</head>
<body>
<h1>サーブレットへのデータ送信</h1>
<form action="RequestRecv" method="GET">
<p>好きなゲームは?</p>
<input type="TEXT" name="Game" size=40>
<input type="submit" value="送信">
</form>
</body>
</html>

テキストボックスに入力後、送信ボタンをクリックした場合のアクションとして、RequestRecvサーブレットを呼び出してします。その際のリクエストの種類には「GET」を指定しています。

RequestRecvサーブレットのdoGet()メソッドには下記のように記述します。

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String gameName = request.getParameter("Game");
    
    Cookie cookGameTitle = new Cookie("GameTitle", gameName);
    cookGameTitle.setMaxAge(60*60*24);
    response.addCookie(cookGameTitle);
    
    PrintWriter out = response.getWriter();
    
    out.println("<html>");
    out.println("<head>");
    out.println("<title>あなたの回答</title>");
    out.println("</head>");
    out.println("<body>");
    out.println("<h1>あなたの回答</h1>");
    out.println("好きなゲームは" + gameName + "です。<br /><br />");
    
    out.println("では、そのゲームの好きなナンバリングは?<br />");
    out.println("<form action=\"SampleGetCookie\" method=\"POST\">");
    out.println("<input type=\"TEXT\" name=\"GameNumbering\" size=40>");
    out.println("<input type=submit value=\"次のサーブレットを起動\">");
    out.println("</form>");
    
    out.println("</body>");
    out.println("</html>");
}

getParameter()メソッドで取得したデータを、addCookie()メソッドでクッキーに保存します。その前の、setMaxAge()メソッドでは、有効期限を1日としています。

「次のサーブレットを起動」ボタンをクリックした場合のアクションとして、SampleGetCookieサーブレットを呼び出してします。その際のリクエストの種類には「POST」を指定しています。また、注意点として、println()メソッドでダブルクォーテーショ(“)を出力したい場合は、「\」(バックスラッシュもしくは環境によっては円マーク)でエスケープしてやる必要があります。

SampleGetCサーブレットのdoPost()メソッドには下記のように記述します。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    Cookie[] cookies = request.getCookies();
    String gameTitle = "";
    String gameNumbering = request.getParameter("GameNumbering");
    
    if (cookies != null) {
        for (Cookie cook : cookies) {
            if (cook.getName().equals("GameTitle")) {
                gameTitle = cook.getValue();
            }
        }
    }
    
    PrintWriter out = response.getWriter();
    
    out.println("<html>");
    out.println("<head>");
    out.println("<title>クッキーサンプル</title>");
    out.println("</head>");
    out.println("<body>");
    out.println("<h1>クッキーサンプル</h1>");
    out.println("好きなゲームは" + gameTitle + "です。<br />");
    out.println("ナンバリングは" + gameNumbering + "です。<br />");
    out.println("</body>");
    out.println("</html>");
}

getCookies()メソッドで、クッキーを取得し、さらに、getParameter()メソッドでデータを取得しています。そして、それらを表示するHTMLを生成しています。

不完全なサーブレット

上記サンプルは一見、動いたように見えますが、致命的な問題が存在しています。例えば、フォーム画面でテキストボックスに日本語を入力して「送信」ボタンをクリックしたら、HTTPステータス500が返り、サーバー内部エラーとなります。

サーバー内部では「java.lang.IllegalArgumentException: Control character in cookie value or attribute.」という例外が発生しています。

このエラーの発生箇所は下記になります。

Cookie cookGameTitle = new Cookie("GameTitle", gameName);
cookGameTitle.setMaxAge(60*60*24);
response.addCookie(cookGameTitle);

実は、Cookieの値として使用できる文字には制限があり、日本語をそのままクッキーに保存しようとしているため、エラーが発生しています。このエラーは回避するにはURLエンコードと呼ばれる処理を行う必要があります。

URLエンコードとデコードはそれぞれ、URLEncoder.encode()メソッド、URLDecoder.decode()メソッドで行えます。
URLEncoder.encodeは、テキストをURLエンコードしたものを返し、URLDecoder.decodeはエンコードされたテキストを元のテキストに戻したものを返します。
これらを利用し、テキストをエンコードしてクッキーに保存し、クッキーから取り出したら再びデコードして利用することで、日本語でも問題なくクッキーに保管しておけるようになります。

但し、今ではこの方法はあまり使われません。クッキーをそのまま使うにはかなり致命的な問題があり、推奨されないからです。では、どのように「ステートフル」を実現すれば良いのでしょう。次回の記事でクッキーの問題点と代替の方法について見ていきます。