Codeigniter 会话因 ajax 调用而出错

Posted

技术标签:

【中文标题】Codeigniter 会话因 ajax 调用而出错【英文标题】:Codeigniter session bugging out with ajax calls 【发布时间】:2011-12-20 06:43:12 【问题描述】:

我的 CodeIgniter 应用程序使用会话库并将数据保存到数据库。

我遇到了一些问题,即在某个 ajax 调用之后创建了空白会话。

经过调查,似乎有 2 个同时触发的函数调用需要会话验证。一个会失败,另一个会很好。

我能够通过不让它们同时启动来解决此问题。但我仍然不明白它失败的原因。它是否与更新用户cookie的一次调用和第二次调用无效有关?或者也许在读取数据库时它会以某种方式死掉?

我查看了一下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 运行良好。还刷新了浏览器,它没有超时。不知道为什么会话 ID 在 ajax 调用时已经更改并且浏览器 cookie 从未更新。

【问题讨论】:

HTTP 408 是请求超时,当服务器停止等待来自浏览器的预期输入时返回。如果不知道您的 ajax 调用是什么样子或者您的控制器对它们做了什么,您的问题就无法回答。 哪种方法适合你? @Zabs 这可能会有所帮助。我正在使用这个线程的解决方案:github.com/EllisLab/CodeIgniter/pull/1900 如果以下答案无效,请检查此问题:***.com/questions/24297447/… 这是一个会话竞赛条件(在此处查看我的答案,并阅读hiretheworld 人员发布的文章,非常好且内容丰富)跨度> 【参考方案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 函数中,它会在 X 秒后生成一个新的 session_id。每个页面都有一个session_id,如果session_id在ajax调用之前就过期了,调用就会失败。

在 /application/libraries/ 中创建一个名为 MY_Session(或您设置的任何前缀)的 php 文件,将这段代码粘贴到那里,仅此而已。 此函数将覆盖会话类中的 sess_update 函数,检查每个请求是否由 ajax 发出,跳过 sess_update 函数。

将 sess_expiration 设置为更高的值是个坏主意。这是一项安全功能,可以保护您免受会话劫持

PD:我的英语不是很流利,如果你不明白,请告诉我。

【讨论】:

警告。 将 CI 实例分配给变量时,请使用 =&amp; 而不是 =。通过引用分配允许您使用原始 CodeIgniter 对象,而不是创建它的副本。鉴于会话的使用频率很高,这目前是内存占用。 正是我想要的!这项工作就像一个魅力,谢谢! 等等,这个文件my_session.php会被自动调用吗?我不需要在某个地方调用这个库?【参考方案2】:

直到它被合并到stable分支中,解决方案(终于!)是使用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`)
);

如需了解更多信息,请阅读pull 1283 comments自上而下。

【讨论】:

【参考方案3】:

我们遇到了这个问题,这是由于 config.php 中的 sess_time_to_update 参数造成的。 CI 使用它来将会话 ID 更新为新的。如果更改发生在 ajax 调用中,CI 会发送一个新的 cookie 来告诉浏览器新的会话 ID。不幸的是,浏览器似乎忽略了这个 cookie 并保留了旧的会话 ID。

我们通过在配置中将 sess_time_to_update 设置为 sess_expiration 来修复它。

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

【讨论】:

您使用的是哪个版本的 CI?为什么 ajax 调用会创建新的 cookie 或会话? 另外,这不是让用户在达到 sess_expiration 时间后超时吗?比如过期2小时,用户已经活跃2小时,last_activity时间永远不会更新,2小时后用户会被强制超时。 sess_time_to_update 和 sess_expirations 都是根据 last_activity 计算的,因此将它们设置为相同的值会使会话在更新之前过期。 sess_time_to_update 用于会话类的 sess_update 函数中。这是在达到超时时更改用户 cookie 中的键的函数,在您的情况下是在 AJAX 调用中。 我希望只需将 $config['sess_expiration'] 设置为小于 $config['sess_time_to_update'] 的值就足够了.【参考方案4】:

当我使用以下配置时,我在 codeigniter 版本 2.1.3 中也遇到了这个问题:

$config['sess_use_database']    = TRUE;

$config['sess_time_to_update']  = 300;

我认为这与 ajax 请求无关,而是与 codeigniter 中的错误有关。

似乎当您将会话存储在数据库中时,会在 300 秒后强制注销。经过3个小时的搜索和分析,我发现代码中有一个明显的bug和一个不清楚的bug,我已经解决了这个bug:

在应用程序/库文件夹中创建一个新文件:MY_Session.php

添加以下代码:

<?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's 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 中。不清楚的错误是它在更新新的会话 id 后执行文件 Session.php 中的函数 sess_read(),我不知道为什么会发生这种情况,因为我希望它在更新之前执行,而不是在构造函数中写入之后执行Session.php。所以 sess_read() 函数开始用旧的 session id 读取旧的 cookie 信息,并想将它与数据库中的 session id 进行比较,但是在 session_id 更新后它不再存在于数据库中,所以这会导致注销。

Session.php文件的函数sess_read中的这行代码负责读取旧的cookie信息:

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

所以在 MY_Session.php 的 _set_cookie 函数中,我添加了这行代码来用新的更新服务器的旧 cookie 信息:

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

通过此修复,“sess_time_to_update”与“sess_use_database”结合使用应该可以正常工作。这是一个简单明了的错误修复。

【讨论】:

你向 CI 的 Github 报告了这个问题吗?如果这是一个真正的错误,我会建议这样做。 不,还没有。我会查找我可以在哪里做这个错误报告,我很确定这是一个错误。 其他人试过这个吗?我有类似的问题,所以会试一试 - ps 这个错误是否已提交到 Github 进行修复?【参考方案5】:

我在使用 ajax 上传图片时遇到了完全相同的问题,我将配置中的 sess_expiration 设置为:

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

它解决了我的问题。

【讨论】:

我不想不使用会话过期。这行不通。【参考方案6】:

好的解决方案就在这里。用 sess_time_to_update 等做任何事情尝试以下解决方案

    https://degreesofzero.com/article/fixing-the-expiring-session-problem-in-codeigniter.html http://ellislab.com/forums/viewthread/138823/#725078

到解决方案编号“1”。我更新了一些脚本。在用 CI 破解了很多之后,我得到了丢失 CI SESSIONS 的两个原因。一种是错误的 ajax 调用使会话得到更新并且会话丢失;其次是在糟糕的 ajax 调用之后,它会影响 CI 的 SESSION 库中的 sess_destroy 函数。所以我对“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 alternate session library

我建议扩展核心 CI_Session 类而不是替换它。

要扩展,请在application/libraries 中创建一个文件MY_Session.php。粘贴备用库的内容,将class CI_Session替换为class MY_Session extends CI_Session

_flashdata_mark()_flashdata_sweep()_get_time()_set_cookie()_serialize()_unserialize()_sess_gc() 函数中删除 protected

希望对你有帮助。

【讨论】:

【参考方案8】:

似乎仍然有很多旧的 CI 版本在使用,我想加两分钱,即使这个线程很旧。我刚刚花了几天时间解决 Code Igniter 中的 AJAX 调用问题,我有一个涵盖主要问题的解决方案,尽管有些解决方案并不“出色”。我(仍在)使用的 CI 版本是2.1.3

我的应用程序要求 AJAX 调用更新 last_activity 字段以维持有效的会话,因此简单地放弃更新 AJAX 调用上的会话对我来说是不够的。

在这个 CI 版本中,sess_update 和 sess_read 的错误检查不足(我没有研究过更新的版本),很多问题都是从那里开始的。

第一部分:sess_update()

多个 AJAX 调用会产生竞争条件,从而导致数据库被锁定以供以后调用。如果我们尝试运行更新查询但数据库被锁定,我们会收到错误,查询返回 false,但 cookie 仍然使用新数据更新?... 糟糕!此外,我们不需要为每个 Ajax 调用创建一个新的 session_id。我们只需要更新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's 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()

这里的问题非常相似...数据库有时在查询期间被锁定。除了这次我们不能忽略错误。我们正在尝试读取会话以查看它是否存在...因此,如果我们遇到锁定的数据库错误,我们可以检查错误并再试一次(如果需要,可以尝试几次)。在我的测试中,我从来没有超过 2 次尝试)。另外,我不了解你,但我不希望 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;

无论如何,恕我直言,这个问题还没有一个完整的答案,我觉得我应该与那些可能仍然在像我这样使用大量 AJAX 的网站上遇到早期会话超时的人分享我的发现。

【讨论】:

【参考方案9】:

在所有控制器构造函数中写入session_start()

【讨论】:

这与 CodeIgniter 会话无关

以上是关于Codeigniter 会话因 ajax 调用而出错的主要内容,如果未能解决你的问题,请参考以下文章

无法发送会话缓存限制器-已发送的标头-CodeIgniter(Session.php)

范围 codeigniter 会话到期

Codeigniter 中的 Ajax 调用

判断帖子是不是来自codeigniter中的ajax调用的方法?

使用 ajax 调用加载视图 codeigniter

使用 ajax 调用 CodeIgniter 模型