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的页面:
- 将前端文件放在Flask的
static目录下 - 修改Electron的
loadFile为loadURL:
win.loadURL('http://localhost:5000'); // 对应Flask默认端口
内容的提问来源于stack exchange,提问作者user24995271
相关产品推荐
相关产品推荐

