2013-06-27 4 views
2

Я наткнулся на интересную проблему. На сайте, на котором я работаю, на данный момент есть три страницы: login.php и welcome.php и ajax.php. Все они называют session_start() в верхней части своего кода. Login.php проверяет переменные $ _SESSION [], чтобы убедиться, что кто-то вошел в систему; если нет, он получает свое имя/pwd, контакты ajax.php, чтобы проверить имя/pwd, и записывает их, установив соответствующие $ _SESSION [] vars. Welcome.php ищет подходящие $ _SESSION [] vars и отображает приветственное сообщение пользователю - если они не установлены, он просит пользователя войти в систему. Типичное поведение, и если оно используется обычным образом, оно отлично работает.PHP: session_start(), называемый «одновременно» на нескольких вкладках, создает несколько сеансов

Однако если вы заложите закладку login.php и welcome.php в виде набора вкладок (например, в Firefox), а затем откройте их как на одновременно, произойдет что-то странное. Возможно, потому что session_start() вызывается дважды в одно и то же время (проверяется с помощью error_log()), создаются два отдельных сеанса (проверяется с помощью session_id()). Какое бы значение session_start() не называлось «последним» (хотя такая же временная метка) является оставшимся сеансом. Это вызывает проблемы в следующем сценарии: login_php session_start() называется до Вызывается session_start() welcome.php.

В этой ситуации сеанс, созданный в файле login.php, продолжает существовать на этой странице, если он не обновляется. Однако, когда он связывается с ajax.php для проверки сведений о имени/pwd, а ajax.php вызывает session_start(), он извлекает сеанс, созданный welcome.php, который не имеет ничего в переменной $ _SESSION [], что делает все это потерпеть неудачу. Поэтому, если login.php сначала вызывает session_start(), мне нужно выяснить способ предотвратить получение session.start() welcome.php от создания нового. Примечание. Вызывающий порядок несовместим, и все явно работает нормально до тех пор, пока session_start() login.php вызывается последним (что я не могу контролировать).

В идеале, для второго файла было бы полезно заметить, что первый файл - в процессе создания сеанса, поэтому он не должен создавать свои собственные (но только для welcome.php).

Это ДОЛЖНО быть проблемой, с которой другие столкнулись, но я не смог найти никаких упоминаний об этом на этих межстранах. Любая помощь будет принята с благодарностью.

EDIT1: Вот файлы, необходимые для воспроизведения проблемы:

login.php

<?php 
session_start(); 
error_log("login.php, session id: ".session_id()); 
$_SESSION['user'] = "EleventyOne" 
?> 
<!DOCTYPE html> 
<html> 
<head> 
<meta charset='utf-8'> 
<title>multiple session bug - login</title> 

<script src='jquery-1.9.1.js'></script> 
<script> 

    $(document).ready(function() { 
     $.ajax({ 
       url : 'ajax.php', 
       data : { 'func' : 'check_login' }, 
       dataType : 'json', 
       type : 'GET', 
       timeout : 10000 
      }) 
     .done(function(data,textStatus,jqXHR){ 
      alert("Done: "+data.status); 
     }) 
     .fail(function(jqXHR,textStatus,errorThrown) {      
      alert("Failed: " + textStatus + "(" + errorThrown + ")"); 
     }); 
    }); // end ready 

</script> 
</head> 

<body> 
</body> 
</html> 

WELCOME.PHP:

<?php 
session_start(); 
error_log("welcome.php, session id: ".session_id()); 
$message = ""; 

if (isset($_SESSION['user'])) { 
    $message = "Hello, ".$_SESSION['user']; 
} 
else { 
    $message = "Please login!"; 
} 
?> 

<!DOCTYPE html> 
<html> 
<head> 
<meta charset='utf-8'> 
<title>multiple session bug - welcome</title> 
</head> 

<body> 
<div> 
<?php echo $message; ?> 
</div> 
</body> 
</html> 

ajax.php:

<?php 
session_start(); 
error_log("ajax.php, session id: ".session_id()); 

// ignoring $_GET[] here, as superfluous to the point... 

if (isset($_SESSION['user'])) { 

    // check the database for that user... 
    // ... 

    // return status 
    $ret['status'] = "ok"; 
    echo json_encode($ret); 
    exit; 
} 
else { 
    // return failed status 
    $ret['status'] = "broken session"; 
    echo json_encode($ret); 
    exit; 
} 
?> 

<!DOCTYPE html> 
<html> 
<head> 
<meta charset='utf-8'> 
<title>multiple session bug - ajax</title> 

<script src='_js/jquery-1.9.1.js'></script> 
<script> 

    $(document).ready(function() { 
    }); // end ready 

</script> 
</head> 

<body> 
</body> 
</html> 

При загрузке login.php, откройте новую вкладку и загрузить welcome.php, вот файл error_log вы получите (все в порядке):

[27-Jun-2013 18:39:40 UTC] login.php, session id: skofpr8g0tg81aqohnkahv3vk5 
[27-Jun-2013 18:39:40 UTC] ajax.php, session id: skofpr8g0tg81aqohnkahv3vk5 
[27-Jun-2013 18:39:44 UTC] welcome.php, session id: skofpr8g0tg81aqohnkahv3vk5 

Если вы BookMark login.php и welcome.php как набор вкладок, закройте браузер, заново откройте его и одновременно откройте обе вкладки, вы получите один из двух файлов error_log, в зависимости от того, какой вызов session_start вызывается первым.

Этот файл работает, поскольку session_start() login.php сохраняется для ajax.php. Итак, аякс.PHP сообщает статус "КИ":

[27-Jun-2013 18:40:39 UTC] welcome.php, session id: 6q2q96lhhoaqqhj214gs3gos36 
[27-Jun-2013 18:40:39 UTC] login.php, session id: j8eaa5mtfsla9q3q80qt03kvt7 
[27-Jun-2013 18:40:39 UTC] ajax.php, session id: j8eaa5mtfsla9q3q80qt03kvt7 

Это один не работает, так как session_start welcome.php (в) сохраняется ajax.php, так ajax.php сообщает "разбитая сессия":

[27-Jun-2013 18:40:18 UTC] login.php, session id: s4b7jo41jpg1ubbe8at7c5qr35 
[27-Jun-2013 18:40:18 UTC] welcome.php, session id: freu86sn3edc3fuoc2pn875o90 
[27-Jun-2013 18:40:18 UTC] ajax.php, session id: freu86sn3edc3fuoc2pn875o90 
+0

Зависит ли 'login.php' от предварительной загрузки хранилища сеансов и впоследствии не работает с более новым сеансом или мешает' welcome.php'? – mario

+0

Извините, Марио, но я не понимаю, о чем вы спрашиваете. Login.php может продолжать использовать данные $ _SESSION [] без проблем, если он не обновляется. Однако каждая другая страница, вызывающая session_start(), получает сеанс, созданный welcome.php, а не login.php (при первом запуске session_start() login.php). – EleventyOne

+0

Как проблема возникает в пользовательском интерфейсе? Разве 'login.php' не требует второго представления формы до фактической регистрации идентификатора пользователя? Потому что тогда это довольно неуместно, если он использует свой первоначальный сеанс или любой из последующих/более поздних идентификаторов сеанса. – mario

ответ

0

Лучшее решение, которое я мог придумать, состояло в том, чтобы перевести сеансовое «создание» (то есть установить переменные сеанса) в файл ajax.php, выполненный только после того, как пользователь успешно отправил их uname/pwd, и поэтому они все равно будет перенаправлено на новую страницу (т. е. welcome.php). Это означает, что login.php не может быть гарантированно иметь доступ к любым переменным сеанса, установленным ajax.php, так что это просто туповатая страница, которая опирается исключительно на ее вызовы ajax, чтобы знать, что происходит. Как оказалось, в конце концов, это не такая хлопот.

+1

Это действительно не лучшее решение. По мере дальнейшего развития вы обнаружите необходимость совместного использования данных во всех ваших файлах, которые будут включать, но не ограничиваясь ими; обработка сеанса. Я бы предложил создать файл «bootstrap», который инициализирует все глобальные настройки. Это может быть файл, который требуется для всех других файлов с использованием конкретного 'require_once', загруженного в автозагрузчик, или если вы захотите следовать шаблону Model, View, Controller (MVC), может быть в файле контроллера. –

+0

@MikePurcell Спасибо за ваш комментарий. У меня есть центральный синглтон, который включен во все мои файлы (с require_once). Этот синглтон, помимо прочего, имеет дело с обработкой сеанса. Однако это не помешало исходной проблеме (как упоминалось в ответ на Wrikken выше ... Я связался с внешним примером «начальной загрузки», который имел бы ту же проблему). Тем не менее, я не понимаю, как работает автозагрузка (это в моем списке), поэтому, возможно, это решит проблему. Я поставлю дополнительную звезду рядом с ней :) – EleventyOne

+0

После того, как мы снова посмотрим на эту проблему, кажется, что эта проблема неизбежна независимо от того, какой подход «обтекания» вы используете. Проблема в том, что одновременно загружаются два отдельных файла .php в двух отдельных вкладках. Предполагая, что оба они имеют «require_once» в файле начальной загрузки, они оба будут включать его, и если 'session_start()' находится там, он будет выполнен дважды, в то же время, вызывая проблему, описанную здесь. Насколько я могу судить, решение, которое я реализовал, - единственный способ справиться с этим. – EleventyOne

0

Я сомневаюсь, что один процесс переписывает другой. Только один процесс может открыть один и тот же файл сеанса в любое время. Второй (и последующий) будет зависать, ожидая, когда файл сеанса станет доступен.

Из моего опыта, $ _SESSION на самом деле не так волшебна, как кажется, он просто использует куки-файлы браузера или что-то в этом роде, чтобы каждый раз читать и записывать в один и тот же файл на диске. Я обошел эту проблему ранее, только используя session_start(), когда мне было нужно. Другими словами, вместо того, чтобы открывать сеанс в верхней части каждой страницы, я сделал session_start непосредственно перед записью или извлечением информации из $ _SESSION и сделал session_write_close, как только я закончил с ней.

+0

Привет Дерек. Я не слишком хорошо разбираюсь в том, как сеансы работают на уровне диска, но, похоже, это не «перезаписывает» что-либо, просто создает разные сеансы. И насколько я могу судить, каждый пост, посвященный работе с session_start(), рекомендует, чтобы он вызывался в верхней части страницы, так что это кажется вполне стандартным. Одним из возможных решений может быть принудительная задержка на 0,5 секунды на каждой странице, кроме login.php, но это кажется слабым выбором. – EleventyOne

+0

Затем убедитесь, что вы вызываете session_write_close, когда закончите сеанс. Даже если ваш сценарий переходит к более крупным и лучшим вещам. – Derek

+0

Это похоже на эффект. Который, я полагаю, имеет смысл, поскольку при вызове session_start(), вызываемых в течение одной секунды, создается несколько сеансов. Все, что я делаю * после * вызова session_start() в login.php, похоже, не влияет на вызов session_start() в welcome.php, так как он будет выполнен до того, как login.php получит вызов session_write_close(). – EleventyOne