サーブレット&JSPの基本|セッションスコープのしくみ

ブラウザごとに情報を見分ける。セッションIDとクッキーでセッションスコープの裏側を理解しよう

セッションスコープを使うと、リクエストをまたいでインスタンスを利用できます。

たとえば、研修コース申込機能であれば、入力画面で入力された申込情報をセッションスコープに保存し、確認画面や完了処理でも同じインスタンスを利用できます。

リクエストスコープでは、レスポンスが返ると保存したインスタンスが消えてしまいます。そのため、次のリクエストでは取得できません。

一方、セッションスコープでは、レスポンス後もインスタンスを残せます。これにより、複数の画面をまたいで同じ情報を扱えるようになります。

では、アプリケーションサーバは、どのブラウザにどのセッションスコープを対応させているのでしょうか。

同じWebアプリケーションに複数の利用者がアクセスしていても、Aさんの情報はAさんだけに見え、Bさんの情報はBさんだけに見えます。ショッピングカートの中身やログイン中のユーザー情報が、他の利用者と混ざらないのは、この仕組みがあるためです。

この記事では、セッションスコープの裏側で使われているセッションID、クッキー、セッションタイムアウト、removeAttributeによる削除、invalidateによるセッション破棄について解説します。

セッションスコープはブラウザごとに用意される

セッションスコープの正体は、HttpSessionインスタンスです。

このHttpSessionインスタンスは、利用者のブラウザごとに作成されます。

より具体的には、そのブラウザに対して最初にrequest.getSessionが実行されたタイミングで、アプリケーションサーバがHttpSessionインスタンスを作成します。

このとき、アプリケーションサーバはセッションIDという識別番号も発行します。

セッション作成時に行われること

項目内容
HttpSessionインスタンスの作成ブラウザごとのセッションスコープを用意する
セッションIDの発行ブラウザを区別するためのIDを作成する
セッションIDの設定HttpSessionインスタンスとブラウザに対応づける

セッションIDは、ブラウザとサーバの間で、このブラウザはどのセッションスコープを使うのかを判断するための目印です。

この目印があることで、利用者ごとに別々のセッションスコープを使えるようになります。

セッションIDで利用者を区別する

セッションIDは、利用者のブラウザを区別するためのIDです。

たとえば、同じWebアプリケーションに青山さんと山田さんがアクセスしているとします。

青山さんのブラウザにはID:00001が設定され、山田さんのブラウザにはID:00002が設定されます。

アプリケーションサーバ側には、それぞれのIDに対応するHttpSessionインスタンスが用意されます。

セッションIDとセッションスコープの対応

ブラウザセッションID対応するセッションスコープ
青山さんのブラウザID:00001青山さん用のHttpSession
山田さんのブラウザID:00002山田さん用のHttpSession

ブラウザは、以降のリクエストで自分に設定されたセッションIDをサーバへ送信します。

サーバは受け取ったセッションIDを確認し、そのIDに対応するHttpSessionインスタンスを取り出します。

この仕組みによって、青山さんは青山さん用のセッションスコープを使い、山田さんは山田さん用のセッションスコープを使えるようになります。

図1:セッションIDによるブラウザの区別

この図から分かること

セッションスコープは、利用者のブラウザごとに用意されます。

アプリケーションサーバは、ブラウザに設定されたセッションIDをもとに、どのHttpSessionインスタンスを使うかを判断します。

青山さんのブラウザからID:00001が送られてきた場合は、ID:00001に対応するセッションスコープを使います。山田さんのブラウザからID:00002が送られてきた場合は、ID:00002に対応するセッションスコープを使います。

この仕組みにより、ログイン情報やカート情報のような利用者ごとの情報が、他の利用者と混ざらずに管理されます。

request.getSessionとセッションIDの関係

サーブレットでセッションスコープを使うときは、request.getSessionでHttpSessionインスタンスを取得します。

最初にrequest.getSessionが実行されたとき、まだそのブラウザ用のセッションがなければ、アプリケーションサーバが新しいHttpSessionインスタンスを作成します。

同時に、そのHttpSessionを識別するためのセッションIDも発行されます。

2回目以降のリクエストでは、ブラウザから送られてきたセッションIDをもとに、同じHttpSessionインスタンスを取得します。

getSession実行時の動き

タイミング動き
初回のgetSession新しいHttpSessionを作成し、セッションIDを発行する
2回目以降のgetSession送られてきたセッションIDに対応するHttpSessionを取得する

この動きにより、同じブラウザからの連続したリクエストでは、同じセッションスコープを利用できます。

セッションスコープに保存したインスタンスを次のリクエストでも取得できるのは、セッションIDによって同じHttpSessionが見つけられるからです。

セッションIDはブラウザから自動送信される

セッションIDは、リクエストのたびにサーバへ送信されます。

ただし、開発者が毎回プログラムでセッションIDを送信する処理を書くわけではありません。

このやりとりは、ブラウザとサーバの裏側で自動的に行われます。

そこで使われる代表的な仕組みがクッキーです。

クッキーを使うことで、サーバはブラウザに小さなデータを保存させることができます。セッションIDも、このクッキーを使ってブラウザ側に保持されることがあります。

クッキーとは

クッキーとは、Webサーバやアプリケーションサーバが、ブラウザにデータを保存させるための仕組みです。

サーバがレスポンスにクッキーを含めると、ブラウザはそのクッキーを保存します。

その後、同じサーバへアクセスするとき、ブラウザは保存しているクッキーをHTTPリクエストに付けて自動的に送信します。

クッキーの基本的な流れ

順番内容
1サーバがレスポンスにクッキーを含める
2ブラウザがクッキーを保存する
3次回以降のリクエストでブラウザがクッキーを送信する
4サーバがクッキー内の情報を確認する

アプリケーションサーバは、このクッキーにセッションIDを含めることで、ブラウザとサーバの間でセッションIDをやりとりします。

開発者が毎回セッションIDを送信するコードを書かなくても、ブラウザが自動的にセッションIDを送信してくれるのは、クッキーの仕組みが使われているためです。

図2:クッキーを使ったセッションIDのやりとり

この図から分かること

サーバは、初回のアクセス時にHttpSessionインスタンスを作成し、セッションIDを発行します。

そのセッションIDは、レスポンスに含まれるクッキーとしてブラウザへ送られます。ブラウザはクッキーを保存し、次回以降のリクエストで自動的にサーバへ送信します。

サーバは送られてきたセッションIDを確認し、対応するセッションスコープを取得します。

この仕組みによって、同じブラウザからのリクエストを、同じ利用者の操作として扱えるようになります。

クッキーには有効期限がある

クッキーには有効期限を設定できます。

有効期限が設定されているクッキーは、その期限内であればブラウザを閉じても残ることがあります。新しくブラウザを開いたあとでも、同じサーバへアクセスすると、保存されているクッキーが送信されます。

そのため、有効期限内のセッションIDが残っていれば、ブラウザを閉じ直してもセッションスコープを継続して利用できる場合があります。

ただし、特別な設定をしない限り、アプリケーションサーバはブラウザが閉じられるまでをセッションIDの有効期限とすることが一般的です。

その場合、ブラウザを閉じると、同じセッションスコープを再利用できなくなります。

クッキーの有効期限とセッション

状況セッションスコープの扱い
ブラウザを開いたまま同じサイトへアクセスする同じセッションIDが送られ、同じセッションスコープを使える
ブラウザを閉じるまでが有効期限の場合ブラウザを閉じると同じセッションを利用できなくなる
有効期限が長く設定されている場合ブラウザを閉じてもクッキーが残ることがある

ショッピングサイトで、ブラウザを閉じたあとにカートの内容が消えてしまうことがあります。

これは、ブラウザを閉じたことでセッションIDを保持できなくなり、前回のセッションスコープを利用できなくなったためです。

ブラウザを閉じてもカートの内容が残るサイトでは、クッキーの有効期限を調整したり、セッションスコープ以外の方法でデータを保存したりしている場合があります。

複数ウィンドウではセッションIDが共有されることがある

ブラウザのウィンドウを複数開いて動作確認するときは注意が必要です。

多くのブラウザでは、同じブラウザで開いている複数のウィンドウが、同じセッションIDを共有します。

そのため、あるウィンドウでセッションスコープに保存した情報が、別のウィンドウでも利用されることがあります。

複数ウィンドウで起こりやすいこと

状況起こること
1つ目のウィンドウで申込情報を保存するセッションスコープに情報が残る
そのウィンドウを残したまま別ウィンドウで再実行する同じセッションIDが使われることがある
空のセッションを想定している前回の情報が残っていて想定と違う動きになることがある

開発中に、セッションスコープを使ったプログラムの動きがおかしいと感じた場合は、ブラウザのウィンドウをすべて閉じてから実行し直すと確認しやすくなります。

セッションスコープは便利ですが、前回の情報が残ることがあるため、動作確認ではセッションの状態を意識することが大切です。

セッションスコープの内容は自動ではすぐに消えない

セッションスコープは、リクエストをまたいでインスタンスを保持できる便利な仕組みです。

しかし、便利だからといって必要以上に使いすぎると、アプリケーションサーバのメモリを圧迫する原因になります。

リクエストスコープは、レスポンスが返されると役目を終えます。

一方、HttpSessionインスタンスは、使われなくなったように見えても、すぐにはサーバから消えません。

サーバ側から見ると、利用者がブラウザを閉じたのか、少し席を外しているだけなのかを正確に判断できないためです。

HttpSessionがすぐに消えない理由

理由内容
利用者の状態が分からないサーバは利用者が戻ってくるかどうか判断できない
ブラウザを閉じたことをすぐに検知しにくいサーバ側では不要になったか分かりにくい
次のリクエストに備える必要がある同じセッションを再利用する可能性がある

そのため、HttpSessionインスタンスは、通常のインスタンスのようにすぐガベージコレクションの対象になるとは限りません。

セッションスコープは便利ですが、長く残る分だけ管理も重要になります。

セッションタイムアウトとは

HttpSessionインスタンスをいつまでもサーバ内に残しておくと、メモリ不足の原因になります。

そこで、アプリケーションサーバは、一定時間利用されていないHttpSessionインスタンスを不要と判断し、破棄できるようにします。

これをセッションタイムアウトといいます。

たとえば、しばらく操作しないまま一定時間が経過すると、そのセッションは無効になります。Webサイトで30分以上操作しないと無効になりますといった案内を見かけることがありますが、これはセッションタイムアウトに関係しています。

Apache Tomcatでは、セッションタイムアウトまでの時間が初期設定で30分になっています。

セッションタイムアウトの考え方

項目内容
対象一定時間利用されていないHttpSession
目的不要なセッションを残し続けないようにする
結果対象のHttpSessionが破棄される
Tomcatの初期設定30分

セッションタイムアウトがあることで、利用されなくなったHttpSessionインスタンスをいつまでも保持し続けることを防げます。

ただし、タイムアウトを待てばよいという考え方だけでは不十分です。

図3:セッションタイムアウトと開発者による削除

この図から分かること

セッションスコープに保存したインスタンスは、リクエストスコープより長く残ります。

そのため、使われなくなったHttpSessionインスタンスは、一定時間が経過するとセッションタイムアウトによって破棄されます。

ただし、タイムアウトを待つだけではなく、開発者が不要になったインスタンスをremoveAttributeで削除したり、ログアウト時などにinvalidateでセッションスコープそのものを破棄したりすることも大切です。

セッションスコープを使いすぎるとメモリを圧迫する

セッションスコープに保存したインスタンスは、レスポンス後も残ります。

これは便利な反面、多くの利用者がアクセスし、それぞれのセッションスコープに大量のデータを保存すると、アプリケーションサーバのメモリを消費します。

短時間にアクセスが集中した場合、セッションタイムアウトやガベージコレクションによる後片付けが間に合わないこともあります。

セッションスコープの多用で起こりやすい問題

問題内容
メモリ使用量が増える利用者ごとのHttpSessionが残り続ける
性能が低下するサーバが多くのインスタンスを管理する必要がある
サーバ停止の原因になるメモリ不足が深刻になる場合がある
古い情報が残る不要なインスタンスが次の処理に影響することがある

セッションスコープは、リクエストをまたいで本当に必要な情報を保存するために使います。

何でもセッションスコープに入れるのではなく、保存する必要があるか、いつ削除するかを意識することが大切です。

不要になったインスタンスはremoveAttributeで削除する

セッションスコープに保存したインスタンスが不要になったら、removeAttributeで削除します。

たとえば、研修コース申込機能では、確認画面から申込を実行したあと、申込情報をセッションスコープに残しておく必要はありません。

そのため、申込完了時にentryUserをremoveAttributeで削除します。

removeAttributeを使う場面

場面内容
申込処理が完了した申込情報を削除する
一時的に保存した情報を使い終えた不要な属性を削除する
古い情報を次の処理に残したくない属性名を指定して削除する

removeAttributeは、セッションスコープの中に保存されている特定のインスタンスだけを削除します。

セッションスコープそのものは残ります。

セッションスコープそのものを破棄する

保存している一部のインスタンスではなく、セッションスコープ全体を破棄したい場合は、invalidateを使います。

invalidateを実行すると、HttpSessionインスタンスそのものが破棄されます。

そのセッションスコープに保存されていたすべてのインスタンスも利用できなくなります。

invalidateの特徴

項目内容
目的セッションスコープ全体を破棄する
消えるものセッションスコープに保存されていたすべてのインスタンス
よく使う場面ログアウト処理
実行後次にgetSessionを実行すると新しいHttpSessionが作成される

ログアウト処理では、ログイン中の利用者情報や、セッションスコープに保存された関連情報をすべて消したいことがあります。

このような場合は、個別にremoveAttributeで削除するよりも、invalidateでセッションスコープ全体を破棄するほうが適しています。

ログアウト時にセッションを破棄する例

ここでは、ログアウト処理でセッションスコープ全体を破棄するサーブレットを例にします。

ログイン中の学習者情報や、申込中の情報などをセッションスコープに保存している場合、ログアウト時にまとめて破棄します。

セッションスコープを破棄するサーブレットの例

package servlet;

import java.io.IOException;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

@WebServlet("/LogoutServlet")
public class LogoutServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {

        // HttpSessionインスタンスを取得
        HttpSession session = request.getSession();

        // セッションスコープそのものを破棄
        session.invalidate();

        // ログアウト後の画面へリダイレクト
        response.sendRedirect("TrainingEntryServlet");
        return;
    }
}

この例では、request.getSessionでHttpSessionインスタンスを取得し、session.invalidateでセッションスコープ全体を破棄しています。

その後、TrainingEntryServletへリダイレクトしています。

ログアウト後は、以前のセッションスコープに保存されていたインスタンスは利用できません。次にrequest.getSessionが実行されると、新しいHttpSessionインスタンスが作成されます。

removeAttributeとinvalidateの使い分け

removeAttributeとinvalidateは、どちらもセッションスコープ内の情報を整理するために使います。

ただし、削除する範囲が違います。

removeAttributeとinvalidateの違い

操作削除する範囲使う場面
removeAttribute指定した属性名のインスタンスだけ申込完了後にentryUserだけ削除する
invalidateセッションスコープ全体ログアウト時にすべての情報を破棄する

removeAttributeは、セッションスコープ内の特定のインスタンスだけを削除したいときに使います。

invalidateは、セッションスコープそのものを破棄したいときに使います。

どちらも、不要になった情報を残さないための大切な操作です。

セッションスコープの管理で意識したいこと

セッションスコープは、リクエストをまたいでデータを保持できるため、Webアプリケーションの幅を広げてくれます。

しかし、長く残るという特徴があるため、管理しないまま使い続けると問題が起こることがあります。

管理で意識したいポイント

確認項目内容
本当にセッションスコープが必要か1回の表示だけならリクエストスコープで十分な場合がある
いつ保存するかログイン時、入力完了時など、保存タイミングを決める
いつ削除するか完了時、キャンセル時、ログアウト時など、削除タイミングを決める
何を保存するか必要な情報だけを保存する
どの属性名で保存するか取得や削除と対応しやすい名前にする

セッションスコープは、保存できるから何でも入れる場所ではありません。

リクエストをまたいで使う必要がある情報だけを保存し、不要になったタイミングで削除または破棄することが大切です。

セッションスコープとステートフルな通信

HTTPは、基本的には前回のリクエストの状態を覚えておく仕組みを持っていません。

つまり、何もしなければ、1回目のリクエストと2回目のリクエストは別々のものとして扱われます。

このように、リクエストをまたいで情報を保持しない通信をステートレスな通信といいます。

一方、リクエストをまたいでユーザーの情報を保持する通信をステートフルな通信といいます。

セッションスコープは、HTTP上でステートフルな動きを実現するための仕組みの1つです。

ステートレスとステートフル

種類内容
ステートレスな通信リクエストをまたいで情報を保持しない
ステートフルな通信リクエストをまたいで情報を保持する

セッションスコープを使えば、ログイン中の利用者情報や、画面遷移中の申込情報などを保持できます。

その結果、複数のリクエストを1つの流れとして扱えるようになります。

セッションスコープ以外の方法

リクエストをまたいで情報を扱う方法は、セッションスコープだけではありません。

リクエストパラメータやクッキーを使って、状態を引き継ぐこともできます。

状態を引き継ぐ方法

方法考え方
セッションスコープサーバ側のHttpSessionにインスタンスを保存する
リクエストパラメータURLやフォームに値を含めて次のリクエストへ送る
クッキーブラウザ側に情報を保存し、次回以降のリクエストで送信する

どの方法を使うかは、扱う情報の内容や、どのくらいの期間保持したいかによって変わります。

この段階では、セッションスコープはサーバ側でユーザーごとの情報を保持する仕組みであり、その識別にセッションIDとクッキーが関わっている、と押さえておきましょう。

セッションスコープのしくみで確認したいこと

セッションスコープを使うときは、表面的な保存や取得だけでなく、裏側のしくみも意識すると理解が深まります。

確認したいポイント

確認項目内容
セッションスコープの正体HttpSessionインスタンス
作成タイミング最初にrequest.getSessionを実行したとき
利用者の区別セッションIDでブラウザを区別する
セッションIDのやりとりクッキーを使って自動的に行われる
複数ウィンドウ同じブラウザではセッションIDを共有することがある
タイムアウト一定時間使われないHttpSessionは破棄される
個別削除removeAttributeで特定のインスタンスを削除する
全体破棄invalidateでセッションスコープ全体を破棄する

セッションスコープは、リクエストをまたいで同じ情報を使うための便利な仕組みです。

その裏側では、ブラウザごとに発行されたセッションIDと、それをやりとりするクッキーが働いています。

便利な一方で、サーバ側にインスタンスを残す仕組みでもあるため、不要になった情報はremoveAttributeで削除し、ログアウトのようにセッション全体が不要になった場合はinvalidateで破棄することを意識しましょう。