🦔

sessionStorageを使い、ブラウザのタブ毎に保持したい情報を独立させる

に公開

はじめに

Flaskを使ったWebアプリを作ろうとした時のメモ。
ユーザーからの処理を非同期に段階的にやり取りする際に、1つの処理が終わるたびにフロント側に結果を返すようにしていた。
次の実行時に前工程の処理データが分かる様にsession機能を使っていたのだが、Flaskのデフォルトのセッション機能ではサーバー側でセッション管理はされず、ユーザーのブラウザにクッキーとして保存される。
しかし、これではもう1つタブを立ち上げて同時実行した際にクッキー情報が更新されてしまい、先に実行した方の処理(最初のタブで実行した処理)の前工程データが特定できなくなってしまう。

サーバー側のセッション管理機能を使う手もあるのだが、今回はもっと簡単にブラウザ側sessionStorage機能を使ってみた。

セッションを上書きしてしまうNGパターン

templates/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Flask セッション デモ</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            justify-content: space-around;
            padding: 40px;
        }
        .container {
            border: 1px solid #ccc;
            padding: 20px;
            width: 40%;
            text-align: center;
            border-radius: 10px;
        }
        button {
            margin-top: 10px;
            padding: 10px 20px;
            cursor: pointer;
        }
        #session-result {
            margin-top: 15px;
            font-weight: bold;
            color: green;
        }
    </style>
</head>
<body>
    <!-- 左側:セッション生成ボタン -->
    <div class="container">
        <h2>セッション生成</h2>
        <button id="create-session-btn">セッションを作成</button>
        <div id="create-result"></div>
    </div>

    <!-- 右側:現在のセッション表示 -->
    <div class="container">
        <h2>現在のセッション表示</h2>
        <button id="get-session-btn">セッションを表示</button>
        <div id="session-result"></div>
    </div>

    <script>
        // セッション生成ボタン
        document.getElementById("create-session-btn").addEventListener("click", async () => {
            const response = await fetch("/create_session", {
                method: "POST",
                headers: { "Content-Type": "application/json" }
            });
            const data = await response.json();
            document.getElementById("create-result").textContent = data.message + " (ID: " + data.session_id + ")";
        });

        // セッション表示ボタン
        document.getElementById("get-session-btn").addEventListener("click", async () => {
            const response = await fetch("/get_session");
            const data = await response.json();
            const resultDiv = document.getElementById("session-result");

            if (data.session_id) {
                resultDiv.textContent = "現在のセッションID: " + data.session_id;
            } else {
                resultDiv.textContent = data.message;
            }
        });
    </script>
</body>
</html>
app.py
from flask import Flask, session, jsonify, render_template
import uuid

app = Flask(__name__)
app.secret_key = 'your_secret_key_here'  # Cookie署名用(必須)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/create_session', methods=['POST'])
def create_session():
    """
    セッションを生成して返すAPI
    """
    # ランダムなセッションIDを生成
    session_id = str(uuid.uuid4())
    session['session_id'] = session_id
    return jsonify({'message': 'セッションを作成しました', 'session_id': session_id})

@app.route('/get_session', methods=['GET'])
def get_session():
    """
    現在のセッションIDを返すAPI
    """
    session_id = session.get('session_id')
    if session_id:
        return jsonify({'session_id': session_id})
    else:
        return jsonify({'message': 'セッションはまだ作成されていません'})

if __name__ == '__main__':
    app.run(debug=True)

ここで動作の確認になるが、上記のプログラムを実行してタブを2つ開き以下の事を試す。

① まず1つ目のタブでセッションを生成した後、現在のセッションを表示すると、当たり前だが以下の様になる。


② 次にタブをもう1つ立ち上げて、同じくセッションを生成した後、現在のセッションを表示すると、これも当たり前だがこのタブの中では今作成したものが表示される。


③ この状態で1つ目のタブに戻り、現在のセッションを表示すると、上記②で生成したセッションに更新されてしまう。

sessionStorage機能使い、タブ毎に独立された情報を保持する

以下はsessionStorageの機能を使い、タブ毎にセッション情報を独立させるサンプルコード。

templates/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Flask + sessionStorage デモ</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            justify-content: space-around;
            padding: 40px;
        }
        .container {
            border: 1px solid #ccc;
            padding: 20px;
            width: 40%;
            text-align: center;
            border-radius: 10px;
        }
        button {
            margin-top: 10px;
            padding: 10px 20px;
            cursor: pointer;
        }
        #session-result, #create-result {
            margin-top: 15px;
            font-weight: bold;
            color: green;
        }
    </style>
</head>
<body>
    <!-- 左側:セッション生成ボタン -->
    <div class="container">
        <h2>セッション生成</h2>
        <button id="create-session-btn">セッションを作成</button>
        <div id="create-result"></div>
    </div>

    <!-- 右側:現在のセッション表示 -->
    <div class="container">
        <h2>現在のセッション表示</h2>
        <button id="get-session-btn">セッションを表示</button>
        <div id="session-result"></div>
    </div>

    <script>
        // セッション生成ボタン
        document.getElementById("create-session-btn").addEventListener("click", async () => {
            const response = await fetch("/create_session", {
                method: "POST",
                headers: { "Content-Type": "application/json" }
            });

            const data = await response.json();
            const sessionId = data.session_id;

            // タブごとに sessionStorage に保存
            sessionStorage.setItem("session_id", sessionId);

            document.getElementById("create-result").textContent =
                `このタブ用に新しいセッションを作成しました (ID: ${sessionId})`;
        });

        // セッション表示ボタン
        document.getElementById("get-session-btn").addEventListener("click", () => {
            const sessionId = sessionStorage.getItem("session_id");
            const resultDiv = document.getElementById("session-result");

            if (sessionId) {
                resultDiv.textContent = `このタブのセッションID: ${sessionId}`;
            } else {
                resultDiv.textContent = "このタブではまだセッションが作成されていません。";
            }
        });
    </script>
</body>
</html>
app.py
from flask import Flask, jsonify, render_template
import uuid

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/create_session', methods=['POST'])
def create_session():
    """
    サーバーでランダムなセッションIDを生成し返すだけ
    (Flaskのセッション機能は使わない)
    """
    session_id = str(uuid.uuid4())
    return jsonify({'session_id': session_id})

if __name__ == '__main__':
    app.run(debug=True)

先程同様にタブを2つ開いてセッションが独立しているか試してみる。

① まず1つ目のタブでセッションを生成した後、現在のセッションを表示すると、これは先程と同じ。


② 次にタブをもう1つ立ち上げて、同じくセッションを生成した後、現在のセッションを表示すると、これも先程と同じ。


③ この状態で1つ目のタブに戻り、現在のセッションを表示すると・・・・・上記①で生成したセッションのままになっている。


Discussion