モーグルとカバとパウダーの日記

モーグルやカバ(EXカービング)山スキー(BC)などがメインの日記でした。今は仕事のコンピュータ系のネタが主になっています。以前はスパム対策関連が多かったのですが最近はディープラーニング関連が多めです。

Javaサーブレットでユーザのセッション数を取得する

Tomcatで動いているWebアプリケーションで、ユーザアカウント毎のセッション数制限を実装する必要が出ました。


調査したところ、3通りの方法が考えられました。


1. サーブレットにプローブを挿しといてそこから取得する
2. セッション生成削除のリスナーを設定してセッション情報コピーを作る
3. セッション情報をDBに持つように設定してDB経由で見る


このうち、1と2の方法を実際に作って試しました。


1の「サーブレットにプローブを挿しといてそこから取得する」という方法は、下記ページのやり方を真似しました。

DAY2907 セッション情報の一覧を取得する(Tomcat限定) - LHD Peugeot

ContainerServletのsetWrapperメソッドをオーバーライドしておいて、渡されてきた containerWrapper を保持しておき、セッション情報を見たいときにはそれ経由でセッションリストにアクセスするというものです。


取得した後の値のセッション情報の触り方などは、tomcatが標準で提供しているmanagerサーブレットのソースが参考になりました。

GC: ManagerServlet - org.apache.catalina.manager.ManagerServlet (.java) - GrepCode Class Source


ただ、この方法はtomcat依存になってしまう、という問題と、このサーブレット自体をJSPなどに埋め込むことはできない(オブジェクトとして持てない)ため、サーブレットに対してWebAPIなどを通じて情報を取得する必要がありました。



そこで2の「セッション生成削除のリスナーを設定してセッション情報コピーを作る」を試しました。


これは下記のページを参考にしました。

8. リスナー2 | TECHSCORE(テックスコア)

javax.servlet.http.HttpSessionListener は、web.xmlで設定しておくと、サーブレットでセッションが生成されたときと削除された時に呼び出されるように出来ます。


この時に得られるセッションオブジェクトは、単にその時のセッション状況コピーではなくて、サーブレット内のセッションオブジェクトそのものなので、そのまま保持しておくとセッション生成後に行われた変更も含めて全てのセッション情報を取得することが出来ます。

なので、セッション生成後にログイン処理がされてユーザ名のセットがされても、ちゃんと取得することが出来ます。

(最初その認識がなかったため、この手法は使えないと思っていた)


下記程度の簡単な内容でセッション情報を保持することが出来るようになり、JSPから普通にUserCounterオブジェクトを生成してユーザのセッション数などを得ることが出来ます。

public class UserCounter {
    private static final UserCounter INSTANCE = new UserCounter();
    private final Map<String, HttpSession> sessions = new HashMap<>();

    private UserCounter() {
    }
    
    public static UserCounter getInstance() {
        return INSTANCE;
    }
    
    synchronized public void addSession(HttpSession session) {
        sessions.put(session.getId(), session);
    }

    synchronized public void delSession(HttpSession session) {
        sessions.remove(session.getId());
    }
    
    synchronized public int countAllSessions() {
        return sessions.size();
    }
    
    synchronized public int countUserSessions(String username) {
        int userCounter = 0;
        for (HttpSession session: sessions.values()) {
            String sessionUsername = (String) session.getAttribute("username");
            if (sessionUsername != null && sessionUsername.equals(username)) {
                userCounter ++;
            }
        }
        return userCounter;
    }
}

public class UserCountListner implements HttpSessionListener {
    private final UserCounter counter = UserCounter.getInstance();

    @Override
    public void sessionCreated(HttpSessionEvent event) {
        HttpSession session = event.getSession();
        counter.addSession(session);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent event) {
        HttpSession session = event.getSession();
        counter.delSession(session);
    }
}


これはtomcat依存ではないため他のWebコンテナでも動くはずです。
ただ、1の方法のように全サーブレットのセッション情報を取得するのではなく、listnerの設定をしたサーブレットのセッションのみになります。