PHP bevat een standaard hulpstuk voor het gebruik van sessies (zie http://nl2.php.net/session voor meer informatie). Deze session-handler heeft een aantal fundamentele problemen, door de manier waarop sessies worden opgeslagen. PHP sessies zijn :

  • Onbetrouwbaar: er is geen enkele garantie dat de sessie voor een bepaalde tijd bestaat. Er zijn verschillende oorzaken waardoor een sessie eerder, of juist heel veel later kan verlopen dan je verwacht. Zo kunnen de bezoekers van je site plotseling worden uitgelogd, of kunnen ze juist na 3 maanden gewoon met dezelfde sessie inloggen.
  • Onveilig: andere gebruikers van dezelfde server, kunnen relatief eenvoudig jouw sessie data bekijken, of jouw sessies laten verlopen. Sla dus nooit gevoelige informatie in PHP sessies op!

Wat is de oorzaak?

Het probleem ontstaat doordat sessie-data lokaal op een locatie voor tijdelijke bestanden wordt opgeslagen. Doordat PHP standaard met de rechten van de webserver draait, kan PHP niet goed onderscheid maken tussen sessies van de ene en de andere site.

Daarnaast is het, door het lokale karakter van de sessies, niet goed mogelijk voor een website om op meerdere servers te draaien. Stel dat er een webserver uitvalt, dan is het niet mogelijk om de sessies mee te nemen naar een reserve server. Byte heeft dit opgelost door ervoor te zorgen dat je altijd wordt geholpen door één en dezelfde server, binnen een time-out van 1440 seconden. Deze time-out is gelijk aan de standaard time-out van PHP sessies. Gebruik je een langere time-out, dan zullen de lokale sessies niet goed meer werken.

Policy gebruik van sessies

PHP slaat sessiebestanden van jouw website op de harde schijf van de webserver op in een standaard locatie. Deze locatie deel je met de andere gebruikers van de webserver.

Ramdisk voor snelheid

Omdat het lezen en schrijven naar disk erg traag is, wordt er bij Byte gebruik gemaakt van een ramdisk: een virtuele harde schijf in het interne geheugen (RAM) van de webserver. Dit zorgt er voor dat de sessiebestanden heel erg snel gelezen en geschreven kunnen worden. Het heeft echter ook een nadeel: het interne geheugen van een server is beperkt, en dus kan deze ramdisk ook niet erg groot gemaakt worden.

Omdat je in het loadbalanced cluster van Byte alle webservers met vele andere websites deelt, kan het voorkomen dat de ramdisk vol raakt met sessie-bestanden van een andere website. Om ervoor te zorgen dat iedereen met normaal gebruik geen hinder ondervindt, hanteert Byte de volgende policy:

Indien de ramdisk voor sessiebestanden vol raakt (meer dan 80% in gebruik), worden net zo lang sessiebestanden verwijderd tot het gebruik onder de 80% gedaald is. Hierbij worden de grootste bestanden het eerst verwijderd.

Bij deze policy gaan we uit van het feit dat sessiebestanden over het algemeen enkele KB’s (Kilo Bytes) tot hoogstens enkele tientallen KB’s groot zijn. Indien je meer data in sessies op wilt slaan kun je daar beter een ander systeem voor verzinnen, of sessies opslaan in de database.

Bij normaal gebruik is de ramdisk op de webserver groot genoeg om tien- tot honderdduizenden sessies tegelijkertijd op te kunnen slaan. In principe worden sessiebestanden niet verwijderd indien ze kleiner dan 100 KB zijn. Dit is een erg ruime limiet, en deze limiet kunnen we verder omlaag schroeven indien de stabiliteit van ons platform daar om vraagt.

Alternatief; sla de sessies op in je database

Maak gebruik van de custom session handler in PHP! De truc is om gebruik te blijven maken van de handige PHP sessie-beheersfuncties, maar de eigenlijke sessies zelf in een database op te slaan. Om de database tabel te maken kun je bijvoorbeeld de volgende query gebruiken:

CREATE TABLE `session` ( `sess_id` varchar(40) NOT NULL, `changed` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `vars` text NOT NULL, PRIMARY KEY(`sess_id`));

Hieronder staat een voorbeeld van een php session handler:

<?php 
/* 
+-----------------------------------------------------------------------+ 
| voorbeeld php session handler                                         | 
| gebaseeerd op de sessie handlers van Roundcube                        | 
+-----------------------------------------------------------------------+ 
*/ 
function sess_open($save_path, $session_name)
{
: global $DB;
: $DB = new mysqli('host', 'gebruiker', 'wachtwoord', 'database');
: return TRUE;
} 
function sess_close(){
: return TRUE;
} /
/ read session data
function sess_read($key){
: global $DB;
: $stmt = $DB->prepare("SELECT vars FROM session WHERE  sess_id=?");
: $stmt->bind_param("s",$key);: $stmt->bind_result($vars);
: if ($stmt->execute() && $stmt->fetch()): 
{    
$stmt->close();    
return $vars;
: }
: $stmt->close();
: return FALSE;
} 
// save session data
function sess_write($key, $vars){
: global $DB;
: $stmt = $DB->prepare("SELECT 1 FROM session WHERE sess_id = ?");
: $stmt->bind_param("s",$key);
: $stmt->bind_result($result); 
: if ($stmt->execute() && $stmt->fetch()): 
{    
$stmt->close();    
$stmt = $DB->prepare("UPDATE session SET vars=?, changed=NOW() WHERE  sess_id = ?");    
$stmt->bind_param("ss", $vars, $key);    
$stmt->execute();    
$stmt->close();
: }
: else: 
{    
$stmt->close();    
$stmt = $DB->prepare("INSERT INTO session (sess_id, vars, changed) VALUES (?, ?, NOW())");    
$stmt->bind_param("ss", $key, $vars);    
$stmt->execute();    
$stmt->close();: } : return TRUE;
}  
// handler for session_destroy()
function sess_destroy($key){: global $DB; : $stmt = $DB->prepare("DELETE FROM session WHERE sess_id=?");: 
$stmt->bind_param("s", $key);: 
$stmt->execute();: 
$stmt->close();: 
return TRUE;} 
// garbage collecting function
function sess_gc($maxlifetime){
: global $DB; : 
// get all expired sessions: 
$stmt = $DB->prepare("DELETE FROM session WHERE now() - changed  > ?");: 
$stmt->bind_param("i", $maxlifetime);: 
$stmt->execute();: $stmt->close();: 
return TRUE;}  
// set custom functions for PHP session management
session_set_save_handler('sess_open', 'sess_close', 'sess_read', 'sess_write', 'sess_destroy', 'sess_gc');
register_shutdown_function('session_write_close');
?>

Toelichting:.

  • We gebruiken hier de mysqli functies van PHP
  • De database wordt geopend in de sess_open() functie. Als je al een database handle hebt, kun je die natuurlijk gebruiken.
  • De regel register_shutdown_function(‘session_write_close’); is nodig omdat de $DB variabele anders vernietigd is voordat de sessie opgeslagen wordt. Zie ook PHP 5 sessions in mysql database with PDO db objects
30