php会话随机丢失,无法理解为啥

Posted

技术标签:

【中文标题】php会话随机丢失,无法理解为啥【英文标题】:php session is randomly lost and cant understand whyphp会话随机丢失,无法理解为什么 【发布时间】:2015-09-05 00:36:15 【问题描述】:

我花钱请一位程序员制作购物篮脚本以使用 Spreadshirt API。一切都运行良好,只是篮子一直在自己倒空。我认为会话在某些时候会丢失,因此脚本会创建另一个 BasketId

我试图找出它发生的具体原因,但没有任何成功......我无法重现该错误。它只是随机发生,没有任何理由。关闭浏览器、重置 apache 甚至整个网络服务器都不会导致会话丢失。

我有两个不同的脚本在同一个域上使用 cookie,它们没有任何问题(一个是管理员登录会话的 cookie,另一个 cookie 是保存用户在商店中最后查看的文章)

我尝试了在谷歌上找到的所有解决方案,但没有成功:编辑php.ini,通过php强制设置ini,尝试htaccess方式,...

这是我的 phpinfo 的“会话”部分:http://gyazo.com/168e2144ddd9ee368a05754dfd463021

shop-ajax.php(会话处理@第 18 行)

ini_set('session.cookie_domain', '.mywebsite.com' );
header("Pragma: no-cache");
header("Cache-Control: no-store, no-cache, max-age=0, must-revalidate");
$language = addslashes($_GET['l']);
$shopid = addslashes($_GET['shop']);


// if($_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest') 
//  die("no direct access allowed");
// 



if(!session_id()) 
  $lifetime=60 * 60 * 24 * 365;
  $domain = ".mywebsite.com";
   session_set_cookie_params($lifetime,"/",$domain);
    @session_start();






// Configuration
$config['ShopSource'] = "com";
$config['ShopId'] = $shopid;
$config['ShopKey'] = "*****";
$config['ShopSecret'] = "*****";



/*
 * add an article to the basket
*/
if (isset($_POST['size']) && isset($_POST['appearance']) && isset($_POST['quantity'])) 
    /*
     * create an new basket if not exist
    */
    if (!isset($_SESSION['basketUrl'])) 
        /*
         * get shop xml
        */
        $stringApiUrl = 'http://api.spreadshirt.'.$config['ShopSource'].'/api/v1/shops/' . $config['ShopId'];
        $stringXmlShop = oldHttpRequest($stringApiUrl, null, 'GET');
        if ($stringXmlShop[0]!='<') die($stringXmlShop);
        $objShop = new SimpleXmlElement($stringXmlShop);
        if (!is_object($objShop)) die('Basket not loaded');

        /*
         * create the basket
        */
        $namespaces = $objShop->getNamespaces(true);
        $basketUrl = createBasket('net', $objShop, $namespaces);
        $_SESSION['basketUrl'] = $basketUrl;
        $_SESSION['namespaces'] = $namespaces;

        /*
         * get the checkout url
        */
        $checkoutUrl = checkout($_SESSION['basketUrl'], $_SESSION['namespaces']);

        // basket language workaround
        if ($language=="fr") 
            if (!strstr($checkoutUrl,'/fr')) 
                $checkoutUrl = str_replace("spreadshirt.com","spreadshirt.com/fr",$checkoutUrl);
            
        

        $_SESSION['checkoutUrl'] = $checkoutUrl;

    



    /*
    Workaround for not having the appearance id :(
    */
    if ($_POST['appearance']==0) 
        $stringApiArticleUrl = 'http://api.spreadshirt.'.$config['ShopSource'].'/api/v1/shops/' . $config['ShopId'].'/articles/'.intval($_POST['article']).'?fullData=true';
        $stringXmlArticle = oldHttpRequest($stringApiArticleUrl, null, 'GET');
        if ($stringXmlArticle[0]!='<') die($stringXmlArticle);
        $objArticleShop = new SimpleXmlElement($stringXmlArticle);
        if (!is_object($objArticleShop)) die('Article not loaded');
        $_POST['appearance'] = intval($objArticleShop->product->appearance['id']);
    


    /*
     * article data to be sent to the basket resource
    */
    $data = array(

            'articleId' => intval($_POST['article']),
            'size' => intval($_POST['size']),
            'appearance' => intval($_POST['appearance']),
            'quantity' => intval($_POST['quantity']),
            'shopId' => $config['ShopId']

    );

    /*
     * add to basket
    */
    addBasketItem($_SESSION['basketUrl'] , $_SESSION['namespaces'] , $data);

    $basketData = prepareBasket();


    echo json_encode(array("c" => array("u" => $_SESSION['checkoutUrl'],"q" => $basketData[0],"l" => $basketData[1])));





// no call, just read basket if not empty
if (isset($_GET['basket'])) 
    if (array_key_exists('basketUrl',$_SESSION) && !empty($_SESSION['basketUrl'])) 

        $basketData = prepareBasket();

        echo json_encode(array("c" => array("u" => $_SESSION['checkoutUrl'],"q" => $basketData[0],"l" => $basketData[1])));
     else 
        echo json_encode(array("c" => array("u" => "","q" => 0,"l" => "")));
    







function prepareBasket() 

    $intInBasket=0;

    if (isset($_SESSION['basketUrl'])) 
        $basketItems=getBasket($_SESSION['basketUrl']);

        if(!empty($basketItems)) 
            foreach($basketItems->basketItems->basketItem as $item) 
                $intInBasket += $item->quantity;
            
        
    

    $l = "";
    $pQ = parse_url($_SESSION['checkoutUrl']);
    if (preg_match("#^basketId\=([0-9a-f\-])*$#i", $pQ['query'])) 
        $l = $pQ['query'];
    

    return array($intInBasket,$l);








// Additional functions
function addBasketItem($basketUrl, $namespaces, $data) 
    global $config;

    $basketItemsUrl = $basketUrl . "/items";

    $basketItem = new SimpleXmlElement('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
            <basketItem xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://api.spreadshirt.net">
            <quantity>' . $data['quantity'] . '</quantity>
            <element id="' . $data['articleId'] . '" type="sprd:article" xlink:href="http://api.spreadshirt.'.$config['ShopSource'].'/api/v1/shops/' . $data['shopId'] . '/articles/' . $data['articleId'] . '">
            <properties>
            <property key="appearance">' . $data['appearance'] . '</property>
            <property key="size">' . $data['size'] . '</property>
            </properties>
            </element>
            <links>
            <link type="edit" xlink:href="http://' . $data['shopId'] .'.spreadshirt.' .$config['ShopSource'].'/-A' . $data['articleId'] . '"/>
            <link type="continueShopping" xlink:href="http://' . $data['shopId'].'.spreadshirt.'.$config['ShopSource'].'"/>
            </links>
            </basketItem>');

    $header = array();
    $header[] = createAuthHeader("POST", $basketItemsUrl);
    $header[] = "Content-Type: application/xml";
    $result = oldHttpRequest($basketItemsUrl, $header, 'POST', $basketItem->asXML());




function createBasket($platform, $shop, $namespaces) 

    $basket = new SimpleXmlElement('<basket xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://api.spreadshirt.net">
            <shop id="' . $shop['id'] . '"/>
            </basket>');

    $attributes = $shop->baskets->attributes($namespaces['xlink']);
    $basketsUrl = $attributes->href;
    $header = array();
    $header[] = createAuthHeader("POST", $basketsUrl);
    $header[] = "Content-Type: application/xml";
    $result = oldHttpRequest($basketsUrl, $header, 'POST', $basket->asXML());
    $basketUrl = parseHttpHeaders($result, "Location");

    return $basketUrl;








function checkout($basketUrl, $namespaces) 

    $basketCheckoutUrl = $basketUrl . "/checkout";
    $header = array();
    $header[] = createAuthHeader("GET", $basketCheckoutUrl);
    $header[] = "Content-Type: application/xml";
    $result = oldHttpRequest($basketCheckoutUrl, $header, 'GET');
    $checkoutRef = new SimpleXMLElement($result);
    $refAttributes = $checkoutRef->attributes($namespaces['xlink']);
    $checkoutUrl = (string)$refAttributes->href;

    return $checkoutUrl;



/*
 * functions to build headers
*/
function createAuthHeader($method, $url) 
    global $config;

    $time = time() *1000;
    $data = "$method $url $time";
    $sig = sha1("$data ".$config['ShopSecret']);

    return "Authorization: SprdAuth apiKey=\"".$config['ShopKey']."\", data=\"$data\", sig=\"$sig\"";




function parseHttpHeaders($header, $headername) 

    $retVal = array();
    $fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $header));

    foreach($fields as $field) 

        if (preg_match('/(' . $headername . '): (.+)/m', $field, $match)) 
            return $match[2];
        

    

    return $retVal;



function getBasket($basketUrl) 

    $header = array();
    $basket = "";

    if (!empty($basketUrl)) 
        $header[] = createAuthHeader("GET", $basketUrl);
        $header[] = "Content-Type: application/xml";
        $result = oldHttpRequest($basketUrl, $header, 'GET');
        $basket = new SimpleXMLElement($result);
    

    return $basket;






function oldHttpRequest($url, $header = null, $method = 'GET', $data = null, $len = null) 

    switch ($method) 

        case 'GET':

            $ch = curl_init($url);
            curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_HEADER, false);

            if (!is_null($header)) curl_setopt($ch, CURLOPT_HTTPHEADER, $header);

            break;

        case 'POST':

            $ch = curl_init($url);
            curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_HEADER, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
            curl_setopt($ch, CURLOPT_POST, true); //not createBasket but addBasketItem
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

            break;

    

    $result = curl_exec($ch);
    curl_close($ch);

    return $result;


?>

脚本还有 2 个其他部分:一个将样品 T 恤添加到购物篮的表单(example.php) 和一个调用 ajax 的脚本(shop-controller.js)。如果需要可以发布,但没有会话处理的东西。

更新 - 问题可能与会话无关。 BasketId 丢失,但 PHPSESSID 在浏览器 cookie 中保持不变。

过去 3 天我做了以下测试(用不同的计算机和浏览器测试):

清空浏览器 cookie,然后在下午开始新会话

将 1 件商品添加到购物篮,我记下 BasketId 并检查浏览器 cookie 以记下 PHPSESSID

通常总是在午夜左右,篮子自己倒空

PHPSESSID 在我的浏览器 cookie 中保持不变,即使篮子自己清空了

但是BASKETID不一样,下午用的那个丢了,重新生成一个新的

服务器是 CentOS 5.9 - PHP 版本 5.2.9(来自 OVH)。专用 IP 上的专用服务器。

【问题讨论】:

您是否尝试在php.ini 中将session.auto_start 设置为on @tftd 他正在使用session_set_cookie_params(),需要在调用session_start()之前调用它,所以也许它不是一个选项,除了它有一些缺点(例如here和@987654323) @)。 没错。我只是建议它,因为我们这里没有完整的代码——只有一部分被称为上帝知道什么时候的 ajax 脚本。开发人员可能忘记在其中一个脚本中设置它,这就是我建议它的原因。 服务器托管在哪里?如果是共享主机,是哪一个?如果自托管,它在哪里,服务器上是否有任何 crons?如果云上的虚拟机(aws/rackspace/etc),是哪一个,是否有任何 crons?我只是想知道服务器是否发生了与 php 代码或会话无关的其他事情。 我在此处发布了与此脚本相关的另外 2 个文件:board.phpbuilder.com/… 【参考方案1】:

首先您需要确定问题是在会话的垃圾收集中还是代码中的逻辑错误。为此,您可以:

// Add this right after session_start()
if (!isset($_SESSION['mySessionCheck'])) 
    $_SESSION['mySessionCheck'] = "This session (" . session_id() . ") started " . date("Y-m-d H:i:s");


// For html pages, add this:
echo '<!-- ' . $_SESSION['mySessionCheck'] . ' -->';

// For AJAX pages, add "mySessionCheck" to the JSON response:
echo json_encode(
    array(
        "c" => array(
            "u" => $_SESSION['checkoutUrl'],
            "q" => $basketData[0],
            "l" => $basketData[1]
        ),
        "mySessionCheck" => $_SESSION['mySessionCheck']
    )
);

如果此消息在购物篮清空的同时发生变化,那么您肯定会知道这是 PHP 会话的问题。

在这种情况下,您可以尝试以下方法:

1) 你在做

$lifetime=60 * 60 * 24 * 365;
$domain = ".mywebsite.com";
session_set_cookie_params($lifetime,"/",$domain);
@session_start();

但根据来自 PHP.net 文档的user contributed note:

使用session_set_cookie_params() 时,PHP 的会话控制无法正确处理会话生命周期。

所以你可以尝试使用setcookie() 代替:

$lifetime=60 * 60 * 24 * 365;
session_start();
setcookie(session_name(),session_id(),time()+$lifetime);

尽管它是 cmets 中指出的 4 年前的笔记,但我对其进行了测试,它仍然会发生(我使用的是 PHP 5.5.7、Windows Server 2008、IIS/7.5)。只有setcookie() 生成了HTTP 标头来更改过期日期(示例将$lifetime 设置为600):

Set-Cookie: PHPSESSID=(the id); expires=Mon, 22-Jun-2015 15:03:17 GMT; Max-Age=600

2) 如果你使用的是 Debian 服务器或一些衍生服务器,他们use a cron job to clear out PHP sessions,所以你可以试试:

Increasing server's configured maxlifetime; Saving your sessions somewhere else; 使用memcached。

3) 要查看是否有某个进程清除了您的会话,您可以在存储会话文件的目录上进行观察(实际路径因服务器而异,请使用session_save_path 查找你的)。我不是服务器管理员,但我读过你可以使用auditctl,只要确保你登录who made the changes to your files。

4) 如果您无权访问服务器配置,或者不想依赖服务器配置(如果您切换主机就好了),您可以实现自己的会话处理程序。看看这个example by Pedro Gimeno。

【讨论】:

该用户提供的笔记已有四年历史了,我怀疑那里所说的是否仍然适用……如果确实如此;我从来没有遇到过 PHP 自己更新会话 cookie 生命周期的任何此类问题。 @CBroe 同意,这是来自 PHP 文档的旧评论。由于 OP 从未说过他正在运行什么版本的 PHP,我会等到他尝试它,如果他没有成功,我很乐意查看我的答案以改进它或删除它。 服务器是 CentOS 5.9 - PHP 版本 5.2.9 查看我第一篇文章的更新,如果我检查浏览器 cookie,似乎会话没有丢失,但购物篮仍然是空的 如果会话丢失或随机重新生成,那么用户的登录状态不应该也不会丢失吗?我的意思是,如果由于会话丢失或未设置而生成购物篮 ID,那么该用户如何保持登录状态?【参考方案2】:

您只将 @session_start(); 放在所有脚本的顶部。

一个也放在你的 ajax 脚本的顶部。

示例如下:

@session_start();
// you may use session script here or header file
include("header.php");
//some code. you may use session script here or header file

include("main.php");
//-----------next code

【讨论】:

【参考方案3】:

我在这里发帖,即使是旧帖,万一有人遇到这个问题,检查php.ini session.gc_maxlifetime,或者打印ini_get('session.gc_maxlifetime');您必须在您的 php 脚本或 php.ini 中设置它,在我的 php 版本上,默认值为 1440 秒,我已将其更改为 1 个月,在我的情况下就足够了。 同样在开始会话后,您可以 setcookie(session_name(),session_id(),time() + $sessionLifetime, "", "", false, true); 我希望这会有所帮助。

【讨论】:

以上是关于php会话随机丢失,无法理解为啥的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个 PHP 脚本(由 AJAX 调用)随机无法正确加载 SESSION?

猜猜随机数为啥我无法输入 - python

为啥我使用 PHP 的 $_SERVER 变量得到一个随机字符串作为 IP 地址?

Laravel 4.1 会话变量被随机遗忘

关于PHP中array_rand函数为啥不能只设置一个随机?

php会话数据有时会丢失