Session refactoring ideas
- juzna.cz
- Člen | 248
Here are my ideas for refactoring Nette's session implementation.
I start with problems of current implementation,
then summarize all current requirements on new imlementation and also
add some new ones. Then, I propose new interfaces and briefly
discuss their usage.
These interfaces are then shown in details with some notes. Finally,
I show outcomes of this implementation and also present some new
extensions, which will be now possible to implement
and which weren't possible in old implementation.
Problems with current session:
- difficult mocking
- serializes requests – more requests cannot run in paralell
- almost no possibility for different storages
Paralell access
PHP's implementation of session opens session file when we call
sesssion_start() and locks it, so until we finish working with session and close
it, another request cannot be processed.
It could be good to implement session storage, where paralell access is
possible, e.g. by locking individual namespaces and not whole session
object.
All methods which are required:
Here is brief list of all features/methods in current implementation, which should be supported in new one.
get+set cookie_params
start
write_close
destroy
get+set name (session_name)
regenerateId
get+set id
configure - Nette\Session::configure
init storage - _SESSION['__NF']
send cookies - SESS_ID + Browser Key
pre-clean - before session is started, remove all expired data
clean
get+set+has+remove namespace
get namespace iterator
get+set parameters - like Counter, BrowserKey, ...
set+get+has+remove values from namespaces
Additional requirements:
- easy change of storage backend (classic, mysql, memcache, …)
- concurrent access – allow multiple simultaneous requests to work with different namespaces
- allow storage with locking – acquire lock for namespace
- storage can or can not rely on PHP's session implementation, e.g. it can set dummy methods for session_set_handler and load/write data when loading individual namespaces
- removing expired values lazily, i.e. not in all namespaces at once, but when namespace is loaded
Proposed interfaces:
- ISessionStorage – given session_id, handles requests for namespaces and/or parameters
- ISessionNamespace – manipulation of variables within namespace, can support locking; has data + its metadata + parameters (misc metadata not dependent on data)
- ISession – configures session, dispatches calls and events and retrieves namespaces
ISessionStorage:
- constructor($sessionId) – receives session id (but I think constructor shouldn't be in interfaces, any ideas?)
- basic features
- open/start() – loads session data and initializes structures (if needed)
- close() – write data to disk (if needed)
- destroy() – removes all session data
- clean() – clean structures (like remove empty namespaces)
- gc() – remove all old existing sessions (not just mine)
- global parameter handling
- {get+set+has+remove}Parameter(…)
- namespace handling
- {get+set+has+remove}Namespace(…)
- getAllNamespaces()
- misc
- changeId($newId) – after regenerateId has been called, it moves data to another sessionId
ISessionNamespace:
- constructor($storage, $name) – loads data from storage
- destructor – flushes data to storage
- flush()
- reload() – reloads data from storage
- manipulation with variables, like it is now
- set+get Expiration
- {get+set+has+remove}Parameter – namespace's metadata
ISession:
- start() – populaes BrowserKey + Counter + …
- close()
- destroy()
- configure()
- sendCookie()
- {get+set}CookieParams
- regenerateId
- {get+set}{Id+Name}
- {get+set+has+remove}Namespace
- gc() – removes expired values
Outcomes
- simple implementation of different storages by just implementing ISessionStorage
- mocking storage with e.g. just static variables
- mocking Session not to send any cookies, but allowing all storages to be used
- namespaces can be overriden and thus support locking (if it is supported by storage)
Future work
ILockableSessionStorage: (pesimistic locking, will not be part of Nette)
- get($namespace, bool $lock = false)
- set($namespace, $data, $metadata, bool $unlock = false)
Loclable namespace will then loads data with lock and in __destruct() call set() and unlock it
IVersionableSessionStorage: (optimistic locking, will not be part of Nette)
- get($namespace) → data, meta, version
- set($namespace, $data, $medatada, $prevVersion) – can throw exception when data has been modified
Not solved problems
- how to pass sessionId to SessionStorage?
- should cookie methods be in Session or rather in HttpRequest/HttpResponse? (as proposed by Ondra Mirtes)
Please discuss and provide your ideas.
Editoval juzna.cz (5. 3. 2011 0:06)
- Patrik Votoček
- Člen | 2221
Není tohle už náhodou pořešené? https://github.com/…tte/pull/206
Btw to už budeme i na českém fóru spíkovat englicky (Tak to jsem pěkně v ***)? Chápu že s tím přepínáním máš problém… :-)
- juzna.cz
- Člen | 248
V tom pull requestu je IMHO par dobrych veci, ale jinak to toho moc neresi. Chce se to poradne zamyslet a pak az neco implementovat. Proto prosim o pripominky, uz me zadne zadrhely nenapadaji, tak to zkusim naimplementovat jak pisu.
PS: bylo to moc textu aby se mi to povedlo napsat cesky, sorry :/ Ale snad nema nikdo problem to precist.
- David Grudl
- Nette Core | 8218
Proč by ne, pokud tvé úložiště nezamyká soubory, budou fungovat paralelně.
- juzna.cz
- Člen | 248
Pak ale zas budou mizet data, protoze session si na zacatku nacte vsecka data do pameti a na konci je pak zase ulozi.
Predstav si paralelni beh 2 requestu pouzivajicich session:
A...............end
B..end
A nacte data, zacne neco generovat a ukladat si to do session. Behem toho uzivatel klikne na B, coz neco ulozi do session, skonci a zapise data. Po chvili skonci A, serializuje celou promennou $_SESSION a ulozi. A ejhle, data z requestu B jsou najednou fuc.
Nebo jde toto nejak vyresit i s klasickou PHP session?
- Filip Procházka
- Moderator | 4668
V klasické tomu zabraňuje, že nemůžou zapisovat dva requesty zároveň. Ve vlastním handleru si přece můžeš ušetřit zamykání tabulek (MySQLDibiStorage) nebo popř. flockem (FileStorage)
- David Grudl
- Nette Core | 8218
juzna.cz napsal(a):
Pak ale zas budou mizet data, protoze session si na zacatku nacte vsecka data do pameti a na konci je pak zase ulozi.
Já vím, ale když to tak moc chceš ;-)
- juzna.cz
- Člen | 248
David Grudl: „Mily zakazniku, chcete aby ten system lagoval neco aby se ztracely data? Zvolte alespon jednu moznost“ ;-)
Jde session otevrit a zavrit vicekrat behem jednoho requestu? A nebo u techto pozadavku ty data hold budu cpat uplne nekam jinam, nez do session, kdyz se vam ty moje napady tak nelibi.
- JJWorren
- Člen | 6
Ahoj hele koukám že to tu už řešíte pokouším se naimplementovat ukládání session do DB a měl bych dotaz kde a jak nette říct že pro všechny sessions má používat můj DatabaseSessionStorage(implementuje ISessionStorage) logicky předpokládám že v bootstrapu něco jako toto
<?php
NEnvironment::getSession()->setStorage(new DatabaseSessionStorage(Model::tb_session_db()));
?>
- MartyIX
- Člen | 217
Vyřešil někdo nakonec tu paralelizaci? Pracuji se SESSIONs málo ve svém kódu (skoro jen přihlašování).
Zkusil jsem:
if ($user->isLoggedIn()) {
$identity = $user->getIdentity();
// Do some work
}
// The method $user->isLoggedIn() starts sessions and concurrent requests are blocked
// until session is closed.
// @link https://forum.nette.org/cs/6375-zamykani-session-blokovani-soucasnych-requestu
$this->context->session->close();
ale toto mi nezafungovalo, následující zápisy do $_SESSION se neuložily.
Něco přehlížím?
______________
Nette 2.0.10
- Jan Tvrdík
- Nette guru | 2595
následující zápisy do $_SESSION se neuložily.
Tak to je snad jasné, že po volání
$this->context->session->close()
se už nic do session
zapsat nedá. Nebo ne?
- MartyIX
- Člen | 217
Jan Tvrdík napsal(a):
následující zápisy do $_SESSION se neuložily.
Tak to je snad jasné, že po volání
$this->context->session->close()
se už nic do session zapsat nedá. Nebo ne?
No, není. Protože:
- Už David výše psal, že sessions jdou během běhu PHP skriptu vícekrát otevřít+zavřít.
- Nette otevírá sessions vždy při zápisu, viz: https://api.nette.org/…ion.php.html#…
- Jan Tvrdík
- Nette guru | 2595
@MartyIX: Už chápu, jak jsi to myslel. Zkus session
nastartovat znovu explicitně voláním start()