2010-06-08 5 views
13

Per impostazione predefinita, i meccanismi di gestione della sessione di PHP impostano un'intestazione del cookie di sessione e memorizzano una sessione anche se non ci sono dati nella sessione. Se nella sessione non sono impostati dati, non voglio che l'intestazione Set-Cookie sia inviata al client nella risposta e non voglio che un record di sessione vuoto sia memorizzato sul server. Se i dati vengono aggiunti a $_SESSION, il normale comportamento dovrebbe continuare.Come devo implementare la creazione di una sessione lazy in PHP?

Il mio obiettivo è quello di implementare pigro comportamento creazione della sessione del tipo che Drupal 7 e Pressflow in cui è memorizzato nessuna sessione (o cookie di sessione intestazione inviato) a meno che i dati vengono aggiunti alla matrice $_SESSION durante l'esecuzione dell'applicazione. Lo scopo di questo comportamento è di consentire ai proxy inversi, ad esempio Varnish, di memorizzare nella cache e servire il traffico anonimo mentre lasciano passare le richieste autenticate ad Apache/PHP. Varnish (o un altro server proxy) è configurato per passare qualsiasi richiesta senza cookie, assumendo correttamente che se un cookie esiste allora la richiesta è per un particolare client.

ho implementato il codice di gestione sessione da Pressflow che utilizza session_set_save_handler() e sostituisce l'implementazione di session_write() per verificare i dati dell'array $_SESSION prima di salvare e sarà scrivere questo come biblioteca e aggiungere una risposta qui se questo è il migliore/solo percorso da percorrere

la mia domanda: Mentre posso implementare un sistema completamente personalizzato session_set_save_handler(), c'è un modo più semplice per ottenere questo comportamento pigro creazione della sessione in modo relativamente generico che sarebbe trasparente alla maggior parte delle applicazioni?

+0

@ Cletus: Ha dichiarato la sua ragione: * Il punto di questo comportamento è quello di consentire ai proxy inversi come Varnish di memorizzare nella cache e servire il traffico anonimo mentre lasciano passare le richieste autenticate ad Apache/PHP. * – webbiedave

+0

Risposta correlata: [classe sessione PHP simile alla classe sessione CodeIgniter?] (http://stackoverflow.com/questions/ 11596082/php-session-class-similar-to-codeigniter-session-class-exists/11596538 # 11596538) – hakre

risposta

3

Ho sviluppato un working solution per questo problema che utilizza session_set_save_handler() e una serie di metodi di archiviazione di sessione personalizzati che controllano il contenuto nell'array $_SESSION prima di scrivere i dati di sessione.Se non ci sono dati da scrivere per la sessione, viene utilizzato header('Set-Cookie:', true); Il cookie di sessione di PHP viene inviato nella risposta

L'ultima versione di questo codice, nonché la documentazione e gli esempi sono available on GitHub.Nel codice sottostante, le funzioni importanti che rendono questo lavoro sono lazysess_read($id) e lazysess_write($id, $sess_data).

<?php 
/** 
* This file registers session save handlers so that sessions are not created if no data 
* has been added to the $_SESSION array. 
* 
* This code is based on the session handling code in Pressflow (a backport of 
* Drupal 7 performance features to Drupal 6) as well as the example code described 
* the PHP.net documentation for session_set_save_handler(). The actual session data 
* storage in the file-system is directly from the PHP.net example while the switching 
* based on session data presence is merged in from Pressflow's includes/session.inc 
* 
* Links: 
*  http://www.php.net/manual/en/function.session-set-save-handler.php 
*  http://bazaar.launchpad.net/~pressflow/pressflow/6/annotate/head:/includes/session.inc 
* 
* Caveats: 
*  - Requires output buffering before session_write_close(). If content is 
*  sent before shutdown or session_write_close() is called manually, then 
*  the check for an empty session won't happen and Set-Cookie headers will 
*  get sent. 
*   
*  Work-around: Call session_write_close() before using flush(); 
*   
*  - The current implementation blows away all Set-Cookie headers if the 
*  session is empty. This basic implementation will prevent any additional 
*  cookie use and should be improved if using non-session cookies. 
* 
* @copyright Copyright &copy; 2010, Middlebury College 
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL), Version 3 or later. 
*/ 

/********************************************************* 
* Storage Callbacks 
*********************************************************/ 

function lazysess_open($save_path, $session_name) 
{ 
    global $sess_save_path; 

    $sess_save_path = $save_path; 
    return(true); 
} 

function lazysess_close() 
{ 
    return(true); 
} 

function lazysess_read($id) 
{ 
    // Write and Close handlers are called after destructing objects 
    // since PHP 5.0.5. 
    // Thus destructors can use sessions but session handler can't use objects. 
    // So we are moving session closure before destructing objects. 
    register_shutdown_function('session_write_close'); 

    // Handle the case of first time visitors and clients that don't store cookies (eg. web crawlers). 
    if (!isset($_COOKIE[session_name()])) { 
     return ''; 
    } 

    // Continue with reading. 
    global $sess_save_path; 

    $sess_file = "$sess_save_path/sess_$id"; 
    return (string) @file_get_contents($sess_file); 
} 

function lazysess_write($id, $sess_data) 
{ 
    // If saving of session data is disabled, or if a new empty anonymous session 
    // has been started, do nothing. This keeps anonymous users, including 
    // crawlers, out of the session table, unless they actually have something 
    // stored in $_SESSION. 
    if (empty($_COOKIE[session_name()]) && empty($sess_data)) { 

     // Ensure that the client doesn't store the session cookie as it is worthless 
     lazysess_remove_session_cookie_header(); 

     return TRUE; 
    } 

    // Continue with storage 
    global $sess_save_path; 

    $sess_file = "$sess_save_path/sess_$id"; 
    if ($fp = @fopen($sess_file, "w")) { 
     $return = fwrite($fp, $sess_data); 
     fclose($fp); 
     return $return; 
    } else { 
     return(false); 
    } 

} 

function lazysess_destroy($id) 
{ 
    // If the session ID being destroyed is the one of the current user, 
    // clean-up his/her session data and cookie. 
    if ($id == session_id()) { 
     global $user; 

     // Reset $_SESSION and $user to prevent a new session from being started 
     // in drupal_session_commit() 
     $_SESSION = array(); 

     // Unset the session cookie. 
     lazysess_set_delete_cookie_header(); 
     if (isset($_COOKIE[session_name()])) { 
      unset($_COOKIE[session_name()]); 
     } 
    } 


    // Continue with destruction 
    global $sess_save_path; 

    $sess_file = "$sess_save_path/sess_$id"; 
    return(@unlink($sess_file)); 
} 

function lazysess_gc($maxlifetime) 
{ 
    global $sess_save_path; 

    foreach (glob("$sess_save_path/sess_*") as $filename) { 
     if (filemtime($filename) + $maxlifetime < time()) { 
      @unlink($filename); 
     } 
    } 
    return true; 
} 

/********************************************************* 
* Helper functions 
*********************************************************/ 

function lazysess_set_delete_cookie_header() { 
    $params = session_get_cookie_params(); 

    if (version_compare(PHP_VERSION, '5.2.0') === 1) { 
     setcookie(session_name(), '', $_SERVER['REQUEST_TIME'] - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']); 
    } 
    else { 
     setcookie(session_name(), '', $_SERVER['REQUEST_TIME'] - 3600, $params['path'], $params['domain'], $params['secure']);   
    } 
} 

function lazysess_remove_session_cookie_header() { 
    // Note: this implementation will blow away all Set-Cookie headers, not just 
    // those for the session cookie. If your app uses other cookies, reimplement 
    // this function. 
    header('Set-Cookie:', true); 
} 

/********************************************************* 
* Register the save handlers 
*********************************************************/ 

session_set_save_handler('lazysess_open', 'lazysess_close', 'lazysess_read', 'lazysess_write', 'lazysess_destroy', 'lazysess_gc'); 

Mentre questa soluzione funziona ed è per lo più alle applicazioni include e richiede riscrivere l'intero meccanismo sessione stoccaggio piuttosto che basarsi su incorporato meccanismi di stoccaggio con un interruttore per salvare o meno.

+0

Attenzione: poiché questa lib non utilizza affatto il blocco, la richiesta simultanea potrebbe sovrascrivere il contenuto l'una dell'altra. – staabm

6

Bene, un'opzione potrebbe essere utilizzare una classe di sessione per avviare/interrompere/memorizzare i dati nella sessione. Quindi, si potrebbe fare qualcosa di simile:

class Session implements ArrayAccess { 
    protected $closed = false; 
    protected $data = array(); 
    protected $name = 'mySessionName'; 
    protected $started = false; 

    protected function __construct() { 
     if (isset($_COOKIE[$this->name])) $this->start(); 
     $this->data = $_SESSION; 
    } 

    public static function initialize() { 
     if (is_object($_SESSION)) return $_SESSION; 
     $_SESSION = new Session(); 
     register_shutdown_function(array($_SESSION, 'close')); 
     return $_SESSION; 
    } 

    public function close() { 
     if ($this->closed) return false; 
     if (!$this->started) { 
      $_SESSION = array(); 
     } else { 
      $_SESSION = $this->data; 
     } 
     session_write_close(); 
     $this->started = false; 
     $this->closed = true; 
    } 

    public function offsetExists($offset) { 
     return isset($this->data[$offset]); 
    } 

    public function offsetGet($offset) { 
     if (!isset($this->data[$offset])) { 
      throw new OutOfBoundsException('Key does not exist'); 
     } 
     return $this->data[$offset]; 
    } 

    public function offsetSet($offset, $value) { 
     $this->set($offset, $value); 
    } 

    public function offsetUnset($offset) { 
     if (isset($this->data[$offset])) unset($this->data[$offset]); 
    } 

    public function set($key, $value) { 
     if (!$this->started) $this->start(); 
     $this->data[$key] = $value; 
    } 

    public function start() { 
     session_name($this->name); 
     session_start(); 
     $this->started = true; 
    } 
} 

Per utilizzare, all'inizio della chiamata di script Session::initialize(). Sostituirà $ _SESSION con l'oggetto e configurerà il caricamento lazy. In seguito, si può solo fare

$_SESSION['user_id'] = 1; 

Se la sessione non è avviato, sarà, e la chiave user_id sarebbe impostato a 1. Se in qualsiasi momento si voleva chiudere (commit) la sessione, basta chiama $_SESSION->close().

Probabilmente vorrai aggiungere altre funzioni di gestione delle sessioni (come destroy, regenerate_id, la possibilità di cambiare il nome della sessione, ecc.), Ma questo dovrebbe implementare la funzionalità di base che stai cercando ...

Non è un save_handler, è solo una classe per gestire le sessioni. Se lo si desidera, è possibile implementare ArrayAccess nella classe e costruire sostituire $ _SESSION con quella classe (il vantaggio di farlo è che in questo modo il codice legacy può ancora utilizzare la sessione come in passato senza chiamare $session->setData()). L'unico svantaggio è che non sono sicuro che la routine di serializzazione utilizzata da PHP funzioni correttamente (avresti bisogno di rimettere l'array in $ _SESSION ad un certo punto ... Probabilmente con uno register_shutdown_function() ...

+0

Questa è ancora la soluzione più consigliata? Ho trovato così poco sulle sessioni pigre che mi fa pensare che theer sia un'altra soluzione. –

+0

Non assegnare qualcos'altro al superglobale '$ _SESSION'. PHP si prende cura di quella variabile e se lo stato della sessione cambia la variabile get set/unset/lost e anche PHP in nirvana. Come non dovresti fare '$ _SESSION = array()' non dovresti fare '$ _SESSION = new XYZ()'. Invece passa la classe insieme. – hakre

+0

invece di 'new Session()' usa 'new self()' e sei in grado di rinominare la classe con quello che ti piace – staabm

0

ho creato una sessione di prove pigra del concetto qui:

  • si utilizza il gestore della sessione php nativa e la matrice _SESSION
  • si avvia solo la sessione se un cookie è stato inviato o
  • si inizia la sessione se qualcosa è stato aggiunto l'array $ _SESSION
  • rimuove la sessione se una sessione viene avviata e $ _SESSION è vuoto

la estenderà nei prossimi giorni:

https://github.com/s0enke/php-lazy-session

Problemi correlati