Back $[@name] session variables with a JSON file via JFile, reusing the @ sigil
The @ sigil previously read through global.getSession(global.sessionId), a MySQL-session binding that was already confirmed dead this session - Global::sessionId is never assigned anywhere in the codebase, so any $[@name] read would always throw. The design goal: persist session state as a JSON file at sessiondir/<sessionId>.json, using CoreUtils's existing JFile (an MFile+JString combination with load-on-construct/save-on- destruct RAII semantics), reusing @ rather than introducing a new sigil since the old @ usage was unreachable in practice anyway. Global.h/.cpp: added Global::sessionFile (a JFile*, NULL unless a request is in cgi mode with sessiondir set). Its destructor now explicitly deletes sessionFile - JFile's actual disk write happens in MFile's destructor via update(), so this has to run for persistence to happen at all (a raw `new`'d pointer that's never deleted, as cgiData still is, would never save anything back to disk). __jet.cpp: the sessiondir block now builds the session file path from the sessiondir keyword and the active session id (existing cookie or newly issued) and constructs global.sessionFile there, for both branches. The "pull sessionfile from sessiondir" comment is now implemented - JFile's own constructor already handles "file doesn't exist yet" by loading as empty, so one construction point serves both a resumed and a brand-new session. Tag.cpp: getVariable's "@" branch now reads via (*global.sessionFile)[name] with a clear error if no session is active, instead of the dead MySQL lookup. Added Tag::assignVariable (declared in Tag.h), a single write path both storeVariable overloads now funnel through, so a "@name"- prefixed target (e.g. <set name="@x" value="y"/>) is routed to the session file instead of the normal global/local/parent variables maps - without this, the read side would have been read-only, which isn't a very useful session mechanism. This surfaced two real, pre-existing CoreUtils bugs, both fixed separately (see CoreUtils commit 3e6e410): JFile's virtual-base diamond skipping JString's own empty-object seed, and MString's ZString-source copy paths pairing an absolute data pointer with a cursor-relative length - both were silently corrupting any cursor-advanced ZString (exactly what assignVariable produces when it strips the "@" prefix via ifNext() before storing). Verified live: two genuinely separate jet-2.0 process invocations sharing one session cookie, where the first writes two session variables and the second - a fresh process with no shared memory - reads both back correctly from the JSON file on disk. Also re-ran tests/posttest.sh (unchanged output, no regression) and the full CoreUtils test suite (no regressions). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6f2f3d7339
commit
fc849c44a4
5 files changed
+72
-23
No files matched your search
+6
-1
@@ -11,7 +11,12 @@ namespace jet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Global::~Global() {
|
Global::~Global() {
|
||||||
|
// JFile's destructor (via MFile::~MFile()->update()) is what actually
|
||||||
|
// writes the session JSON back to disk - it has to run for session
|
||||||
|
// persistence to happen at all, so sessionFile can't be left as a
|
||||||
|
// leaked, never-deleted pointer the way cgiData currently is.
|
||||||
|
if(sessionFile)
|
||||||
|
delete sessionFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Global::dump() {
|
void Global::dump() {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
# include "CGIFormattedData.h"
|
# include "CGIFormattedData.h"
|
||||||
# include "CGIFormData.h"
|
# include "CGIFormData.h"
|
||||||
# include "StreamReader.h"
|
# include "StreamReader.h"
|
||||||
|
# include "JFile.h"
|
||||||
# include <map>
|
# include <map>
|
||||||
|
|
||||||
namespace jet {
|
namespace jet {
|
||||||
@@ -40,6 +41,16 @@ namespace jet {
|
|||||||
|
|
||||||
coreutils::StreamReader cgiReader;
|
coreutils::StreamReader cgiReader;
|
||||||
coreutils::CGIFormData *cgiData = NULL;
|
coreutils::CGIFormData *cgiData = NULL;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// sessionFile backs $[@name] (read) and <set name="@name" .../>
|
||||||
|
/// (write) - a JSON file at sessiondir/<sessionId>.json, loaded when
|
||||||
|
/// opened and saved automatically when destroyed (JFile/MFile's own
|
||||||
|
/// RAII behavior - see MFile.cpp). NULL unless cgi mode and the
|
||||||
|
/// sessiondir keyword are both active for this request.
|
||||||
|
///
|
||||||
|
|
||||||
|
coreutils::JFile *sessionFile = NULL;
|
||||||
std::map<coreutils::MString, coreutils::MString> variables;
|
std::map<coreutils::MString, coreutils::MString> variables;
|
||||||
std::map<coreutils::MString, __mysql *> sessions;
|
std::map<coreutils::MString, __mysql *> sessions;
|
||||||
std::map<coreutils::MString, coreutils::MString> headers;
|
std::map<coreutils::MString, coreutils::MString> headers;
|
||||||
|
|||||||
@@ -428,7 +428,9 @@ namespace jet {
|
|||||||
throw coreutils::Exception("cgi variable only allowed in cgi mode.");
|
throw coreutils::Exception("cgi variable only allowed in cgi mode.");
|
||||||
} else if(variable.ifNext("@")) {
|
} else if(variable.ifNext("@")) {
|
||||||
renderVariableName(variable, name, modifier);
|
renderVariableName(variable, name, modifier);
|
||||||
coreutils::MString sessionValue = global.getSession(global.sessionId)->getColumnValue(name);
|
if(global.sessionFile == NULL)
|
||||||
|
throw coreutils::Exception("session variable requested but no session is active (missing sessiondir keyword, or cgi mode is off).");
|
||||||
|
coreutils::MString sessionValue = (*global.sessionFile)[name];
|
||||||
return processModifier(sessionValue, modifier);
|
return processModifier(sessionValue, modifier);
|
||||||
} else if(variable.ifNext("%")) {
|
} else if(variable.ifNext("%")) {
|
||||||
// NOTE: unreachable - '%' is already claimed by the keyword-lookup branch
|
// NOTE: unreachable - '%' is already claimed by the keyword-lookup branch
|
||||||
@@ -483,7 +485,15 @@ namespace jet {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tag::storeVariable(coreutils::ZString variable, coreutils::MString value, coreutils::ZString scope) {
|
void Tag::assignVariable(coreutils::ZString variable, coreutils::MString value, coreutils::ZString scope) {
|
||||||
|
if(variable.startsWith("@")) {
|
||||||
|
coreutils::ZString sessionName = variable;
|
||||||
|
sessionName.ifNext("@");
|
||||||
|
if(global.sessionFile == NULL)
|
||||||
|
throw coreutils::Exception("session variable assignment requested but no session is active (missing sessiondir keyword, or cgi mode is off).");
|
||||||
|
(*global.sessionFile)[sessionName] = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if((scope == "global") || (scope == ""))
|
if((scope == "global") || (scope == ""))
|
||||||
global.variables[variable] = value;
|
global.variables[variable] = value;
|
||||||
else if(scope == "local")
|
else if(scope == "local")
|
||||||
@@ -491,39 +501,43 @@ namespace jet {
|
|||||||
else if(scope == "parent")
|
else if(scope == "parent")
|
||||||
local->parent->variables[variable] = value;
|
local->parent->variables[variable] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Tag::storeVariable(coreutils::ZString variable, coreutils::MString value, coreutils::ZString scope) {
|
||||||
|
assignVariable(variable, value, scope);
|
||||||
|
}
|
||||||
|
|
||||||
void Tag::storeVariable(coreutils::ZString variable) {
|
void Tag::storeVariable(coreutils::ZString variable) {
|
||||||
if(keywordDefined("expr")) {
|
if(keywordDefined("expr")) {
|
||||||
if(!keywordDefined("scope") || (resolveKeyword("scope") == "global"))
|
if(!keywordDefined("scope") || (resolveKeyword("scope") == "global"))
|
||||||
global.variables[variable] = Operand(keywords["expr"], *this).string;
|
assignVariable(variable, Operand(keywords["expr"], *this).string, "global");
|
||||||
else if(resolveKeyword("scope") == "local")
|
else if(resolveKeyword("scope") == "local")
|
||||||
local->variables[variable] = Operand(keywords["expr"], *this).string;
|
assignVariable(variable, Operand(keywords["expr"], *this).string, "local");
|
||||||
else if(resolveKeyword("scope") == "parent")
|
else if(resolveKeyword("scope") == "parent")
|
||||||
local->parent->variables[variable] = Operand(keywords["expr"], *this).string;
|
assignVariable(variable, Operand(keywords["expr"], *this).string, "parent");
|
||||||
} else if(hasContainer) {
|
} else if(hasContainer) {
|
||||||
processContainer(container);
|
processContainer(container);
|
||||||
if(evaluate) {
|
if(evaluate) {
|
||||||
if(!keywordDefined("scope") || (resolveKeyword("scope") == "global"))
|
if(!keywordDefined("scope") || (resolveKeyword("scope") == "global"))
|
||||||
global.variables[variable] = out;
|
assignVariable(variable, out, "global");
|
||||||
else if(resolveKeyword("scope") == "local")
|
else if(resolveKeyword("scope") == "local")
|
||||||
local->variables[variable] = out;
|
assignVariable(variable, out, "local");
|
||||||
else if(resolveKeyword("scope") == "parent")
|
else if(resolveKeyword("scope") == "parent")
|
||||||
local->parent->variables[variable] = out;
|
assignVariable(variable, out, "parent");
|
||||||
} else {
|
} else {
|
||||||
if(!keywordDefined("scope") || (resolveKeyword("scope") == "global"))
|
if(!keywordDefined("scope") || (resolveKeyword("scope") == "global"))
|
||||||
global.variables[variable] = container;
|
assignVariable(variable, container, "global");
|
||||||
else if(resolveKeyword("scope") == "local")
|
else if(resolveKeyword("scope") == "local")
|
||||||
local->variables[variable] = container;
|
assignVariable(variable, container, "local");
|
||||||
else if(resolveKeyword("scope") == "parent")
|
else if(resolveKeyword("scope") == "parent")
|
||||||
local->parent->variables[variable] = container;
|
assignVariable(variable, container, "parent");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(!keywordDefined("scope") || (resolveKeyword("scope") == "global"))
|
if(!keywordDefined("scope") || (resolveKeyword("scope") == "global"))
|
||||||
global.variables[variable] = resolveKeyword("value");
|
assignVariable(variable, resolveKeyword("value"), "global");
|
||||||
else if(resolveKeyword("scope") == "local")
|
else if(resolveKeyword("scope") == "local")
|
||||||
local->variables[variable] = resolveKeyword("value");
|
assignVariable(variable, resolveKeyword("value"), "local");
|
||||||
else if(resolveKeyword("scope") == "parent")
|
else if(resolveKeyword("scope") == "parent")
|
||||||
local->parent->variables[variable] = resolveKeyword("value");
|
assignVariable(variable, resolveKeyword("value"), "parent");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,12 @@ namespace jet {
|
|||||||
void renderVariableName(coreutils::ZString &variable, coreutils::MString &name, coreutils::MString &modifier);
|
void renderVariableName(coreutils::ZString &variable, coreutils::MString &name, coreutils::MString &modifier);
|
||||||
void storeVariable(coreutils::ZString variable, coreutils::MString value, coreutils::ZString scope);
|
void storeVariable(coreutils::ZString variable, coreutils::MString value, coreutils::ZString scope);
|
||||||
void storeVariable(coreutils::ZString variable);
|
void storeVariable(coreutils::ZString variable);
|
||||||
|
// Single write path shared by both storeVariable overloads. An
|
||||||
|
// "@name"-prefixed variable is routed to the active JSON session
|
||||||
|
// file (global.sessionFile) instead of the normal global/local/parent
|
||||||
|
// variables maps, mirroring getVariable's "@" read branch.
|
||||||
|
void assignVariable(coreutils::ZString variable, coreutils::MString value, coreutils::ZString scope);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool containerOnly = false;
|
bool containerOnly = false;
|
||||||
coreutils::ZString splitTagName;
|
coreutils::ZString splitTagName;
|
||||||
|
|||||||
@@ -17,19 +17,33 @@ namespace jet {
|
|||||||
if(keywordDefined("sessiondir")) {
|
if(keywordDefined("sessiondir")) {
|
||||||
global.session = true;
|
global.session = true;
|
||||||
global.cookies = getenv("HTTP_COOKIE");
|
global.cookies = getenv("HTTP_COOKIE");
|
||||||
|
coreutils::MString sessionIdValue;
|
||||||
if(global.cookies.data["session"] != NULL) {
|
if(global.cookies.data["session"] != NULL) {
|
||||||
// pull sessionfile from sessiondir.
|
sessionIdValue = global.cookies.data["session"];
|
||||||
// if last activity time is expired then ignore.
|
// if last activity time is expired then ignore.
|
||||||
// follow sessiontimeoutredirecturl.
|
// follow sessiontimeoutredirecturl.
|
||||||
} else {
|
} else {
|
||||||
SessionId sessionId;
|
SessionId sessionId;
|
||||||
global.headers["Set-Cookie"] << "session=" << sessionId;
|
global.headers["Set-Cookie"] << "session=" << sessionId;
|
||||||
global.cookies.data["session"] = sessionId;
|
global.cookies.data["session"] = sessionId;
|
||||||
|
sessionIdValue = sessionId;
|
||||||
if(keywordDefined("sessiontimeout")) {
|
if(keywordDefined("sessiontimeout")) {
|
||||||
time_t timeout = time(0) + keywords["sessiontimeout"].asInteger();
|
time_t timeout = time(0) + keywords["sessiontimeout"].asInteger();
|
||||||
}
|
}
|
||||||
// also save last activity time in session file.
|
// also save last activity time in session file.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JFile's own RAII behavior (see CoreUtils MFile.cpp) loads
|
||||||
|
// whatever JSON is already at this path, or starts empty if the
|
||||||
|
// file doesn't exist yet (a brand-new session), and writes any
|
||||||
|
// changes back automatically when destroyed - no explicit save
|
||||||
|
// call needed anywhere.
|
||||||
|
coreutils::MString sessionFilePath;
|
||||||
|
sessionFilePath << resolveKeyword("sessiondir");
|
||||||
|
sessionFilePath << "/";
|
||||||
|
sessionFilePath << sessionIdValue;
|
||||||
|
sessionFilePath << ".json";
|
||||||
|
global.sessionFile = new coreutils::JFile(sessionFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
coreutils::ZString requestMethod(getenv("REQUEST_METHOD"));
|
coreutils::ZString requestMethod(getenv("REQUEST_METHOD"));
|
||||||
|
|||||||
Reference in new issue
Block a user