Salutations,
sur le mutualisé OVH que j'ai, je n'ai pas accès aux configurations PHP. Or, les sessions ont une durée d'expiration d'une quinzaine de minutes (à vue de nez), ce qui est assez lourdingue pour un jeu web (devoir se reconnecter constamment, c'est pesant).
Donc, évidemment, j'ai tenté de modifier la configuration, mais sans succès (je dirai qu'on ne peut pas altérer cette partie-là de la configuration, même via un ini_set()).
Le but est donc de faire tenir les sessions plus longtemps, pour qu'on puisse jouer sans se reconnecter constamment.
La solution la plus évidente serait de changer d'hébergement pour avoir la main sur la config, mais franchement, non, ça me gonfle de basculer tous mes sites. Donc, on va oublier cette solution.
L'alternative possible consiste à modifier quelque chose dans la session à chaque page, histoire de remettre le chrono à zéro à chaque visite. C'est efficace pour ne plus être déconnecté sauvagement en pleine partie, mais ce n'est ni performant (écriture de la session pour pas grand chose) ni efficace (on sera quand même déconnecté après une quinzaine de minutes d'inactivité).
Une troisième solution serait de changer le système de sauvegarde des sessions, pour les sauver en BDD par exemple. C'était tentant, mais cela implique de faire des appels BDD constamment, et de bypasser les éventuelles optimisations faites par OVH.
Du coup, j'ai appliqué une quatrième solution: via SessionHandlerInterface, j'ai implémenté un système de backup en BDD, qui va enregistrer l'ID de session & son contenu, ainsi qu'une date de dernière activitée (pour pouvoir purger les vieilles sessions). Via cette même classe, j'ai implémenté un hook: si la session native n'existe pas (car elle aurait été purgée par le GC des sessions), alors je vais voir en BDD si elle existe encore et si oui, je la restaure. Si non, je considère que cette session est bien morte.
C'est fonctionnels (la session a bien une durée de vie de plus de 15 minutes, et je la maîtrise), cela me semble performant (pas de connexion BDD si la session native est déjà là), et sécurisé (je ne réinvente pas le mécanisme de session, je "by-pass juste" le GC).
Voyez-vous des soucis avec ce système? Avez-vous avez d'autres alternatives?
Pour info, en voici l'implé de ce système
Appelée par
Qui utilise
Qui extends
PS: le titre de cette section contient un "pas de code, juste des idées", mais je laisse tout de même le code de mon implé pour que ce soit éventuellement plus compréhensible. Je ne cherche pas une correction de mon dit code, mais bien d'autres idées éventuelles que vous pourriez avoir.
sur le mutualisé OVH que j'ai, je n'ai pas accès aux configurations PHP. Or, les sessions ont une durée d'expiration d'une quinzaine de minutes (à vue de nez), ce qui est assez lourdingue pour un jeu web (devoir se reconnecter constamment, c'est pesant).
Donc, évidemment, j'ai tenté de modifier la configuration, mais sans succès (je dirai qu'on ne peut pas altérer cette partie-là de la configuration, même via un ini_set()).
Le but est donc de faire tenir les sessions plus longtemps, pour qu'on puisse jouer sans se reconnecter constamment.
La solution la plus évidente serait de changer d'hébergement pour avoir la main sur la config, mais franchement, non, ça me gonfle de basculer tous mes sites. Donc, on va oublier cette solution.
L'alternative possible consiste à modifier quelque chose dans la session à chaque page, histoire de remettre le chrono à zéro à chaque visite. C'est efficace pour ne plus être déconnecté sauvagement en pleine partie, mais ce n'est ni performant (écriture de la session pour pas grand chose) ni efficace (on sera quand même déconnecté après une quinzaine de minutes d'inactivité).
Une troisième solution serait de changer le système de sauvegarde des sessions, pour les sauver en BDD par exemple. C'était tentant, mais cela implique de faire des appels BDD constamment, et de bypasser les éventuelles optimisations faites par OVH.
Du coup, j'ai appliqué une quatrième solution: via SessionHandlerInterface, j'ai implémenté un système de backup en BDD, qui va enregistrer l'ID de session & son contenu, ainsi qu'une date de dernière activitée (pour pouvoir purger les vieilles sessions). Via cette même classe, j'ai implémenté un hook: si la session native n'existe pas (car elle aurait été purgée par le GC des sessions), alors je vais voir en BDD si elle existe encore et si oui, je la restaure. Si non, je considère que cette session est bien morte.
C'est fonctionnels (la session a bien une durée de vie de plus de 15 minutes, et je la maîtrise), cela me semble performant (pas de connexion BDD si la session native est déjà là), et sécurisé (je ne réinvente pas le mécanisme de session, je "by-pass juste" le GC).
Voyez-vous des soucis avec ce système? Avez-vous avez d'autres alternatives?
Pour info, en voici l'implé de ce système
Code :
<?php
namespace framework\session;
use PDO;
use SessionHandlerInterface;
class DatabaseSession implements SessionHandlerInterface {
/** @var SessionHandlerInterface $backupedSession */
public $backupedSession;
/** @var PDO $pdo */
public $pdo;
/** @var string $sessionData */
public $sessionData;
/** @var string $sessionId */
public $sessionId;
public function __construct(SessionHandlerInterface $backuped, PDO $pdo) {
$this->backupedSession = $backuped;
$this->pdo = $pdo;
$this->sessionData = null;
$this->sessionId = null;
}
public function close() {
return $this->backupedSession->close();
}
public function destroy($session_id) {
$destroyed = $this->backupedSession->destroy($session_id);
$query = $this->pdo->prepare("DELETE FROM session WHERE id = ?");
$dbDestroyed = $query->execute(array($session_id));
// Reset the cached data, otherwise, the session is not actually destroyed
// (calling "read" again will return the same old data)
$this->sessionData = null;
$this->sessionId = null;
return ($destroyed && $dbDestroyed);
}
public function gc($maxlifetime) {
$gc = $this->backupedSession->gc($maxlifetime);
$query = $this->pdo->prepare("DELETE FROM session WHERE edit_date < DATE_SUB(NOW(), INTERVAL ? SECOND)");
$dbGc = $query->execute(array($maxlifetime));
return ($gc && $dbGc);
}
public function open($save_path, $name) {
$opened = $this->backupedSession->open($save_path, $name);
return $opened;
}
public function read($session_id) {
if ($this->sessionData === null) {
// Only read this once (shouldn't be called more than once actually)
$this->sessionId = $session_id;
$read = $this->backupedSession->read($session_id);
if ($read) {
$this->sessionData = $read;
} else {
// An other OVH server probably GCed the sessions, so check in database
$query = $this->pdo->prepare("SELECT data FROM session WHERE id = ?");
$query->execute(array($session_id));
$data = $query->fetch(PDO::FETCH_ASSOC);
$this->sessionData = $data ? $data['data'] : '';
}
}
return $this->sessionData;
}
public function write($session_id, $session_data) {
// Always save on disk because session may have been retrieved from DB
$wrote = $this->backupedSession->write($session_id, $session_data);
if ($this->sessionId === $session_id && $this->sessionData === $session_data) {
return $wrote;
}
// Save in DB only if there was changes
// What about concurrencies?
$query = $this->pdo->prepare("INSERT INTO session (id, data) VALUES (?, ?) ON DUPLICATE KEY UPDATE data=VALUES(data)");
$dbWrite = $query->execute(array($session_id, $session_data));
return ($wrote && $dbWrite);
}
}
Appelée par
Code :
$this->session = new EclerdSession(
new DatabaseSession(new SessionHandler(), $this->getPdo()),
96*60*60,
80*60*60,
48*60*60,
false);
Qui utilise
Code :
<?php
namespace eclerd\session;
use framework\session\ASession;
use framework\session\NotLoggedInException;
/**
* Session for ECLERD project.
*/
class EclerdSession extends ASession {
const SESSION_RESET_VALUE = 'eclerd.2017-04-08/1';
const BEAN_IDUSER = 'idUser';
const BEAN_USERNAME = 'username';
const BEAN_EMAIL = 'email';
/** @var int $idUser */
public $idUser;
/** @var string $username */
public $username;
/** @var string $email */
public $email;
protected function setData(array $beanData) {
$this->idUser = $beanData[static::BEAN_IDUSER];
$this->username = $beanData[static::BEAN_USERNAME];
$this->email = $beanData[static::BEAN_EMAIL];
}
protected function getData() {
return array(
static::BEAN_IDUSER => $this->idUser,
static::BEAN_USERNAME => $this->username,
static::BEAN_EMAIL => $this->email
);
}
/**
* Wipes out the session.
*/
public function clear() {
$this->email = null;
$this->idUser = null;
$this->username = null;
}
public function getLoggedIdUser() {
if ($this->idUser === null) {
throw new NotLoggedInException("Vous devez d'abord vous connecter");
}
return $this->idUser;
}
public function save() {
if (!$this->idUser) {
parent::destroy();
} else {
parent::save();
}
}
}
Qui extends
Code :
<?php
namespace framework\session;
use SessionHandlerInterface;
use function array_key_exists;
/**
* Generic session project.
*/
abstract class ASession implements ISession {
const SESSION_RESET_VALUE = '2017-04-14/1';
const RESET_KEY = 'reset-code';
const BEAN_KEY = 'bean';
const CREATIONDATE_KEY = 'created-on';
const RENEWDATE_KEY = 'renewed-on';
/** @var int $renewAfter */
private $renewAfter;
/**
* Constructs and start the session.
*/
public function __construct(SessionHandlerInterface $sessionHandler, $lifeTime, $maxGcLifetime, $renewAfter, $secureOnly) {
session_set_cookie_params($lifeTime, '/', null, $secureOnly, true);
session_set_save_handler($sessionHandler, false);
ini_set('session.gc_maxlifetime', $maxGcLifetime);
$this->renewAfter = $renewAfter;
}
private function getSessionKey() {
return self::SESSION_RESET_VALUE . '@' . static::SESSION_RESET_VALUE;
}
abstract protected function setData(array $beanData);
abstract protected function getData();
public function start() {
session_start();
if (array_key_exists(static::RESET_KEY, $_SESSION) && $_SESSION[static::RESET_KEY] != $this->getSessionKey()) {
$this->destroy();
$this->start();
return;
}
if (!array_key_exists(static::RESET_KEY, $_SESSION)) {
$now = time();
$_SESSION[static::CREATIONDATE_KEY] = $now;
$_SESSION[static::RENEWDATE_KEY] = $now;
$_SESSION[static::RESET_KEY] = $this->getSessionKey();
return;
}
$sessionAge = time() - $_SESSION[static::RENEWDATE_KEY];
if ($sessionAge > $this->renewAfter) {
$this->regenerateId();
}
$this->setData($_SESSION[static::BEAN_KEY]);
}
/**
* Saves the session.
*/
public function save() {
$_SESSION[static::BEAN_KEY] = $this->getData();
session_commit();
}
/**
* Destroys the session.
* ie: avoids to save guest sessions.
*/
public function destroy() {
session_unset();
session_destroy();
}
/**
* Regenerates the session (ie: on access rights change).
*/
public function regenerateId() {
$_SESSION[static::RENEWDATE_KEY] = time();
session_regenerate_id(true);
}
}
PS: le titre de cette section contient un "pas de code, juste des idées", mais je laisse tout de même le code de mon implé pour que ce soit éventuellement plus compréhensible. Je ne cherche pas une correction de mon dit code, mais bien d'autres idées éventuelles que vous pourriez avoir.