Как реализовать ленивое создание сеанса в PHP?

По умолчанию механизмы обработки сеансов PHP устанавливают заголовок cookie сеанса и сохраняют сеанс, даже если в сеансе нет данных. Если в сеансе не задано никаких данных, я не хочу, чтобы заголовок Set-Cookie отправлялся клиенту в ответ, и я не хочу, чтобы на сервере хранилась пустая запись сеанса. Если данные добавляются в $_SESSION , тогда нормальное поведение должно продолжаться.

Моя цель – реализовать поведение ленивого сеанса сортировки, которое Drupal 7 и Pressflow не хранит ни один сеанс (или отправленный заголовок cookie сеанса), если данные не будут добавлены в массив $_SESSION во время выполнения приложения. Суть этого поведения заключается в том, чтобы позволить обратным прокси-серверам, таким как Varnish, кэшировать и обслуживать анонимный трафик, а также пропускать аутентифицированные запросы в Apache / PHP. Лак (или другой прокси-сервер) настроен для передачи любых запросов без куки-файлов, предполагая, что если файл cookie существует, то запрос предназначен для конкретного клиента.

Я портировал код обработки сеанса из Pressflow, который использует session_set_save_handler() и переопределяет реализацию session_write() чтобы проверить данные в $_SESSION перед сохранением и напишет это как библиотеку и добавит ответ здесь, если это лучший / только маршрут.

Мой вопрос. Хотя я могу реализовать полностью настраиваемую систему session_set_save_handler() , есть ли более простой способ получить это ленивое поведение создания сеанса относительно общим образом, что было бы прозрачным для большинства приложений?

Related of "Как реализовать ленивое создание сеанса в PHP?"

Ну, одним из вариантов было бы использование класса сеанса для запуска / остановки / хранения данных в сеансе. Итак, вы можете сделать что-то вроде:

 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; } } 

Для использования в начале вашего скрипта вызывается Session::initialize() . Он заменит $ _SESSION на объект и установит ленивую загрузку. После этого вы можете просто сделать

 $_SESSION['user_id'] = 1; 

Если сеанс не запущен, он будет, и ключ user_id будет установлен в 1. Если в любой момент вы хотели закрыть сеанс, просто вызовите $_SESSION->close() .

Вероятно, вы захотите добавить еще несколько функций управления сеансом (таких как destroy, restoreate_id, возможность изменения имени сеанса и т. Д.), Но это должно реализовать основные функции, которые вы после …

Это не save_handler, это просто класс для управления вашими сеансами. Если бы вы действительно этого захотели, вы могли бы реализовать ArrayAccess в классе, а в конструкции замените $ _SESSION этим классом (преимущество этого заключается в том, что устаревший код все еще может использовать сеанс, как он привык, без вызова $session->setData() ). Единственным недостатком является то, что я не уверен, что процедура сериализации, используемая PHP, будет работать должным образом (вам нужно будет вернуть массив в $ _SESSION в какой-то момент … Вероятно, с register_shutdown_function()

Я разработал рабочее решение этой проблемы, которое использует session_set_save_handler() и набор настраиваемых методов хранения сеансов, которые проверяют содержимое в $_SESSION перед записью данных сеанса. Если нет данных для записи для сеанса, тогда header('Set-Cookie:', true); используется для предотвращения отправки файла cookie PHP в ответ.

Последняя версия этого кода, а также документация и примеры доступны в GitHub . В приведенном ниже коде важными функциями, которые делают эту работу, являются lazysess_read($id) и 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'); с <?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'); 

Хотя это решение работает и в основном прозрачно для приложений, в том числе и для него, требуется переписать весь механизм хранения сеанса, а не полагаться на встроенные механизмы хранения с помощью переключателя для сохранения или нет.

Я создал ленивый сеанс доказательств концепции здесь:

  • он использует собственный обработчик сеанса php и массив _SESSION
  • он запускает сеанс только в том случае, если cookie отправлен или
  • он запускает сеанс, если что-то было добавлено в массив $ _SESSION
  • он удаляет сеанс, если сеанс запущен, а $ _SESSION пуст

Расширит его в следующие дни:

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

эта тема обсуждается для будущей версии php https://wiki.php.net/rfc/session-read_only-lazy_write