A PHP munkamenet-kezelés buktatói
PHP-ben a beépített munkamenet-kezelő átírásáról nem található elegendő információ a kézikönyvben, ezért gondoltam összeszedem az ismereteimet ezzel kapcsolatban, hogy másnak már könnyebben menjen. (Az itt leírtak PHP 5.2.17-re érvényesek.)
Ha meglévő kódhoz akarunk munkamenet-kezelőt írni, és meg akarjuk tartani a $_SESSION
szuperglobális használatát, akkor problémák sorozatába ütközünk.
Munkamenetek tárolása
Kezdjük az elején: saját munkamenet-tárolót PHP-ben a session_set_save_handler()
segítségével állíthatunk be. Ennek bemutatására csináltam egy kis adapter vázat:
class SessionStoreAdapter
{
public function __construct() {
session_set_save_handler(
array($this, 'openSession'),
array($this, 'closeSession'),
array($this, 'readSession'),
array($this, 'writeSession'),
array($this, 'destroySession'),
array($this, 'collectGarbage')
);
// Oldal generálása után a munkamenet mentése (hiba és kivétel fellépése esetén is lefut)
register_shutdown_function('session_write_close');
}
public function openSession($savePath, $cookieName)
{
// Munkamenet nyitása
// Egy session header automatikusan küldésre kerül (bufferrel lehetséges elfogni)
// Kapcsolat nyitása a tárolást végző rendszerhez kerülhet ebbe a részbe
return true;
}
public function closeSession()
{
// Munkamenet zárása
// Kapcsolat zárása a tárolást végző rendszerhez, ami ide kerülhet
return true;
}
public function readSession($id)
{
// Adatok kiolvasása az id alapján
// Sztringet vár vissza a szerializált adatokról
$data = array(
'testBoolean' => true,
'testString' => 'test string',
'testInteger' => 13
);
return $this->encode($data);
}
public function writeSession($id, $encodedData)
{
// Adatok tárolása az id alapján
// Az adatokat szerializálva kapjuk
$data = $this->decode($encodedData);
return true;
}
public function destroySession($id)
{
// Adatok törlése az id alapján
return true;
}
public function collectGarbage($lifeTime)
{
// Szemétgyűjtés
// Adatok törlése, ha régebben módosultak, mint a lifeTime
// A lifeTime – mint minden „time” végződésű változó – másodpercben értendő
return true;
}
public function encode(array $data)
{
// Így működik a beépített szerializáló
$encodedData = '';
foreach ($data as $key => $value) {
$encodedData .= $key . '|' . serialize($value);
}
return $encodedData;
}
public function decode($encodedData)
{
// Így csomagol ki a beépített szerializáló (a kézikönyvből másolt kód)
$encodedDataSplitterPattern = '/([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff^|]*)\|/';
$entries = preg_split($encodedDataSplitterPattern, $encodedData, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
$data = array();
for ($index = 0, $entriesCount = count($entries); $index < $entriesCount; ++$index) {
$key = $entries[$index];
++$index;
$value = unserialize($entries[$index]);
$data[$key] = $value;
}
return $data;
}
}
Ez a kérdés elég egyszerűen megy, megkapjuk az adatokat, mentjük őket, aztán meg kiolvassuk, ha a következő oldalon újra lekérik őket. Már itt is látszik, hogy csak limitált eszköztárat kaptunk, mert eleve szerializálva kapjuk meg az adatokat, és ilyen formában is várja őket vissza a munkamenet-kezelő rendszer. Tetszőleges módon csak akkor szerializálhatjuk őket, ha használjuk az adapterben a decode()
és encode()
metódusokat. (Ezek rekurzióra nincsenek felkészítve.)
Munkamenetek kezelése
Most nézzük meg a beépített munkamenet-kezelő függvényeket, és azt, hogy ezek mit csinálnak.
A session_start()
függvény állítja be a válaszban a munkamenethez kapcsolódó fejléceket. Az adapteren az openSession()
és a readSession()
metódusokat hívja; beolvassa az adatokat a $_SESSION
szuperglobálisba. Ha a tartalom kirajzolása után próbáljuk indítani, akkor hibával tér vissza, hiszen nem lehet fejlécet küldeni, ha már a tartalmat írjuk. Egynél többszöri meghívása garantáltan hibaüzenethez vezet.
A session_id()
nevű függvénnyel saját munkamenet-azonosítót állíthatunk be az aktuális munkamenetünknek. A beállítással kapcsolatban ami fontos, hogy a session_start()
előtt kell megtenni, ellenkező esetben nem veszi figyelembe a munkamenet-kezelő. (Erről hibaüzenetet nem kapunk.)
A session_name()
függvény a session_id()
-hez hasonlóan működik, a session cookie nevét lehet beállítani vele.
A session_regenerate_id()
új munkamenet-azonosítót állít be, újraküldi a fejlécet az új azonosítóval, hogy felülírja a régi fejlécet. Így a kimenetben mindkettő ott lesz egymás után. Ha azt szeretnénk, hogy a régi munkamenetre meghívja az adapteren a destroySession()
-t, akkor session_regenerate_id(true)
formában kell hívnunk. Ami érdekes, hogy az adapteren semmit nem hív, tehát semmi garancia nincs arra, hogy olyan azonosítót kapunk, ami még nincs bent a rendszerben, tehát kicsi az esélye, de elviekben simán megkaphatjuk akár mások munkamenetét is. (Ezzel a függvénnyel a session fixation támadásokat lehet kivédeni. Ha valaki fixált munkamenettel érkezik, akkor a bejelentkezéskor érdemes megváltoztatni az azonosítót, így a támadók által beállított nem kapja meg az áldozat jogait a bejelentkezéskor.)
A session_commit()
és session_write_close()
függvények gyakorlatilag azonosak. Mindkettő menti a munkamenetet, és bezárja azt, ennek megfelelően a writeSession()
és a closeSession()
metódusokat hívják egymás után az adapteren. A PHP fejlesztői szerintem nem voltak a helyzet magaslatán, amikor ezeket a függvényeket alkották, és ez abból is látszik, hogy két eltérő nevű függvény csinálja ugyanazt. Én úgy gondolom, hogy a session_commit()
-nak nem lenne szabad bezárnia a munkamenetet, főleg nem úgy, hogy utána az adaptert újra be kelljen állítani újraindításkor. (Ha úgy hívjuk meg őket, hogy nincs nyitott munkamenet, akkor nem kapunk hibaüzenetet.)
A session_destroy()
törli az aktuális munkamenetet. Az adapteren a destroySession()
és a closeSession()
metódusokat hívja. Persze a beállított session cookie-k így is kimennek. Törlés után nem lehet új munkamenetet indítani.
Összességében szerintem a beépített munkamenet.kezelőt nem érdemes használni a benne lévő biztonsági és logikai hibák miatt. Én éppen saját munkamenet-kezelőn dolgozom, ettől függetlenül csináltam adaptert a beépítetthez, hogy például egy eleve arra épülő oldalt könnyebb legyen refaktorálni.
■
Jövőre írok egy bejegyzést a
Hol vannak a problémák? Ha
Ha jól értem egyetlen egy gond az az, hogy serializálva kapod meg az adatokokat.
Tudnál mutatni egy példát, hogy miért nem jó ez?
Azt írod nem található elegendő információ a kézikönyvben, de én nem látom azt, hogy részletesebben foglalkoznál a kérdésekkel, mint az ott bemutatott példa és leírás. Vagy ez valaminek a kezdete?
Pontosan milyen biztonsági problémákról és logikai hibákról van szó?
A fő probléma, hogy
- nem tudsz két munkamenetet párhuzamosan futtatni
- nem tudod több részletben menteni a munkamenet információit, mert egy mentés után lezárul (erre a commit-nál meg a write_close-nál utaltam)
- ha töröltél egy munkamenetet, nem tudsz a helyére nyitni egy másikat
- nem tudsz az általad megadott módon szerializálni (a következő blog bejegyzésemben, majd leírom, hogy erre mikor van szükség)
- ha elindult a munkamenet, akkor nem tudod beállítani a session id-t, csak a regenerate-el lehet új id-t megadni
- a regenerate meg nem nézi, hogy létezik e már session vagy sem a generált id alatt, tehát két munkamenet simán összeakadhat (ennek megoldását a statisztikára bízták, ami szerintem biztonsági szempontból nem jó megoldás)
Ha gondolod szívesen felvázolom, hogy szerintem milyennek kéne lennie egy session kezelő rendszernek. Hmm azt hiszem, ebből egy rövidke sorozat lesz, aminek következő részében a szerializálásról írok, utána meg arról, hogy ezt a kettőt hogyan lehet összehozni. Igazából erre nincs túl sok időm, meg amit ilyen elven építettem az használ még ezer másik osztályt, uh. ezekre a blogbejegyzésekre még várni kell.
egy kliens egy munkamenet
A kiírás problémája számomra eddig nem volt probléma. Az elképzelés (szerintem) az mögötte, hogy a session_start() hívásától a script futás lehető legutolsó pillanatáig lehessen írni olvasni a $_SESSION-t. Ezt ugye úgy tudjuk elérni, hogy a leállítási folyamatban úgy történik meg a kiírás, hogy te neked azzal már ne kelljen foglalkozni.
Az alap regenerate új, pillanatnyilag nem használt ID-t generál, ennél fogva, nem is akadhat össze egy már érvénytelenített munkamenettel.
Tapasztalatom szerint, aki neki állt az alap session folyamatot újra gondolni, megjavítani, rengeteg problémát csinált magának. És nekem is, mert arra már nem tellett, hogy a session név helyett másikat keressen. Én meg a nulla dokumentáció mellett (szerintem jogosan) azt vártam, hogy minden megy a megszokott mederben. Ehhez képest random helyeken random problémák jöttek elő. Elkezdtek össze akadni munkamenetek. Egyszer ott volt a berakott adat, másszor nem, és sorolhatnám…
Az meg, hogy neked az alap szerializálási mód nem tetszik, lelked rajta. Ha készítesz egy saját munkamenet kezelőt akkor olyan formában írod és olvasod ki az adatokat ahogy jól esik.
vesszek meg ha értem, hogy
GC-nél néha jó kiszedni az adatokat a munkamenetből. Mondjuk ha van egy webshopod ami levonja a raktárkészletből, hogy mi van a kosárban, akkor a destroy-nál a megsemmisülő kosarakból ki kell szedni a termékeket a kosárból és visszatenni a raktárkészletbe... (Ezt kicsit félreérthetően írtam le, ehhez is több párhuzamos Session példány kell, de nem vezetünk több párhuzamos munkamenetet, csak az adatokat olvassuk ki belőlük.)
Ha az alap regenerate egy szót sem szól a save handler-ben megadott függvényekhez, akkor hogyan garantálja, hogy pillanatnyilag nem használják a munkamenetet? Onnan kéne lekérdeznie neki, hogy létezik e már fájl a session id-hez...
Az alap szerializálási móddal nincs különösebb baj, de azért néhány extra hiányzik belőle. Nekem konkrétan az hiányzik, hogy nem lehet a sleep-nek és a wakeup-nak paramétereket átadni. Szívesen átadnék mondjuk egy listát, hogy mit ne szerializáljon (pl adatbázis kapcsolat), visszaállításnál meg egy listát ami alapján a nem szerializált részeket vissza lehet állítani. Mondjuk erre lehet workaround-ot csinálni...
A PHP 16 bájtos véletlen
Egyet kell, hogy értsek tikuval, ha nincs nyomós okod rá, ne piszkálj hozzá a sessionkezeléshez, csak rontani fogsz rajta. (Pl. a te példakódod fatal errornál elveszti a sessiont.)
Ezzel szemben ha minden
Nem teljesen világos, hogy miért kéne minden request-re plusz egy fájl lekérdezést csinálni.
Ne haragudj, de nem értem, hogy milyen példakódról beszélsz. Amit kitettem az egy Adapter vázlat. Amit szintén nem értek, hogy miért kellene fatal error-ra felkészítenem bármit is?
Egyébként ha jól tudom, akkor ez teljesen független a session kezeléstől, a register_shutdown_function-ben megadottak hibától függetlenül mindig lefutnak.
A statisztikai részével
Minimális eséllyel mindig történhet probléma, például a memóriában a bitek időnként random módon átváltanak a kozmikus háttérsugárzás miatt, és ennek az esélye még crc-vel is sokkal nagyobb (10^-20 fölött van / óra), mint a véletlen session id ütközés (10.000 párhuzamos sessiont és másodpercenként egy új sessiont feltételezve ~ 10^-40). Praktikusan persze ezek soha be nem következő esélyek, miközben a programozói hiba esélye viszonylag nagy (jó minőségű, tesztelt kódban is kb. ezer sorra esik egy hiba), tehát arra érdemes optimalizálni (pl. nem piszkálni feleslegesen a session kezelő kódot).
Hát ha minden requestnél generálsz egy session id-t, és meg kell nézned, hogy létezik-e már... (jó, minden session süti nélküli requestnél, de tipikusan a requestek nagy része olyan.)
Mert valószínűleg nem szeretnéd, ha a sessionbe írt adatok fatal errornál nem mentődnének.
PHP verziótól függően esetleg a garbage collection után, vagyis mire mentenéd a sessiont, már nincs meg az adapter objected.
Minimális eséllyel mindig
A franc, akkor jobb, ha nem is csinálok semmit, mert a kozmikus háttérsugárzás úgyis keresztbe tesz :D Oké, akkor ezt így elfogadom. :-)
Szerintem ez teljes mértékben oldal függő... Mondjuk ha egy webshopról van szó, akkor már az elején adni kell egy session-t, hogy lehessen mibe betenni a kosarat...
Nem tudom te hogy vagy vele, de én tesztelni szoktam a kódomat, és azért egy fatal error elég durván ki szokott jönni ezeken a teszteken...
Fatál?
De, gyanús :-) De továbbra
A session-t cookie alapján azonosítja, ha a böngészőben megmarad a cookie, akkor miért veszne el a munkamenet?
A hagyományos session kezelő ugyanúgy nincs felkészítve ilyen problémákra, és fatal error esetén ugyanúgy azért menti csak el a munkamenet változásait, mert a register shutdown function-ben megadtad a session write close-t... Ha meg elfogy a hely a vinyón, akkor meg ugyanúgy meghal az is... De kíváncsi vagyok továbbra is, hogy miben jobb az a megoldás, esetleg kihagytam valamit?
Passz...
Szóval pusztán arra akartam reagálni, hogy szerinted nem kell foglalkozni a fatal errorokkal, amit nem feltétlenül tartok jó ötletnek. ;)
Szóval pusztán arra akartam
Azt hiszem ezt hívják az érveléstechnikában a vita manipulálásának. :D Én olyat nem mondtam, hogy nem kell foglalkozni a fatal error-ral, természetesen loggolni kell, már ha erre van lehetőség. Többet úgysem tehetsz... A fatal error-ból nem tudsz ErrorException-t csinálni, szóval max annyit tehetsz, hogy display errors off. Ennél többet szerintem senki nem tud csinálni vele... Elmondom harmadszor is, amit a register shutdown function függvénnyel beállítasz, az fatal error esetén is lefut, tehát ha van lehetőség a session mentésére, akkor teljesen mindegy, hogy milyen a session kezelőd, ha beállítod a register shutdown function-nel, hogy fusson le a session mentése, és erre lehetőség van, akkor az lefut.
Mi futottunk már ilyen
Nekem szerencsém volt... :)
Nálunk igazából a memória
Az vicces :D Mondjuk ez a
Mondjuk ez a része szerintem már abszolút üzemeltetési kérdés, és nem kódolási...
Mondjuk úgy, itt kezdődnek a
Ez szerintem is programozói
Jah előfordul... Mondjuk
A shutdown handler elején meg
Köszi, utána fogok járni.
Mondjuk ha egy webshopról van
A látogatók nagy része a kezdőlapról visszafordul, úgyhogy a látogatások nagy része session süti nélkül kezdődik akkor is, ha mindig adsz session sütit, amikor még nincsen.
Egy részük ki szokott jönni, más részük meg benn szokott maradni, pl. mert ritkán használt kód-útvonalon van a hiba, vagy változók aktuális értékétől függ.
Maga a munkamenet nem veszik el, de amit az adott requestben módosítottál rajta, az igen. Ami elég nagy probléma lehet, ha olyan adatokat tárolsz a sessionben, amiket adatbázisban illene (a DB módosításaid elmentődnek, a session módosításaid nem, és inkonzisztens adataid lesznek), de anélkül is okozhat kellemetlenséget (pl. duplaposzt kivédésre raksz a sessionbe poszt azonosítót, user végez valami adatbázisműveletet, mentés után fatal errora fut -> white screen of death-t kap -> frissít -> duplán került az adatbázisba a művelet).
A session_write_close-t a PHP alapból regisztrálja valami shutdown-handler-szerűségként, de nem ez a lényeg, hanem hogy a default session kezelő, vagy akár egy kézzel írt, de nem objektumorientált vidáman lefut ilyenkor, a te sessionkezelő objektumodat viszont eddigre már megette a garbage collector.
Egyébként vannak olyan - nem is különösebben súlyos - hibák, amiktől a PHP elszáll fatal errorral, és nem hívja meg a shutdown functiont (pl. ha egy null objektumnak próbálod meghívni egy függvényét), úgyhogy még csak arra sem lehet alapozni, hogy a shutdown handler mindig meghívódik.
a te sessionkezelő
Fura, ezt én nem tapasztaltam. Nekem az a benyomásom a shutdown function-nel kapcsolatban, hogy a szemétgyűjtés előtt fut le, tehát még minden objektum érintetlen ilyenkor... (Fatal error-ral csak az adaptert próbáltam ki, megnézem, hogy a többi objektum megmarad e vagy sem, de az a gyanúm, hogy igen ...)
Kieg: Ironikus módon igazad lett, tényleg nem megy fatal error esetén a mentés, viszont nem a GC, hanem egy 2002 óta nem javított bug miatt: https://bugs.php.net/bug.php?id=19022
Érdekes hozzáállás, hogy 10 éve nincs feedback egy létező bughoz...
Az alapján, amiket olvasok nekem úgy tűnik, hogy fatal error esetén nem működik az adapteres megoldás, mert valamiért nem veszi figyelembe ilyenkor a php a session_set_save_handler-ben beállított dolgokat. Azt hiszem csinálok erről egy bug reportot... Még azért tesztelem előtte az újabb verziókkal is. (Tisztán adapter nélkül még nem próbáltam...) Egyébként azt írják, hogy auto_append_file-al megy, de azt még nem teszteltem...
Verziófüggő, én PHP 5.3-nál
A PHP bugtrackert minden valószínűség szerint egy csapat szociopata tervezte, akik azon versenyeztek, ki tud jobban kib***ni a PHP közösséggel. Az egyik zseniális húzásuk az volt, hogy a bugs.php.net-re nem lehet regisztrálni, hanem egy jelszót kell megadnod a hiba jelentésekor, és csak annak birtokában lehet módosítani. Így aztán ha egy hibát no feedbackre tesznek, és az eredeti bejelentő nem aktív (vagy csak elfelejtette a jelszavát), az már örökre úgy marad. Láttam már egészen durva biztonsági hibától kezdve mindent, ami erre a sorsra jutott.
Fantörpikus :D Az igazi
Milyen verzió?
Valamit nem értek: az általad linkelt bug még 1.3-as Apache + 4.2.2-es PHP alatt lett jelentve. Ugyanez Apache2.x és mondjuk 5.2-es v. 5.3-as PHP alatt is megvan? Vagy a PHP4-es sorozata még mindig támogatott lenne?
5.2.17 alatt volt, de nem
Na teszteltem, kiderült, hogy
Amúgy teljesen jól működik az adapteres változat is:
A shutdown function működik a
Amit korábban írtál, abból az
A boltos példádban említett két problémádra volna itt egy-egy megoldási javallat:
Vegyünk azt a nem túl bonyolult esetet, amikor a lehető legegyszerűbb módszerrel adatbázisban tárolod a munkamenet adatokat (
session <id[char32]>, value<blob>, updated<datetime>
). Csak aread
éswrite
handlereket kell megírnod, plusz egy takarító metódust, ami időről időre törli a session táblából a lejárt adatokat. Ehhez ugye elengedhetetlen, hogy minden alkalommal amikor egy session-t indítasz, akkor az utolsó módosítás dátumát frissítsd.A problémádat úgy szoktuk hívni, hogy elhagyott kosár. Képzeld magad egy kiskereskedő helyébe, aki zárás után körbe járja az üzletét és hátul a sarokban talál egy pár teli pakolt kosarat. Szerinted mit fog kezdeni? Melyik módszer a hatékonyabb?
Ha ez a raktárkészlet csökkentés problémát jelent, akkor
- a vásárlás végén, amikor már tuti, hogy elviszik azt a darabot, csökkentsd a raktárkészletet
- Ne Session változóként tárold a kosár tartalmát, hanem pl egy kapcsoló táblában (session id, termék id, mennyiség) tárold a fogott darabokat. Így, ha a kapcsolat foreign key segítségével rendesen ki van építve, akkor egy session rekord törlésekor automatikusan törlődik a foglalás. Az aktuális raktárkészletből meg egy SUM SELECT-el egyszerűen ki tudod vonni a fogott darabokat
.Ha meg a regeneratt-et is már felülírod, akkor generáltathatsz addig amíg nem állít elő egy olyat, ami még nincs az adatbázisban.
Lehet én értek valamit félre, de a __sleep megírásával nem épp azt mondod meg, hogy "ha ennek az osztálynak egy pédlányát valaki beküldené a serialize-ba, akkor csak ezt és ezt az objektum tulajdonságomat írd ki!"? A __wakeup-ban meg megírhatod a nem kiírt tagváltozók feltöltését.
Amit korábban írtál, abból az
=>
Úgy érzem nem érted a problémát a webshopos példánál. A munkamenet mondjuk fájlrendszerben vagy memóriában van, hogy ne nyírja ki az sql szervert. Az sql szerveren meg az adatbázisban van bent, hogy melyik termék van lefoglalva. Amikor kosárba kerül egy termék, akkor lefoglaljuk. Ezután kikerülhet úgy is a kosárból a termék, hogy a felhasználó törli meg úgy is kikerülhet, hogy a munkamenet semmisül meg, és maga a kosár törlődik. Ez utóbbit a GC csinálja. Na mármost valahogy össze kellene kötni a munkamenet törlését és az adatbázisban a termékek újra aktiválását. Kíváncsi vagyok te hogyan oldanád meg ezt?
Az én logikám szerint, amikor a vásárlók leadják a kosarukat, akkor megnézem, hogy maradt e benne valami, és ha igen, akkor mindent visszapakolok belőle a polcokra. Nálam meg a vásárlók nem hagyják el a kosarukat. ;-)
Ez így van, de ez a rendszer rugalmatlan, mert nem lehet paraméterezni. A feltöltés része csak global-ból vagy singleton-ból mehet, mert csak onnan tudja kiolvasni a __wakeup, hogy mivel töltse fel őket. (A másik lehetőség, hogy a wakeup-ban példányosítasz mindent, ez viszont nem szerencsés az erőforrásoknál.) A __sleep-pel szintén az a baj, hogy nem automatizálható, ami meg könnyen kódismétlődéshez vezet. Sajnos ezekre csak erőforrás igényes megoldás van. :S
utolsó pillanatban
A
vásárló munkamenetébenB
,C
ésD
felhasználó munkamenet adatai között fogsz turkálni. És ez változatos biztonsági kérdéseket vet fel.Fentebb már leírtam a legegyszerűbb megoldást: a készletet csak akkor csökkentsd, amikor már tuti, hogy eladtad a darabot.
Ha a foglalásokat (már kosárba tett, de még nem megrendelt) is akarod rögzíteni, akkor azt továbbra se session változóba tedd, hanem adatbázisba. Minden kosár hivatkozzon egy session ID-ra. Munkamenet változóban a kosárral kapcsolatos minden adat maradhat, de a betett termékek azonosítója és a fogott darabszám egy központi ("nyilvános") helyre kerüljön. Az adatbázisban tárolt adatoknak természetesen egy utolsó aktivitás mező is kell, hogy a lejárt rekordokat automatikusan törölhesd. Ha az adatbázis terhelést csökkenteni szeretnéd, akkor legyen a munkamenetben egy flag, hogy van-e a kosárban termék. Ha nincs, akkor nem kell frissíteni a DB-ben tárolt adatokat.
<off>
Úgy érzem, az alap probléma megoldásához alapvetően rossz utat választottál. Olyan mintha a feladatod egy szeg beverése lenne, az eszközöd meg egy turmix gép. Amikor az első pár ütés után nem megy, neki állsz, hogy a kezedben levő eszközt alkalmassá tedd szeg beverésére. Pedig neked egy kalapácsra van szükséged. Esetleg egy szegbelövőre.
</off>
A fő baj az általad vázolt
Konkrétan a CRON/collectGarbage munkamenetében fogok B, C és D vásárló munkamenet adatai között turkálni. Ez milyen biztonsági kérdéseket vet fel?
Ez egy konkrét webshopban egy megrendelői igény, amit meg kellett valósítanom...
Értem, tehát azt javaslod, hogy minden egyes oldallekérésnél küldjem el az adatbázisnak, a frissítés időpontját, hogy ne törölje x idő után a termékeket a kosárból. Hát kösz, de nincs kedvem agyonterhelni az sql szervert...
Ez egy konkrét webshopban egy
Bocs a kissé drasztikus fogalmazásért, de ha a megrendelő hülyeséget kér, esetleg meg lehet győzni róla, hogy nem úgy kellene megoldani, ahogy ő képzelte.
Anélkül, hogy a részletekrő bármit tudnék: ha félbeszakad egy vásárlás, te meg már lekönyvelted, hogy csökkent a készlet, abból elég csúf dolgok jöhetnek ki...
A kérés nem feltétlenül
Itt konkrétan egyedi termékek
Nem a konkrét kérésre írtam,
----
Egyébként ha valaki kis raktárkészlettel dolgozik, akkor sem fogom neki lekönyvelni a készletcsökkentést azért, mert valaki online a kosarába tett egy terméket.
A termék mellett bejegyzem, hogy kosárban van, de ez a kosár az a session megszűnésekor törlődik és így automatikusan visszakerül készletre a meg nem vett darab is.
Anélkül, hogy a részletekrő
Egy terméknek több állapota van, pl: "szabad", "kosárban van", "elkelt", stb... A session destroy-ra (ill. GC-re) meg pont ezért kell egy eseménykezelő, ami visszaállítja "szabad"-ra a kosárban lévő termékek állapotát. Nem hiszem, hogy ebből bármi csúf dolog jöhetne ki, működni működik, ennyi. Ha esetleg az SQL nem fut le, akkor meg a GC elszáll exception-nel és nem törli a session fájlt, tehát a következő GC hívás javítja a hibát... Nyilván oda kell figyelni, hogy szinkronban legyenek a session fájlok és az adatbázis, de ez nem egy annyira bonyolult probléma...
Amire reagáltam, abban nem
ui: hozzászólás után csak nekem ugrik rossz helyre a böngésző? Eddig az új hozzászólásra ugrott, most meg mintha véletlenszerűen kiválasztott helyre dobna vissza.
Abban igazad van, hogy a
Valami hasonló gondolataim
(úgy egyébként meg egyről beszélünk, csak rosszkor szóltam közbe ;) )
a probléma és az ő gyökere
Ezt DB agyonterhelési történetet azért jó lenne valami kísérlettel, meg mérésekkel ellenőrizni. De ha már kipróbáltad, akkor elhiszem neked.
Végig gondoltam a felvetett problémát, és a következőre jutottam:
Az én szememben a munkamenetben tárolt adatok kizárólag két szereplőre tartoznak, a kliensre és az aktuális PHP alkalmazás példányra. Az egész session pedig arra való, hogy az azonos kliens által, egymás után futtatott alkalmazás példányok között megosszak adatot, és nem arra, hogy
A
kliens információhoz jussonB
,C
ésN
felhasználó adataiból, még haA
kliens egy Cron futtatta takarító script is.Ha egy kliens adatot másik kliens számára is láthatóvá szeretnék tenni, akkor azt nem teszem munkamenet változók közé, hanem egy nyilvános helyen fogom tárolni, és a session változókból hivatkozok erre a közös forrásra. A közös tároló hely pedig lehet DB, lehet valamilyen NoSQL megoldás, vagy bármi más.
Ha jól értelek, akkor számodra a munkamenet és a benne tárolt adatok nem számítanak ennyire tabunak.
Ha jól értelek, akkor
Jól értesz. Nálam ugyanolyan adatok, mint az adatbázisban tároltak, vagy bármi más. Pl. az adatbázisban ugyanúgy érzékeny információk vannak, mint a munkamenetekben, és ugyanúgy az összes script tud bele írni ... Biztos van valami logikus magyarázat arra, hogy nálad ez miért ekkora tabu, lehet, hogy valahol már előjött probléma ezzel a megközelítéssel kapcsolatban, vagy így oktatták, passz. Én jelenleg nem tudok elképzelni olyan helyzetet, ahol ez különösebb veszélyforrás lehetne az általad felvázolt tárolási módhoz képest, de ha tudsz ilyet mondani, akkor elfogadom, hogy igazad van.
Általában jó ötlet, ha az
Ahm, hát ha nem tisztán
Itt igazából az a gond, hogy a session változás és az adatbázis változás mentése nincs egy tranzakcióban, így ha a session és adatbázis mentések között lép fel hiba, akkor inkonzisztens lesz a két adattároló... Ezen valamennyit segítene ha rögtön adatbázis mentés után mentenénk a session változásait is... (Ezért írtam korábban, hogy jobb lenne, ha a session_commit csak mentene, de nem zárná le a sessiont, ilyen esetben pl használható lenne...)
Alias
Nem összehasonlítottam,
Alias
write_close
, valójában acommit
végleges befejezése valaminek, tehát szintén "benne van" a lezárás. Szerintem azért volt szimpi, mert csak egy szó, és szerintük jól kifejezi amit csinál. A save, load inkább fájlműveleteknél használatos. Persze lehet mondani: a süti is fájl, de azért a session az már nem.$_SESSION ?
Miért akarod megtartani? főleg ha:
"nem tudsz két munkamenetet párhuzamosan futtatni"
-t hiányolod.
Haha, kezdem azt hinni, hogy
PHP verziószám?
A felvetett problémák mennyire vannak a megadott verzióhoz kötve?
Még nem néztem az 5.4 újdonságait, az ott bevezetett SessionHandler fog változtatni ezen?
Azért, mert csak 5.2.17 alatt
Tudtommal a zárójel nem kiemelés, de javíts ki, ha tévednék. Type hint nincs a kódban, tehát 5.0-val is menne, és gondolom 4-es verziónál is, ha a public-ot kiszednénk mindenhonnan.
Egyébként elég triviális oka van, hogy odaírtam: csak 5.2.17 alatt néztem meg, hogy hogyan működik. Egyébként remélem a SessionHandler majd változtat rajta (jó irányba).
Re: 5.2.17
public function encode(array $data)
Nem a szavakon lovagolok, de ha valamire azt írják, hogy ezzel a verzióval érvényes, akkor arra gondolhatunk, hogy ezzel érvényes, de mással nem biztos. Triviálisan erre kérdeztem rá, hogy van-e, volt-e, lesz-e olyan PHP változás, ami érinti a fentieket?
Merthogy nem az volt odaírva, hogy ezzel teszteltem, de valójában a ma használatos stabil verzióval ugyanúgy érvényes lenne.
(zárójel, ami nem kiemelés: Szerintem ami a nyitóoldalon van bevezető szöveg az kiemelés. :)
Hát akkor át kéne írnom a
Valószínűleg előbb vagy utóbb lesz olyan változás, ami érinti. Nem tudom, hogy mikor, mert a PHP-sek eléggé visszafele kompatibilitás mániások, ami meg szerintem nem tesz jót a nyelvnek, mert nekem úgy tűnik nem tesztelik és nem gondolják át eléggé a dolgaikat, tehát bőven lenne még mit változtatni rajta. Amiért nekem saját session implementáció kellett, az az egyedi szerializálás és unszerializálás, amit mondjuk egy Serializer cserével meg lehetne oldani. A másik ami miatt kellett az a CRON-ból hívott GC. Ezt mondjuk ki lehetne váltani az ini_set-es gc beállításokban való trükközéssel (érdekes, hogy 2 változóval kell megadni egy arányt), de az se túl szép, jobban tetszik, ha direktbe lehet hívni a GC-t. Ezt mondjuk a SessionHandler osztályon meg is lehet tenni, de ha jól tudom az 5.4-es php-tól van csak.
visszafele kompatibilitás
Ez tény, de egy verzió váltás
lefele kompatibilitás
Hát olyan megkötések mellett,
MySql vs. Session
Véleményem szerint a legtöbben nem kapnánk fel a fejünket, ha valaki azt írná, hogy ne az eredeti mysql_akármi függvényeket használjuk, hanem írjunk saját osztályt erre, elfedve a beépített függvényeket. Sőt, negatív példa lenne, ha nem így csinálnánk.
Ezzel szemben a session kezelés felülírása már messze nem ennyire egyértelmű. Még akkor, sem ha a felvetett problémák jogosak. Igaz, én nem érzem jelentős problémának, ha csak becslést tudunk adni a kosárban lévő termékek számáról.
(amúgy remélem, hogy a mysqlnd abba az irányba tett első lépés, hogy rendet tegyenek a MySql kapcsolat terén)
vagyis...
A beépített PHP
Szerintem nem lenne gyorsabb,
Persze, hogy nem lehet,
Tegyük fel, hogy a
Igen, ez logikus lenne.
Nézzünk egy buta példát:
Max. 10 kg kenyeret lehet venni, és 9 van a kosaramban. Mint a villám rányomok 2x a gombra, ami hozzáad még 1 kg kenyeret. A két kérés egyszerre beérkezik, egyszerre le is kezeli a szerver. Ehhez a kód nagyon lebutítva kb. így néz ki:
Azt meg aztán végképp nem kívánom senkinek, hogy tele legyenek ilyen problémákkal a fórumok, mert Pistike még nem hallott a threadekről.
Abban az esetben megvalósítható, ha lehetséges bizonyos feltételek mellett lock-ot tenni a sessionre. Ezt persze MySQL-el, vagy egyéb adatbázissal lehet saját úton.
És egy kérdés: Ha a kenyér hozzáadása hamarabb fut le, mint a login, kié lesz a kenyér a kosárban?
Egy anonim felhasználóé, aki
Amúgy jah, nem thread safe...
Egy anonim felhasználóé, aki
Amúgy jah, nem thread safe...
Egyszóval használhatatlan? :-)
Maga a példa, mert ugyanazt a
kieg:
Amúgy utánaolvastam kicsit ennek az ajaxos témának, és azt javasolják rá, hogy csináljunk egy queue-t, és csak akkor indítsuk a második ajax kérést, ha az első már lefutott... Így mondjuk nem aszinkron az aszinkron... :S
Érdemes lenne egy olyan session kezelőt készíteni, ami kiolvassa a fájlokat, de a mentés / felülírás csak akkor fut le, ha ténylegesen módosult is valami... Ez GC szempontjából nehezen megvalósítható, mert az meg a módosítás dátuma alapján gyűjtöget... :S :S :S Hát ez sem egy egyszerű probléma...
Na, így mindig csak adott
Hát nem tudom ez mennyivel
ha a write close-al lezárod a
Ezt ki mondta neked, mert hazudott. :-)
Nem ütköznek, mert amíg nyitva van, addig zárolja.
majd lesd meg milyen es hany
Tyrael
Azért furcsállom, mert
Ezt nem mondták, hanem
(Persze attól, hogy én 5.2.17-ben az én beállításaimmal errort kaptam még lehet, hogy másik verzióban működik, amit írsz...)
Megnéztem érdekességképpen a
Valójában elég régen volt már az, hogy ilyesmivel foglalkoztam phpban, így nem is emlékszem tisztán, ezért előfordulhat, hogy a régen működő kódom nem teljesen olyan, mint most emlékszem rá, de már meg sincsen nálam, olyan régen volt. :)
Hát nekem valami error-al
kieg:
Közben megnéztem a beépített session kezelő tényleg lockolja a fájlt amíg él a session. Azt hiszem talán olyan session kezelőt lehetne írni, ami figyelembe veszi mindezt, és tol egy sleep-et magára, hogy kivárja a lockolás végét...
Tévedtem, újra teszteltem, és
Oké, újabb érdekesség:
Minden session_start előtt be kell állítani a session_set_save_handler-el, hogy az adapter kezelje a session-t, ezért kaptam hibát. A blog bejegyzésben tehát nem igaz az, hogy nem lehet újraindítani a session-t, pusztán ilyen bugja van, hogy nem jegyzi meg a beállításokat...
Jó lenne ezt javítani a blog bejegyzésben is...
Ha valami nagyon párhuzamos
Közben találtam ilyet,
Ez nem egy shm-hez hasonló
Mindenesetre biztató, hogy egy daemon, így valószínű nem rossz az elve a dolognak.
OFF:
Érdekes, hogy mennyire fel tudta kelteni az érdeklődésemet ez a téma, hogy még a php kódjába is bele túrtam, pedig jelenleg nincs is rá szükségem, meg szerintem a közeljövőben se nagyon lesz. :-)
Egy hónapja találtam kb. ezt