You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Electron+Flask后端服务端会话变量重复初始化问题排查

问题描述

开发基于Flask后端的Electron应用,需求是获取会话变量,不存在则初始化。但调用路由时会话变量每次都会重新创建,无法识别已初始化的变量,导致无法校验表单detail字段的唯一性。该问题仅在Electron和curl调用时出现,单独运行Flask则正常。具体表现为:

  • /add_detail路由每次触发session['lst']的初始化逻辑
  • Redis中生成多个会话键
  • 前端始终获取空列表
    怀疑是CORS配置不当或请求会话传递问题。

前端校验与提交代码

/* INPUT VALIDATION */
async function checkUniqueDetail(detail_to_check){
    try{
        const response=await fetch(`${SERVER_URL}/check_detail/${detail_to_check}`,{
            method: "GET",
            headers: {
                Accept: "application/json",
                "Content-Type": "application/json",
            },
        });
        const data=await response.json();
        const existing_details=data.detail_list;
        console.log("Existing details:", existing_details);
        console.log("Checking detail:", detail_to_check);
        const result=data.result;
        console.log("Result is:",result);
        if(result=="True"){
            return false;
        }else{
            console.log("The detail is unique");
            return true;
        }
    }catch(e){
        console.error(e);
        return false;
    }
}
/* FORM SUBMISSION */
function confirmFormSubmit(formData){
    if(confirm("Are you sure you want to submit?")){
        const qd=formData.get("detail");
        console.log("q_detail is:",qd);
        /* Form validation */
        checkUniqueDetail(qd).then(result=>{
            isUniqueDetail=result;
            console.log(qd,"unique?:",isUniqueDetail);
            /* If validated, send info */
            if(isUniqueDetail==true){
                sendForm();
                sendDetail(qd);
                message.innerText="Question added!"
            }else{
                message.innerText="A question with this detail already exists. Please try again"
            }
        });
    }
}
form.addEventListener("submit", async (event)=>{
    event.preventDefault();
                
    const fData = new FormData(form);
    confirmFormSubmit(fData);
});

Flask后端路由代码

# ROUTES
from flask import Flask, request, session
from flask_session import Session
from flask_cors import CORS
import redis, json
#for forcibly clearing session files
import atexit

app = Flask(__name__)

# Configurations
app.config["SECRET_KEY"]="change_later"
app.config["SESSION_TYPE"] = "redis"
app.config["SESSION_PERMANENT"] = False
r = redis.from_url('redis://127.0.0.1:6379')
app.config['SESSION_REDIS'] = r
def test_redis_connection(redis_session):
    """Check that Redis is connected to"""
    try:
        redis_session.ping()  # Check if Redis is alive
        print("Redis connection successful!")
    except redis.exceptions.ConnectionError as e:
        print(f"Redis connection error: {e}")
        exit()  # Or handle the error appropriately
test_redis_connection(r)
app.config["CORS_HEADERS"] = "Content-Type"

# Initialize Plugins
sess=Session()
sess.init_app(app)
CORS(app,resources={r"/*": {"origins": "http://localhost*"}})

#check that the server's running and connected
@app.route("/", methods=["GET","POST"])
def check():
    return {"result":"Server active!"}

### ERROR!!!: This only works as a Flask app
#       It seems like every time this is run,
#       it makes a new session variable, rather than getting the old one
@app.route("/add_detail/<detail>",methods=["GET","POST"])
def add_detail_to_list(detail):
    """Add a q_detail to a list of q_details"""
    # Initialize the list if it doesn't exist
    if 'lst' not in session:
        print("Session variable not found. Initializing...")
        session['lst'] = json.dumps([])
        session.modified = True

    # Append to the list
    lst:list[str]=json.loads(session['lst'])
    print("Before appending:",lst)
    lst.append(detail)
    print("After appending:",lst)
    session['lst'] = json.dumps(lst)
    session.modified = True
    
    return {"response":f"{lst}"}
@app.route("/get_all_details",methods=["GET"])
def get_all_details():
    details:list[str]=session.get("lst",[])
    return {"result":details}
@app.route("/check_detail/<detail>")
def check_detail(detail):
    details:list[str]=session.get("lst",[])
    if detail in details:
        return {"result":"True","detail_list":details}
    else:
        return {"result":"False","detail_list":details}


## NOTE: Make sure this works in production
### ERROR!!! Doesn't work with Electron app
#       Maybe due to how the Flask app is killed?
def clear_redis_sessions(redis_session):
    """Clears all session data from Redis."""
    try:
        for key in redis_session.keys("session:*"): # Important: Use a pattern to only delete session keys
            redis_session.delete(key)
        print("Redis sessions cleared.")
    except Exception as e:
        print(f"Error clearing Redis sessions: {e}")
atexit.register(clear_redis_sessions,redis_session=r)  # Register the cleanup function


# print(app.url_map)

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

Electron主进程代码

const { app, BrowserWindow } = require('electron');
const { exec } = require("child_process");

/**
 * Connects to the Flask app
 */
const connectToFlask=function(){
    let python;
    //test version
    python = require('child_process').spawn('py', ['./py/test_routes.py']);
    //executable version
    //python = require('child_process').execFile("routes.exe");
    python.stdout.on('data', function (data) {  
        console.log("FLASK RUNNING! data:", data.toString('utf8'));  
    });  
    python.stderr.on('data', (data) => {    // when error
        console.error(`stderr: ${data}`);
        console.log(`stderr: ${data}`);
    });
    python.on("close", (code)=>{
        console.log(`child process exited with code ${code}`);
    });
}

/**
 * Create a new BrowserWindow that's connected to Flask with index.html as its UI
 */
const createWindow = () => {
    const win = new BrowserWindow({
        width: 800,
        height: 600
    });

    connectToFlask();

    win.loadFile('index.html');
}

/**
 * When all the windows close, quit the app
 */
app.on('window-all-closed', () => {
    if (process.platform !== 'darwin'){
        /* Kill the Flask app on close too */
        exec("taskkill /f /t /im python.exe", (err, stdout, stderr) => {
            if (err) {
                console.error(err);
                return;
            }
            console.log(`stdout: ${stdout}`);
            console.log(`stderr: ${stderr}`);
            console.log('Flask process terminated');
        });
        app.quit();
    }
});

/**
 * Wait for the app to be ready, then create a new window
 */
app.whenReady().then(() => {
    createWindow();
});
解决方案

核心原因

Flask的session依赖Cookie中的session_id识别用户会话,但跨域请求(包括Electron本地文件file://发起的请求)默认不会携带Cookie,导致每次请求都生成新的会话,无法复用已有的session['lst']。此外,部分路由中存在session数据解析错误,导致无法正确读取已存储的列表。

1. 修正Flask的CORS配置

修改CORS初始化代码,允许携带凭证(Cookie),并适配Electron的file:// origin:

# 替换原CORS初始化代码
CORS(app, 
     resources={r"/*": {"origins": ["http://localhost*", "file://*"]}}, 
     supports_credentials=True)

2. 前端Fetch请求添加凭证配置

所有与Flask交互的fetch请求都要添加credentials: 'include',确保携带Cookie:

// 以checkUniqueDetail为例修改
async function checkUniqueDetail(detail_to_check){
    try{
        const response=await fetch(`${SERVER_URL}/check_detail/${detail_to_check}`,{
            method: "GET",
            credentials: 'include', // 新增:携带Cookie
            headers: {
                Accept: "application/json",
                "Content-Type": "application/json",
            },
        });
        // ... 其余代码不变
    }catch(e){
        console.error(e);
        return false;
    }
}

同理,sendDetail等其他请求也要添加该配置。

3. 修正Flask路由中的Session数据解析错误

原代码中session.get("lst", [])会在lst不存在时返回空列表,但实际存储的是JSON字符串,导致类型不匹配。修改如下:

@app.route("/get_all_details",methods=["GET"])
def get_all_details():
    lst_json = session.get("lst", json.dumps([])) # 默认返回空列表的JSON字符串
    details:list[str] = json.loads(lst_json)
    return {"result":details}

@app.route("/check_detail/<detail>")
def check_detail(detail):
    lst_json = session.get("lst", json.dumps([]))
    details:list[str] = json.loads(lst_json)
    if detail in details:
        return {"result":"True","detail_list":details}
    else:
        return {"result":"False","detail_list":details}

4. 可选:调整Electron加载方式(更规范)

如果不想允许file://的CORS origin,可以让Electron加载http://localhost的页面:

  1. 将前端文件放在Flask的static目录下
  2. 修改Electron的loadFile为loadURL:
win.loadURL('http://localhost:5000'); // 对应Flask默认端口

内容的提问来源于stack exchange,提问作者user24995271

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 13:47:02