По умолчанию механизмы обработки сеансов 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()
, есть ли более простой способ получить это ленивое поведение создания сеанса относительно общим образом, что было бы прозрачным для большинства приложений?
Ну, одним из вариантов было бы использование класса сеанса для запуска / остановки / хранения данных в сеансе. Итак, вы можете сделать что-то вроде:
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 © 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 © 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 https://wiki.php.net/rfc/session-read_only-lazy_write