По умолчанию сессия PHP, обрабатывающая механизмы, установила заголовок сеансовых куки и хранит сессию, даже при отсутствии данных на сессии. Если никакие данные не установлены на сессии затем, я не хочу a Set-Cookie
заголовок отправил клиенту в ответе, и я не хочу пустую запись сессии, сохраненную на сервере. Если данные добавляются к $_SESSION
, затем нормальное поведение должно продолжиться.
Моя цель состоит в том, чтобы реализовать ленивое поведение создания сессии вида, что Drupal 7 и Pressflow, где никакая сессия не хранится (или отправленный заголовок сеансовых куки), если данные не добавляются к $_SESSION
массив во время выполнения приложений. Точка этого поведения должна позволить обратным прокси, таким как Лак кэшировать и служить анонимному трафику, позволяя аутентифицируемым запросам передать до Apache/PHP. Лак (или другой прокси-сервер) настроен для прохождения через любые запросы без cookie, предположив правильно, что, если cookie существует затем, запрос для конкретного клиента.
Я портировал код обработки сессии от Pressflow, который использует session_set_save_handler()
и переопределяет реализацию session_write()
проверять на данные в $_SESSION
массив прежде, чем сохранить и опишет это как библиотеку и добавит ответ здесь, если это будет лучшим/единственным маршрутом для взятия.
Мой Вопрос: В то время как я могу реализовать полностью пользовательское session_set_save_handler()
система, там более легкий способ получить это ленивое поведение создания сессии относительно универсальным путем, который был бы очевиден для большинства приложений?
Я разработал рабочее решение этой проблемы, которое использует 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');
Хотя это решение работает и в основном прозрачно для приложений, включающих его, оно требует переписывания всего механизма хранения сессий, а не полагаться на встроенные механизмы хранения с переключателем сохранять или нет.
Ну, один из вариантов - использовать класс сеанса для запуска / остановки / сохранения данных в сеансе. Итак, вы можете сделать что-то вроде:
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, redurate_id, возможность изменить имя сеанса и т. Д.), Но это должно реализовать базовые функции, которые вам нужны ...
Это не save_handler, это просто класс для управления вашими сессиями. Если вы действительно этого хотите, вы можете реализовать ArrayAccess в классе и при построении заменить $ _SESSION этим классом (преимущество этого заключается в том, что устаревший код все еще может использовать сеанс, как раньше, без вызова $ session -> setData ()
). Единственным недостатком является то, что я не уверен, что процедура сериализации, которую использует PHP, будет работать должным образом (в какой-то момент вам нужно будет вернуть массив в $ _SESSION ... Возможно, с помощью register_shutdown_function ()
...