Сеанс Codeigniter, прослушивающий вызовы ajax

Приложение My CodeIgniter использует библиотеку сеансов и сохраняет данные в БД.

У меня возникли проблемы, когда пустые сеансы создаются после определенного вызова ajax.

При расследовании кажется, что было вызвано два одновременных вызова функций, которые требуют проверки сеанса. Один потерпит неудачу, а другой будет в порядке.

Я смог исправить это, не одновременно отключив их. Но я до сих пор не понимаю ПРИЧИНЫ, почему он терпит неудачу. Связано ли это с одним вызовом, обновляющим cookie пользователя, и 2-м недействительным вызовом? Или, может быть, при чтении БД он как-то умирает?

Я немного изучил класс ядра Session и не нашел никаких оснований для этой причины.

Если у кого-то была такая же проблема, прежде чем я буду признателен за любые советы по отладке или по какой причине.

Спасибо!

ИЗМЕНИТЬ:

Я изначально сказал, что было возвращение статуса 408. Это был несвязанный случай.

Это функция, которая запускает MyVar.refresh() параллельно:

function (event)
{
    var self$ = this.a$;
    var uid  = this.b$.val();
    var tid  = this.c$.val();
    var jqxhr = $.post('/controller1/index',{'uid':uid,'tid':tid,'action':true},function(re)
    {
        if(re.message != 'success')
        {
            MyVar.alert('<span class="msg_error sprite"></span>' + re.error);
            MyVar.refresh();
        } 

    },'json');
    MyVar.refresh();
    return stopDefault(event);
};

ВОЗМОЖНЫЕ РЕШЕНИЯ:

Найдено следующее: http://codeigniter.com/forums/viewthread/102456/

По-видимому, он неплохо работает с ajax. Одним из решений является запрет обновления сеанса, если это вызов ajax; только проблема в том, что наш сайт в основном построен с помощью ajax..

Также, просто опустил sess_time_to_update на что-то очень частое, и ajax все в порядке. Также обновился браузер, и он не затягивался. Не уверен, почему, если идентификатор сеанса уже был изменен при вызове ajax, и cookie браузера никогда не обновлялся.

Ответы

Ответ 1

Попробуйте это

<?php
/**
 * ------------------------------------------------------------------------
 * CI Session Class Extension for AJAX calls.
 * ------------------------------------------------------------------------
 *
 * ====- Save as application/libraries/MY_Session.php -====
 */

class MY_Session extends CI_Session {

    // --------------------------------------------------------------------

    /**
     * sess_update()
     *
     * Do not update an existing session on ajax or xajax calls
     *
     * @access    public
     * @return    void
     */
    public function sess_update()
    {
        $CI = get_instance();

        if ( ! $CI->input->is_ajax_request())
        {
            parent::sess_update();
        }
    }

}

// ------------------------------------------------------------------------
/* End of file MY_Session.php */
/* Location: ./application/libraries/MY_Session.php */

Проблема заключается в функции sess_update класса session, которая генерирует новый session_id через X секунд. На каждой странице есть session_id, если session_id истекает до того, как сделан ajax-вызов, этот вызов завершится с ошибкой.

Создайте php файл в/application/libraries/с именем MY_Session (или любым префиксом, который вы установили), вставьте этот код там, и это все. Эта функция переопределит функцию sess_update в классе сеанса, проверяя каждый запрос, если этот запрос был сделан ajax, пропуская функцию sess_update.

Его плохая идея установила sess_expiration при более высоких значениях. Это функция безопасности, которая защитит вас от захвата сессии

PD: я не очень свободно говорю по-английски, если вы что-то не понимаете, просто дайте мне знать.

Ответ 2

До тех пор, пока он не будет объединен с устойчивой ветвью, решение (наконец!) должно использовать Areson commit 245bef5 в сочетании со схемой базы данных:

CREATE TABLE IF NOT EXISTS  `ci_sessions` (
    session_id varchar(40) DEFAULT '0' NOT NULL,
    ip_address varchar(45) DEFAULT '0' NOT NULL,
    user_agent varchar(120) NOT NULL,
    last_activity int(10) unsigned DEFAULT 0 NOT NULL,
    user_data text NOT NULL,
    prevent_update int(10) DEFAULT NULL,
    PRIMARY KEY (session_id),
    KEY `last_activity_idx` (`last_activity`)
);

Для получения дополнительной информации прочитайте потяните 1283 комментариев вверх-вниз.

Ответ 3

У нас была эта проблема, это было связано с параметром sess_time_to_update в config.php. CI использует это, чтобы обновить идентификатор сеанса до нового. Если изменение происходит при вызове ajax, CI отправляет новый файл cookie, чтобы сообщить браузеру новый идентификатор сеанса. К сожалению, браузеры, похоже, игнорируют этот файл cookie и сохраняют старый идентификатор сеанса.

Мы исправили его, установив sess_time_to_update в sess_expiration в config.

$config['sess_time_to_update'] = $config['sess_expiration']; 

Ответ 4

У меня тоже была эта проблема в версии 2.1.3 для codeigniter, когда я использую следующую конфигурацию:

$config['sess_use_database']    = TRUE;

$config['sess_time_to_update']  = 300;

Я думаю, что это не имеет никакого отношения к запросам ajax, а скорее к ошибке в codeigniter.

Похоже, что при сохранении сеанса в базе данных выключение происходит через 300 секунд. После 3 часов поиска и анализа я обнаружил явную ошибку в коде и неясную ситуацию, я решил ошибку следующим образом:

Создайте новый файл: MY_Session.php в папке application/libraries

Добавьте в него следующий код:

<?php
// fixed by sirderno 2013

if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 

class MY_Session extends CI_Session
{

    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Update an existing session
     *
     * @access  public
     * @return  void
     */
    public function sess_update()
    {
        // We only update the session every five minutes by default
        if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now)
        {
            return;
        }

        // Save the old session id so we know which record to
        // update in the database if we need it
        $old_sessid = $this->userdata['session_id'];
        $new_sessid = '';
        while (strlen($new_sessid) < 32)
        {
            $new_sessid .= mt_rand(0, mt_getrandmax());
        }

        // To make the session ID even more secure we'll combine it with the user IP
        $new_sessid .= $this->CI->input->ip_address();

        // Turn it into a hash
        $new_sessid = md5(uniqid($new_sessid, TRUE));

        // Update the session data in the session data array
        $this->userdata['session_id'] = $new_sessid;
        $this->userdata['last_activity'] = $this->now;

        // _set_cookie() will handle this for us if we aren't using database sessions
        // by pushing all userdata to the cookie.
        $cookie_data = NULL;

        // Update the session ID and last_activity field in the DB if needed
        if ($this->sess_use_database === TRUE)
        {
            // set cookie explicitly to only have our session data
            $cookie_data = array();
            foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
            {
                $cookie_data[$val] = $this->userdata[$val];
            }

            $cookie_data['session_id'] = $new_sessid;  // added to solve bug

                    //added to solve bug
            if (!empty($this->userdata['user_data']))
                $cookie_data['user_data'] = $this->userdata['user_data'];

            $this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid)));

        }

        // Write the cookie
        $this->_set_cookie($cookie_data);
    }

    /**
     * Write the session cookie
     *
     * @access  public
     * @return  void
     */
    public function _set_cookie($cookie_data = NULL)
    {
        if (is_null($cookie_data))
        {
            $cookie_data = $this->userdata;
        }

        // Serialize the userdata for the cookie
        $cookie_data = $this->_serialize($cookie_data);

        if ($this->sess_encrypt_cookie == TRUE)
        {
            $cookie_data = $this->CI->encrypt->encode($cookie_data);
        }
        else
        {
            // if encryption is not used, we provide an md5 hash to prevent userside tampering
            $cookie_data = $cookie_data.md5($cookie_data.$this->encryption_key);
        }

        $_COOKIE[ $this->sess_cookie_name ] = $cookie_data;  // added to solve bug

        $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();

        // Set the cookie
        setcookie(
                    $this->sess_cookie_name,
                    $cookie_data,
                    $expire,
                    $this->cookie_path,
                    $this->cookie_domain,
                    $this->cookie_secure
                );
    }   
}


?>

Явная ошибка в том, что она не сохранила "user_data" в обновленном файле cookie. Неясная ошибка заключается в том, что она выполняет функцию sess_read() в файле Session.php после обновления нового идентификатора сеанса, я не знаю, почему это происходит, потому что я ожидал, что он будет выполняться перед обновлением, а не после того, как он написан в конструкторе of Session.php. Таким образом, функция sess_read() начинает считывать старую информацию cookie с помощью старого идентификатора сеанса и хочет сравнить его с идентификатором сеанса в базе данных, но после обновления session_id его больше нет в базе данных, поэтому это приводит к выходу из системы.

Эта строка кода в функции sess_read файла Session.php отвечает за чтение старой информации cookie:

$session = $this->CI->input->cookie($this->sess_cookie_name);

Итак, в функции _set_cookie из MY_Session.php я добавил эту строку кода для обновления старой информации cookie сервера с помощью новой:

$_COOKIE[ $this->sess_cookie_name ] = $cookie_data;  // added to solve bug

С этим исправлением "sess_time_to_update" в сочетании с "sess_use_database" должно работать нормально. Это простая и простая ошибка.

Ответ 5

У меня была такая же проблема при загрузке изображений с помощью ajax, и я установил sess_expiration в config:

$config['sess_expiration'] = time()+10000000;

И он исправил мою проблему.

Ответ 6

хорошо хорошие решения здесь. сделать что-либо с помощью sess_time_to_update и т.д. попробуйте приведенные ниже решения.

к номеру решения "1." Я немного обновляю script. после многократного взлома с CI я получаю очки, которые есть две причины для потери CI SESSIONS. один - когда плохие вызовы ajax делаются сессией, ОБНОВЛЯЮТСЯ, и сеансы теряются; во-вторых, после неудачного ajax-вызова он вызывает функцию sess_destroy в библиотеке SESSION CI. ТАК Я СДЕЛАЛ МАЛЕНЬКИЕ ИЗМЕНЕНИЯ ДЛЯ "1." РЕШЕНИЕ, КОТОРЫЙ ЯВЛЯЕТСЯ

/*add this code to MY_Session.php*/     
function sess_destroy()
{
// Do NOT update an existing session on AJAX calls.
if (!$this->CI->input->is_ajax_request())
{
return parent::sess_destroy();
}
/* WHEN USER HIS/HER SELF DO A LOGOUT AND ALSO IF PROGRAMMER SET TO LOGOUT USING AJAX CALLS*/
$firsturlseg = $this->CI->security->xss_clean( $this->CI->uri->segment(1) );        
$securlseg = $this->CI->security->xss_clean( $this->CI->uri->segment(2) );      
if((string)$firsturlseg==(string)'put ur controller name which u are using for login' &&    (string)$securlseg==(string)'put url controler function for logout')
{
 return parent::sess_destroy();
}
}

Надеюсь, что это поможет и людям.

Ответ 7

Кажется, что существует ошибка в сеансе обработки сеанса основного сеанса CI.

Найдена альтернативная библиотека сеансов, которая работает как прелесть.

Альтернативная библиотека сеансов CI

Я бы рекомендовал расширить основной класс CI_Session, а не заменить его.

Чтобы расширить, создайте файл MY_Session.php в application/libraries. Вставьте содержимое альтернативной библиотеки, замените class CI_Session на class MY_Session extends CI_Session.

Удалите защищенные из _flashdata_mark(), _flashdata_sweep(), _get_time(), _set_cookie(), _serialize(), _unserialize(), _sess_gc() функций.

Надеюсь, что это поможет.

Ответ 8

Кажется, все еще много старых версий CI, и я хотел добавить свои два цента, хотя эта ветка устарела. Я просто потратил несколько дней на решение проблемы вызовов AJAX в Code Igniter, и у меня есть решение, которое охватывает основные проблемы, хотя некоторые из решений не являются "замечательными". Версия CI, которую я использую (еще), - 2.1.3

Мое приложение требует, чтобы AJAX вызывал обновление поля last_activity, чтобы поддерживать действительный сеанс, поэтому мне недостаточно просто отказаться от обновления сеанса по вызовам AJAX.

Проверка ошибок для sess_update и sess_read неадекватна в этой версии CI (я не изучал более поздние версии), и многие проблемы начинаются там.

Часть первая: sess_update()

Несколько вызовов AJAX создают условия гонки, которые приводят к блокировке базы данных для последующих вызовов. Если мы попытаемся запустить запрос на обновление, но база данных заблокирована, мы получим ошибку, запрос возвращает false, но файл cookie по-прежнему обновляется новыми данными?... BAD! Кроме того, нам не нужен новый session_id для каждого вызова Ajax. Нам нужно обновить last_activity. Попробуйте следующее:

    function sess_update()
{
    // We only update the session every five minutes by default
    if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now)
    {
        return;
    }

    // Save the old session id so we know which record to
    // update in the database if we need it

    $old_sessid = $this->userdata['session_id'];
    //Assume this is an AJAX call... keep the same session_id
    $new_sessid = $old_sessid;

    if( !$this->CI->input->is_ajax_request() ){ 
        //Then create a new session id
        while (strlen($new_sessid) < 32)
        {
            $new_sessid .= mt_rand(0, mt_getrandmax());
        }

        // To make the session ID even more secure we'll combine it with the user IP
        $new_sessid .= $this->CI->input->ip_address();

        // Turn it into a hash
        $new_sessid = md5(uniqid($new_sessid, TRUE));

    }

    // _set_cookie() will handle this for us if we aren't using database sessions
    // by pushing all userdata to the cookie.
    $cookie_data = NULL;

    // Update the session ID and last_activity field in the DB if needed
    if ($this->sess_use_database === TRUE)
    {

        //TRY THE QUERY FIRST!
        //Multiple simultaneous AJAX calls will not be able to update because the Database will be locked. ( Race Conditions )
        //Besides... We don't want to update the cookie if the database didn't update
        $query = $this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid)));
        if( $query ){

            // Update the session data in the session data array
            $this->userdata['session_id'] = $new_sessid;
            $this->userdata['last_activity'] = $this->now;

            // set cookie explicitly to only have our session data
            $cookie_data = array();
            foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
            {
                $cookie_data[$val] = $this->userdata[$val];
            }

            // Write the cookie
            $this->_set_cookie($cookie_data);

        }else{
            //do nothing... we don't care, we still have an active retreivable session and the update didn't work
            //debug: error_log( "ERROR::" . $this->CI->db->_error_message() ); //Shows locked session database
        }
    }else{
        // Update the session data in the session data array
        $this->userdata['session_id'] = $new_sessid;
        $this->userdata['last_activity'] = $this->now;

        // Write the cookie
        $this->_set_cookie($cookie_data);
    }
}

часть 2: sess_read()

Очень похожая проблема здесь... База данных иногда блокируется во время запроса. Кроме того, на этот раз мы не можем игнорировать ошибки. Мы пытаемся прочитать сеанс, чтобы узнать, существует ли он... поэтому, если мы получим ошибку заблокированной базы данных, мы можем проверить ошибку и повторить попытку (при необходимости пару раз). В моем тестировании я никогда не делал больше двух попыток). Кроме того, я не знаю о вас, но я не хочу, чтобы php терпела неудачу по фатальной ошибке, не проверяя результат ложного запроса. Это нужно в верхней части файла session.php, если вы хотите попробовать этот код напрямую:

var $sess_query_attempts = 5;

Также обратите внимание: это не вся функция sess_read

$query = $this->CI->db->get($this->sess_table_name);

//Multiple AJAX calls checking
//But adding add a loop to check a couple more times has stopped premature session breaking
$counter = 0;
while( !$query && $counter < $this->sess_query_attempts     ){

    usleep(100000);//wait a tenth of a second

   $this->CI->db->where('session_id', $session['session_id']);

    if ($this->sess_match_ip == TRUE)
   {
        $this->CI->db->where('ip_address', $session['ip_address']);
    }

    if ($this->sess_match_useragent == TRUE)
    {
        $this->CI->db->where('user_agent', $session['user_agent']);
    }

    $query = $this->CI->db->get($this->sess_table_name);

    $counter++;
}
if ( !$query || $query->num_rows() == 0)
{
    $this->CI->db->where('session_id', $session['session_id']);
    $query = $this->CI->db->get( $this->sess_table_name );

    $this->sess_destroy();
    return FALSE;
}

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

Ответ 9

напишите session_start() во всем конструкторе контроллера