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 实例分配给变量时,请使用=&
而不是 =
。通过引用分配允许您使用原始 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)