GVKun编程网logo

基于 PDO 的 SESSION 管理 bug 排查(pdu session modification)

13

想了解基于PDO的SESSION管理bug排查的新动态吗?本文将为您提供详细的信息,我们还将为您解答关于pdusessionmodification的相关问题,此外,我们还将为您介绍关于Cookie和

想了解基于 PDO 的 SESSION 管理 bug 排查的新动态吗?本文将为您提供详细的信息,我们还将为您解答关于pdu session modification的相关问题,此外,我们还将为您介绍关于Cookie 和 Session 的区别和联系?session的生命周期?多个服务器部署session的管理?、Debug %57 waiting for xdebug session问题求教、discuz清空session,导致session保存机制失败,session无法更新与解决、nginx – Xdebug设置cookie XDEBUG_SESSION太多次了的新知识。

本文目录一览:

基于 PDO 的 SESSION 管理 bug 排查(pdu session modification)

基于 PDO 的 SESSION 管理 bug 排查(pdu session modification)

简介

最近做一个小网站,需要用户登录,发现用户登录后经常会莫名其妙随机地自动退出登录。本文简单记录一下这个 bug 排查的过程,希望以后尽量避免此类错误。

最初代码

SESSION 数据是存放在数据库中,利用 PDO 处理读写过程,实现参考了 SO 和 php 手册中关于 session 的章节,最终的数据表结构和 php 代码如下:

数据表结构:

CREATE TABLE `sessions` (
  `session_id` varchar(40) NOT NULL DEFAULT '''',
  `session_data` text NOT NULL,
  `expire_at` int(11) unsigned NOT NULL,
  UNIQUE KEY `session_id` (`session_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

用 PDO 实现的 session 处理:

/*
 * session db handler, based on pdo
 */
class SessionDBHandler
{
    /*
     * session table name
     */
    const SESSION_TABLE = ''sessions'';

    /*
     * session data structure
     */
    const FIELD_SESSION_ID      = ''session_id'';
    const FIELD_SESSION_DATA    = ''session_data'';
    const FIELD_EXPIRE_AT       = ''expire_at'';

    /*
     * session db config
     */
    private $config    = null;

    /*
     * session pdo
     */
    private $sess_pdo  = null;

    /*
     * session lifetime
     */
    private $sess_lifetime = 0;

    /*
     * default session max lifetime in seconds
     * max_lifetime  31536000=3600*24*365
     */
    const SESSION_MAX_LIFETIME = 31536000;

    /*
     * init db_conf and session maxlifetime
     * @db_conf array
     * @max_lifetime  int default 31536000=3600*24*365
     */
    public function __construct($db_conf = array(), $max_lifetime = self::SESSION_MAX_LIFETIME)
    {
        $this->config = $db_conf;
        $this->sess_lifetime = $max_lifetime;

        session_set_save_handler(
            array($this, ''sess_open''),
            array($this, ''sess_close''),
            array($this, ''sess_read''),
            array($this, ''sess_write''),
            array($this, ''sess_destroy''),
            array($this, ''sess_gc'')
            );
        register_shutdown_function(''session_write_close'');
    }

    /*
     * the first callback function executed when the session is started
     * automatically or manually with session_start()
     * return TURE for success, FALSE for failure
     */
    public function sess_open($save_path, $session_name)
    {
        if($this->sess_pdo == null)
        {
            if($this->config[''unix_socket''])
            {
                $dsn = "mysql:dbname={$this->config[''database'']};" .
                    "unix_socket={$this->config[''unix_socket'']}";
            }
            else
            {
                $dsn = "mysql:host={$this->config[''host'']};port={$this->config[''port'']};" .
                    "dbname={$this->config[''database'']}";
            }

            $username = $this->config[''username''];
            $password = $this->config[''password''];

            $options = array_merge(array(PDO::ATTR_PERSISTENT => $this->config[''persistent'']), $this->config[''options'']);

            try
            {
                $this->sess_pdo = new PDO($dsn, $username, $password, $options);
                $this->sess_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
                $this->sess_pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
                $this->sess_pdo->exec("SET NAMES {$this->config[''charset'']}");
                $this->sess_pdo->exec("SET character_set_client=binary");
            }
            catch(PDOException $e)
            {
                $this->sess_pdo = null;
                die(''error in '' . __METHOD__ . '' : '' . $e->getMessage());
            }
        }

        return true;
    }

    /*
     * executed after the session write callback has been called,
     * also invoked when session_write_close() is called
     * TRUE for success, FALSE for failure
     */
    public function sess_close()
    {
        $this->sess_pdo = null;

        return true;
    }

    /*
     * must always return a session encoded string, or an empty string if no data to read
     * called internally by PHP where session start, before this callback is invoked PHP
     * will invoke the open callback
     */
    public function sess_read($session_id)
    {
        try
        {
            $query = ''SELECT %s FROM %s WHERE %s=? AND %s>=?'';
            $query = sprintf($query, self::FIELD_SESSION_DATA, self::SESSION_TABLE,
                self::FIELD_SESSION_ID, self::FIELD_EXPIRE_AT);
            $stmt = $this->sess_pdo->prepare($query);
            $stmt->bindValue(1, $session_id);
            $stmt->bindValue(2, time());
            $stmt->execute();

            if($stmt->rowCount() == 1)
            {
                list($sess_data) = $stmt->fetch();

                return $sess_data;
            }

            return '''';
        }
        catch(PDOException $e)
        {
            $this->sess_pdo = null;
            die(''error in '' . __METHOD__ . '' : '' . $e->getMessage());
        }
    }

    /*
     * called when the session needs to be saved and closed
     * @session_id session ID
     * @data    serialized version of the $_SESSION
     * invoked when php shuts down or explicitly session_write_close is called
     */
    public function sess_write($session_id, $data)
    {
        try
        {
            /*
             * the session_id field must be defined as UNIQUE
             */
            $query = ''INSERT INTO %s (%s, %s, %s) VALUES (?, ?, ?)''.
                " ON DUPLICATE KEY UPDATE %s=VALUES(%s), %s=VALUES(%s)";
            $query = sprintf($query, self::SESSION_TABLE, self::FIELD_SESSION_ID,
                self::FIELD_SESSION_DATA, self::FIELD_EXPIRE_AT,
                self::FIELD_SESSION_DATA, self::FIELD_SESSION_DATA,
                self::FIELD_EXPIRE_AT, self::FIELD_EXPIRE_AT);
            $stmt = $this->sess_pdo->prepare($query);
            $stmt->bindValue(1, $session_id);
            $stmt->bindValue(2, $data);
            $stmt->bindValue(3, time() + $this->sess_lifetime);
            $stmt->execute();

            return (bool)$stmt->rowCount();
        }
        catch(PDOException $e)
        {
            $this->sess_pdo = null;
            die(''error in '' . __METHOD__ . '' : '' . $e->getMessage());
        }
    }

    /*
     * executed when a session is destroyed with session_destroy or session_regenerate_id
     * return TRUE for success, FALSE for failure
     */
    public function sess_destroy($session_id)
    {
        try
        {
            $query = ''DELETE FROM %s WHERE %s=? LIMIT 1'';
            $query = sprintf($query, self::SESSION_TABLE, self::FIELD_SESSION_ID);
            $stmt = $this->sess_pdo->prepare($query);
            $stmt->bindValue(1, $session_id);
            $stmt->execute();
            return (bool)$stmt->rowCount();
        }
        catch(PDOException $e)
        {
            $this->sess_pdo = null;
            die(''error in '' . __METHOD__ . '': '' . $e->getMessage());
        }
    }

    /*
     * invoked internally by PHP periodically in order to purge old session data.
     * frequency session.gc_probability/session.gc_divisor, the value of lifetime is passed to
     * this call back be set in session.gc_maxlifetime
     * return TRUE for success, FALSE for failure
     */
    public function sess_gc($lifetime)
    {
        try
        {
            $query = "DELETE FROM %s WHERE %s <= NOW()";
            $query = sprintf($query, self::SESSION_TABLE, self::FIELD_EXPIRE_AT);
            $count = $this->sess_pdo->exec($query);
            
            return true;
        }
        catch(PDOException $e)
        {
            $this->sess_pdo = null;
            die(''error in '' . __METHOD__ . '' : '' . $e->getMessage());
        }
    }
}

其中,数据库配置参数数组为:

$DB_CONF        = array(
    ''host''      => ''127.0.0.1'',
    ''port''      => ''3306'',
    ''username''  => ''test'',
    ''password''  => ''test'',
    ''database''  => ''test'',
    ''charset''   => ''utf8'',
    ''persistent''    => true,
    ''unix_socket''   => '''',
    ''options''       => array()
    );

这里 在 sess_write 中利用 INSERT INTO ... ON DUPLICATE KEY UPDATE ... 语句更新 session 数据,需要将 session_id 字段声明为 UNIQUE。

第一次排查,误入歧途

最初出现自动退出并没有引起注意,在和前端的同学联调的过程中,登录后退出的问题就充分暴露出来了。在排查的过程中,将连接属性 persistent=false,发现退出后就无法登录系统了。这引入了一个新问题,偏离了原 bug,但也定位出程序中的一个隐藏 bug。为 session 函数添加了执行日志,并查看 mysql 中实际执行的语句序列。以 sess_write 和 sess_destroy 为例,代码片段为如下:

/*
     * called when the session needs to be saved and closed
     * @session_id session ID
     * @data    serialized version of the $_SESSION
     * invoked when php shuts down or explicitly session_write_close is called
     */
    public function sess_write($session_id, $data)
    {
        try
        {
            /*
             * the session_id field must be defined as UNIQUE
             */
            $query = ''INSERT INTO %s (%s, %s, %s) VALUES (?, ?, ?)''.
                " ON DUPLICATE KEY UPDATE %s=VALUES(%s), %s=VALUES(%s)";
            $query = sprintf($query, self::SESSION_TABLE, self::FIELD_SESSION_ID,
                self::FIELD_SESSION_DATA, self::FIELD_EXPIRE_AT,
                self::FIELD_SESSION_DATA, self::FIELD_SESSION_DATA,
                self::FIELD_EXPIRE_AT, self::FIELD_EXPIRE_AT);
            $stmt = $this->sess_pdo->prepare($query);
            $stmt->bindValue(1, $session_id);
            $stmt->bindValue(2, $data);
            $stmt->bindValue(3, time() + $this->sess_lifetime);
            $stmt->execute();
	    $write_count = $stmt->rowCount();

	    error_log("sess_write:(records num) write_count=" . $write_count, 3, "/tmp/logout.log");

	    //read data immediately after write
	    $query = ''SELECT %s FROM %s WHERE %s=? AND %s>=?'';
            $query = sprintf($query, self::FIELD_SESSION_DATA, self::SESSION_TABLE,
                self::FIELD_SESSION_ID, self::FIELD_EXPIRE_AT);
            $stmt = $this->sess_pdo->prepare($query);
            $stmt->bindValue(1, $session_id);
            $stmt->bindValue(2, time());
            $stmt->execute();

            list($sess_data) = $stmt->fetch();
	    error_log("sess_write:(read after write) sess_data=" . print_r($sess_data, true), 3, "/tmp/logout.log");

            return (bool)$write_count;
        }
        catch(PDOException $e)
        {
            $this->sess_pdo = null;
            die(''error in '' . __METHOD__ . '' : '' . $e->getMessage());
        }
    }
/*
     * executed when a session is destroyed with session_destroy or session_regenerate_id
     * return TRUE for success, FALSE for failure
     */
    public function sess_destroy($session_id)
    {
        try
        {
            $trace_arr = debug_backtrace();
            $trace_str = '''';
            foreach($trace_arr as $index => $trace)
            {
                $trace_str .= ''#'' . $index . ''|file='' . $trace[''file''] . ''|line='' . $trace[''line''] .
                    ''| . $trace[''class''] . ''|function='' . $trace[''function''];
            }
            $trace_str .= "\n";
            error_log($trace_str, 3, ''/tmp/logout.log'');
    
            $query = ''DELETE FROM %s WHERE %s=? LIMIT 1'';
            $query = sprintf($query, self::SESSION_TABLE, self::FIELD_SESSION_ID);
            $stmt = $this->sess_pdo->prepare($query);
            $stmt->bindValue(1, $session_id);
            $stmt->execute();
            return (bool)$stmt->rowCount();
        }
        catch(PDOException $e)
        {
            $this->sess_pdo = null;
            die(''error in '' . __METHOD__ . '': '' . $e->getMessage());
        }
    }

打开数据库执行语句序列的方法,参考 SO 上的问答:

mysql> show variables like ''general_log%'';
mysql> SET GLOBAL general_log = ''ON'';
--查看输出形式和输出文件存放位置
mysql> show variables like ''%log_out%'';
mysql> show variables like ''general_log_file'';
用户登录成功后 ssession_start () 函数调用的 sess_open 对应执行的 SQL 序列为:
Query	SET AUTOCOMMIT=0
Query	SET NAMES utf8
Query	SET character_set_client=binary
Prepare	SELECT session_data FROM sessions WHERE session_id=? AND expire_at>=?
Execute	SELECT session_data FROM sessions WHERE session_id=''fd99bece38392b9e7d5fe49cf6de32c8'' AND expire_at>=''1375685562''
Close stmt

从 SQL 语句的执行序列中可以发现,一开始就执行了一个

SET AUTOCOMMIT=0

的语句。另一方面,从 session 处理函数的 log 中可以看到,每次 sess_write 之后,可以读到刚写的数据。然而断开连接后就无法读到 session 数据了。这正好与没有 SQL 语句序列中不自动提交语句相符和。在 sess_open 函数中设置每次都自动提交:

$this->sess_pdo->setAttribute(PDO::ATTR_AUTOCOMMIT, true);
此时就发现比较有意思的 SQL 执行序列:
Query	SET AUTOCOMMIT=0
Query	SET AUTOCOMMIT=1
Query	SET NAMES utf8
Query	SET character_set_client=binary
在 InnoDB 中,默认是自动提交的,可以通过下面的方法查看:
mysql> show variables like ''%autocommit%'';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    | 
+---------------+-------+
1 row in set (0.00 sec)

近一步检查代码,就可发现,原来在 sess_open 初始化 PDO 的时候,为了设置长连接参数,使用了一个 array_merge 操作,问题就出在这里,php 手册中说过:

Values in the input array with numeric keys will be renumbered with incrementing keys starting from zero in the result array.
array_merge 会对数值型下标重排,这样
array_merge(array(PDO::ATTR_PERSISTENT => $this->config[''persistent'']), $this->config[''options'']);

的结果并不是设置了长连接属性值,而是设置了 PDO::ATTR_AUTOCOMMIT 的值为 false,从而导致无法登录的问题。至此,随机退出的问题没解决,反而发现了程序中一个隐藏的 bug,长连接参数不起作用。这个 bug 的修正很直接:只需要用 + 替换 array_merge 就可以了。

偶然发现玄机

可是随机退出的问题还是没有解决,直到今天,查看退出的日志才偶然发现是 gc 函数的缘故。其实早就该怀疑是 gc 异常导致的问题,只是受到上面一个 bug 的影响,一直也没有往这方面多想,再加上 sess_gc 中记录的日志太粗糙,没有记录删除语句影响的行数,每次都无法直观地看出问题来,记录日志的效果很不好。

sess_gc 函数中执行的语句是:

$query = "DELETE FROM %s WHERE %s <= NOW()";
$query = sprintf($query, self::SESSION_TABLE, self::FIELD_EXPIRE_AT);

而数据库表中的数据类型是 int。结果就很明显了,gc 以 php.ini 中配置的概率执行,而每次执行的时候都会删除当前登录用户数据,从而造成用户的随机登出的现象。bug 的修正也很直接,略去不记了。

小结

通过这次 bug 追踪,如果常用函数如 array_merge 等,理解的不够细致,往往会引入隐藏的 bug。日志是为了帮助解决问题的,而在 gc 函数中只记录了执行的 sql 语句,而没有记录语句执行所影响的行数,不但不利于排查,反而会让人误以为这里没有问题。此外,导致这次 bug 最主要的原因,也是容易让人陷入 "这里没问题" 的思维惯性从而导致很难发现 bug原因:数据结构的定义和操作函数一定要一致

Cookie 和 Session 的区别和联系?session的生命周期?多个服务器部署session的管理?

一、session 和 cookie

1、cookie

  Cookie会根据响应报文里的一个叫做Set-Cookie的首部字段信息,通知客户端保存Cookie。当下次客户端再向服务端发起请求时,客户端会自动在请求报文中加入Cookie值之后发送给服务器

  cookie的内容主要包括:名字,值,过期时间,路径和域。路径与域一起构成cookie的作用范围。若不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就消失。这种生命期为浏览器会话期的cookie被称为会话cookie。

  会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。若设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。

2、session

  session机制采用的是一种在服务器端保持状态的解决方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的。而session提供了方便管理全局变量的方式 。

  session是针对每一个用户的,变量的值保存在服务器上,用一个sessionID来区分是哪个用户session变量,这个值是通过用户的浏览器在访问的时候返回给服务器,当客户禁用cookie时,这个值也可能设置为由get来返回给服务器。

二、Session 和 Cookie 的区别

(1)session 和 cookie 都是由服务器生成的;

(2)session 和 cookie 都是键值对,即 session 和 cookie 就是用来保存特定的值的一种技术。

(3)session 是保存在服务器的,cookie是返回给客户端的。一般来说,sessionId 是通过类似于 cookie 的形式返回给客户端的;

(4)客户端会在发送请求的时候,自动将本地存活的 cookie 封装在信息头中发送给服务器;

(5)cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session。

(6)session 和 cookie 都是由生命周期的:

  cookie的生命周期受到 cookie 自身生命周期以及客户端是否保留 cookie 文件的影响;

       session的生命周期受到 session 自身的存活周期以及客户端连接是否关闭的影响;

(7) session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用COOKIE。

(8)单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

三、session 和 cookie 的联系  

  当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识(称为session id),如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用(检索不到,会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。一般这个cookie的名字都是类似于SEEESIONID。

  cookie可以被人为的禁止,则必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面。还有一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。

 

 

 

四、session的生命周期(https://www.cnblogs.com/caiyao/p/4082984.html)

  1、Session存储在服务器端,一般放置在服务器的内存中(为了高速存取),Sessinon在用户访问第一次访问服务器时创建,需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session,可调用request.getSession(true)强制生成Session。

  2、Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。用户每访问服务器一次,无论是否读写Session,服务器都认为该用户的Session"活跃(active)"了一次。

  3、Session什么时候失效

   (1) 服务器会把长时间没有活动的Session从服务器内存中清除,此时Session便失效。Tomcat中Session的默认失效时间为20分钟。

  (2)调用Session的invalidate方法。

 

  4、若生命周期以20分钟为例:

  (1)cookie的生命周期是累计的,从创建时,就开始计时,20分钟后,cookie生命周期结束,

  (2)session的生命周期是间隔的,从创建时,开始计时如在20分钟,没有访问session,那么session生命周期被销毁

  但是,如果在20分钟内(如在第19分钟时)访问过session,那么,将重新计算session的生命周期

  (3)关机会造成session生命周期的结束,但是对cookie没有影响

Debug %57 waiting for xdebug session问题求教

Debug %57 waiting for xdebug session问题求教

环境:wampserver 2.1
eclipse for php 配置
preferebces里面的php debug 选了xdebug
php excutable也配置了
xdebug的端口也设置了19001

php.ini是这样的
zend_extension = "e:/win7tools/wamp/bin/php/php5.3.3/ext/php_xdebug-2.1.2-5.3-vc9.dll"
;
[xdebug]
xdebug.remote_enable = on
xdebug.profiler_enable = on
xdebug.profiler_enable_trigger = on
xdebug.profiler_output_name = cachegrind.out.%t.%p
xdebug.profiler_output_dir = "e:/win7tools/wamp/tmp"
xdebug.show_local_vars=0
xdebug.remote_port = 19001

但是依然无法调试,提示waiting for xdebug session 一直在57%


回复讨论(解决方案)

zi ji ding yi xia

https://www.baidu.com/s?ie=UTF-8&wd=Debug+%2557+waiting+for+xdebug+session

自己解决了。。。。。。。

discuz清空session,导致session保存机制失败,session无法更新与解决

discuz清空session,导致session保存机制失败,session无法更新与解决

[php] 
 
 
 
 
function userErrorHandler() { 
    $e = func_get_args(); 
    echo ''

<br>----------运行出错---------:<br>''.print_r($e, 1).''<br>----------运行出错---------<br>
登录后复制
登录后复制
''; 

set_error_handler("userErrorHandler"); 
set_exception_handler("userErrorHandler"); 
 
function shutdown() { 
    $a=error_get_last();     
    if($a != null) echo ''
<br>++++++低级错误+++++<br>''.print_r($a, 1).''<br>++++++低级错误+++++<br>
登录后复制
登录后复制
'';    
}  
 
register_shutdown_function(''shutdown'');//如果使用了exit将不运行此脚本  
 
 
switch($_GET[''how'']) { 
    case ''s''://set  
        session_start(); 
        $_SESSION[''qidizi''] = rand(); 
        echo $_SESSION[''qidizi'']; 
        break; 
    case ''u''://unset  
        session_start(); 
        $_SESSION[''qidizi''] = ''qidiziUNSET''; 
        echo $_SESSION[''qidizi'']; 
        break; 
    case ''g''://get  
        session_start(); 
        var_dump($_SESSION); 
        break; 
    case ''c''://clean  
        session_start(); 
        echo ''get---------
''; 
        var_dump($_SESSION); 
        echo ''
edit-------/>''; 
        $_SESSION[''qidizi''] = ''qidiziCLEAN''; 
        var_dump($_SESSION); 
        echo ''
under clean---------
''; 
        $GLOBALS[''_SESSION'']=null;unset($GLOBALS[''_SESSION'']);//unset后,session会失效  
        empty($GLOBALS[''_SESSION'']) && ($GLOBALS[''_SESSION''][''qidiziReBuid''] = ''1'');//本句并不能重建/重触发session保存机制  
            session_write_close();//提前保存session改变,discuz清除了session导致保存机制失败,by qidizi,这句话才有效,提交保存  
        var_dump($_SESSION); 
        break; 

 


function userErrorHandler() {
    $e = func_get_args();
    echo ''

<br>----------运行出错---------:<br>''.print_r($e, 1).''<br>----------运行出错---------<br>
登录后复制
登录后复制
'';
}
set_error_handler("userErrorHandler");
set_exception_handler("userErrorHandler");

function shutdown() {
    $a=error_get_last();   
    if($a != null) echo ''

<br>++++++低级错误+++++<br>''.print_r($a, 1).''<br>++++++低级错误+++++<br>
登录后复制
登录后复制
'';  
}

register_shutdown_function(''shutdown'');//如果使用了exit将不运行此脚本


switch($_GET[''how'']) {
 case ''s''://set
  session_start();
  $_SESSION[''qidizi''] = rand();
  echo $_SESSION[''qidizi''];
  break;
 case ''u''://unset
  session_start();
  $_SESSION[''qidizi''] = ''qidiziUNSET'';
  echo $_SESSION[''qidizi''];
  break;
 case ''g''://get
  session_start();
  var_dump($_SESSION);
  break;
 case ''c''://clean
  session_start();
  echo ''get---------
'';
  var_dump($_SESSION);
  echo ''
edit-------/>'';
  $_SESSION[''qidizi''] = ''qidiziCLEAN'';
  var_dump($_SESSION);
  echo ''
under clean---------
'';
  $GLOBALS[''_SESSION'']=null;unset($GLOBALS[''_SESSION'']);//unset后,session会失效
  empty($GLOBALS[''_SESSION'']) && ($GLOBALS[''_SESSION''][''qidiziReBuid''] = ''1'');//本句并不能重建/重触发session保存机制
   session_write_close();//提前保存session改变,discuz清除了session导致保存机制失败,by qidizi,这句话才有效,提交保存
  var_dump($_SESSION);
  break;
}
以上是测试代码

 


关键是在 $GLOBALS[''_SESSION'']=null; 这句.且

[php]
unset($GLOBALS[''_SESSION'']); 

unset($GLOBALS[''_SESSION'']);会让session在解析结束保存session的机制失败,看起来是这样的.不懂session自动保存的机制是怎么样的.演示代码中简单的重建并没有触发保存机制.

所以,后来我使用了提前调用方法提前保存我的session更改.


在discuz_application这个类中有对全局变量进行清空,

因为
正面面变量不需要保留,


    var $superglobal = array(
        ''GLOBALS'' => 1,
        ''_GET'' => 1,
        ''_POST'' => 1,
        ''_REQUEST'' => 1,
        ''_COOKIE'' => 1,
        ''_SERVER'' => 1,
        ''_ENV'' => 1,
        ''_FILES'' => 1,
    );

接着正面的代码就会对它进行清空

 

        foreach ($GLOBALS as $key => $value) {
            if (!isset($this->superglobal[$key])) {
                $GLOBALS[$key] = null; unset($GLOBALS[$key]);
            }
        }

最终效果出现如下的代码功能
关键是在 $GLOBALS[''_SESSION'']=null; 这句.

使用我上面的测试代码进行演示:
正面的说法指 local.q/t.php?how=s(设置)|g(获取)|u(修改)|c(清空)

操作步骤1 设置 -> 获取 -> 修改 -> 获取     ==== 结果,修改能反馈到获取时的结果中
操作步骤2 设置->获取 -> 修改-> 获取 -> 清空 -> 获取 ====结果:获取修改后数据正常.获取清空的数据失败,获取到的是修改时的数据.(注意这里的测试并没有把提前)

 

问题就是disucz的init方法导致清空相同的效果.导致某些情况下的使用session会出现清除不掉的问题.简单就是导致验证码输入一次就可以无限提交.

虽然可以使用其它方法来防止.但是这个session的正常的机制被破坏了.问题比较多.

目前我在写这个时,还不清楚使用什么方法可以恢复它的机制.上面的尝试方法并不起作用.

 

 

 




 

 

 


经过测试发现使用 session_write_close();提前保存session理性.即可解决我遇到的问题,不知为何经过清空session后,自动保存会失效.需要主动保存.

nginx – Xdebug设置cookie XDEBUG_SESSION太多次了

nginx – Xdebug设置cookie XDEBUG_SESSION太多次了

我使用PHPStorm,xdebug和Nginx PHP-fpm进行远程调试.当我在请求GET参数中传递XDEBUG_SESSION_START = my_ide_key时,Nginx会使用502错误代码(Bad Gateway)进行调用.同时我在IDE中的代码断点工作正常.当我没有传递XDEBUG_SESSION_START参数时,Nginx响应格式良好的HTML和代码200.但是没有这个参数显然没有调试.

在Nginx错误日志中,我看到有关从上游收到的大标头的通知.我尝试在PHP-fpm和Nginx之间转储通信,只有一个不同的东西是一个Set-Cookie头:

Set-Cookie: XDEBUG_SESSION=666; expires=Mon,16-Sep-2013 16:07:28 GMT; path=/

我试着找到这个标题出现在响应中的时间.我发现在我的smarty插件Smarty_Internal_Template析构函数(在我的启动脚本的最后一行代码行之后)如果我调用headers_list(),我看到了大量的Set-Cookie标头(等于析构函数调用和Set-Cookie标头数量).我确信我的代码中没有一个显式头(‘Set-Cookie:XDEBUG_SESSION = …’)调用.我尝试升级和降级xdebug版本但仍然具有相同的行为.在Smarty_Internal_Template放置代码remove_header(‘Set-Cookie’)解决了我的问题,但这是丑陋的黑客!

有关这种奇怪情况的任何想法?

最佳答案
我建议不要在这种情况下使用XDEBUG_SESSION_START.对我来说,看起来XDEBUG_SESSION_START正在触发服务器端的一些代码执行来设置cookie.这就是干扰智能模板代码.

在我使用PHPStorm的所有经验中,我发现打开xdebug的最佳方法是通过bookmarklet,您可以在此处生成:

https://www.jetbrains.com/phpstorm/marklets/

bookmarklet在浏览器中设置cookie.因此,在服务器中不执行任何代码来设置XDEBUG_SESSION和路径变量,这可以减少或消除对智能代码的干扰.

此外,PHPStorm的一个提示是确保PHPStorm启动并运行,并且PHPStorm和PHP-fpm之间的网络连接正常工作(我认为这是你与Nginx结合使用的).

如果PHP-fpm无法连接到PHPStorm,根据我的经验,代码最终将在服务器上执行,但速度非常慢.

有几次我把这个错误地看作性能问题并浪费了很多时间.

今天关于基于 PDO 的 SESSION 管理 bug 排查pdu session modification的分享就到这里,希望大家有所收获,若想了解更多关于Cookie 和 Session 的区别和联系?session的生命周期?多个服务器部署session的管理?、Debug %57 waiting for xdebug session问题求教、discuz清空session,导致session保存机制失败,session无法更新与解决、nginx – Xdebug设置cookie XDEBUG_SESSION太多次了等相关知识,可以在本站进行查询。

本文标签: