PHP 使用 session_write_close() 时保存会话;

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10046570/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 21:13:47  来源:igfitidea点击:

PHP Save Session when using session_write_close();

phpsession

提问by Jerome Ansia

I've one page where i do a long polling i've to use at the begin of this page this

我有一个页面,我在此页面的开头进行了长时间的轮询

session_start();
session_write_close();

Because :

因为 :

to prevent concurrent writes only one script may operate on a session at any time

为了防止并发写入,任何时候只有一个脚本可以在一个会话上运行

So if i do not and the long polling is running the user will not be able to load another page.

所以如果我不这样做并且长轮询正在运行,用户将无法加载另一个页面。

So accessing to my data in session from this polling page is possible but at some point in my script i've to save my session back to the server because i made some change in it.

因此,可以从此轮询页面访问会话中的数据,但在脚本中的某个时刻,我必须将会话保存回服务器,因为我对其进行了一些更改。

What's the way to do it?

有什么办法呢?

That will be very nice it'll be a way to do something like

那将是非常好的这将是一种做类似事情的方法

session_write_open();
//do stuff
session_write_close();

But the session_write_open() doesn't exist!

但是 session_write_open() 不存在!

Thanks

谢谢

采纳答案by Jon

Beforeyou make some change to the session, call session_startagain. Make the changes, and if you still do not want to exit call session_write_closeonce more. You can do this as many times as you like.

对会话进行一些更改之前,请session_start再次调用。进行更改,如果您仍然不想再次退出调用session_write_close。您可以根据需要多次执行此操作。

回答by Armel Larcier

The previous solution will create a session ids and cookies... I wouldn't use it as is:

以前的解决方案将创建一个会话 ID 和 cookie...我不会按原样使用它:

Session is created every time you call session_start(). If you want to avoid multiple cookie, write better code. Multiple session_start() especially for the same names in the same script seems like a really bad idea.

每次调用 session_start() 时都会创建会话。如果您想避免多个 cookie,请编写更好的代码。多个 session_start() 尤其是对于同一脚本中的相同名称似乎是一个非常糟糕的主意。

see here : https://bugs.php.net/bug.php?id=38104

看这里:https: //bugs.php.net/bug.php?id=38104

I am looking for a solution right now too and I can't find one. I agree with those who say this is a "bug". You should be able to reopen a php session, but as you said session_write_open()does not exist...

我现在也在寻找解决方案,但找不到。我同意那些说这是一个“错误”的人。你应该能够重新打开一个 php 会话,但正如你所说的session_write_open()不存在......

I found a workaround in the above thread. It involves sending a header specifying manually the session id's cookie after processing the request. Luckily enough I am working with a home-brewed Front Controller that works so that no sub-controller will ever send data on its own. In a nutshell, it works perfectly in my case. To use this you might just have to use ob_start()and ob_get_clean(). Here's the magic line:

我在上面的线程中找到了一个解决方法。它涉及在处理请求后发送一个标头,手动指定会话 ID 的 cookie。幸运的是,我正在使用自制的前端控制器,该控制器可以正常工作,因此没有子控制器会自行发送数据。简而言之,它在我的情况下完美运行。要使用它,您可能只需要使用ob_start()ob_get_clean()。这是魔术线:

if (SID) header('Set-Cookie: '.SID.'; path=/', true);

EDIT : see CMCDragonkai's answer below, seems good!?

编辑:请参阅下面的 CMCDragonkai 的回答,看起来不错!?

回答by rinogo

The other answers here present pretty good solutions. As mentioned by @Jon, the trick is to call session_start() again before you want to make changes. Then, when you are done making changes, call session_write_close() again.

这里的其他答案提供了很好的解决方案。正如@Jon 所提到的,诀窍是在您想要进行更改之前再次调用 session_start()。然后,当您完成更改后,再次调用 session_write_close()。

As mentioned by @Armel Larcier, the problem with this is that PHP attempts to generate new headers and will likely generate warnings (e.g. if you've already written non-header data to the client). Of course, you can simply prefix the session_start() with "@" (@session_start()), but there's a better approach.

正如@Armel Larcier 所提到的,问题在于 PHP 尝试生成新的标头并且可能会生成警告(例如,如果您已经将非标头数据写入客户端)。当然,您可以简单地在 session_start() 前面加上“@”(@session_start()),但还有更好的方法。

Another Stack Overflow question, provided by @VolkerK reveals the best answer:

@VolkerK 提供的另一个 Stack Overflow 问题揭示了最佳答案:

session_start(); // first session_start
...
session_write_close();
...

ini_set('session.use_only_cookies', false);
ini_set('session.use_cookies', false);
//ini_set('session.use_trans_sid', false); //May be necessary in some situations
ini_set('session.cache_limiter', null);
session_start(); // second session_start

This prevents PHP from attempting to send the headers again. You could even write a helper function to wrap the ini_set() functions to make this a bit more convenient:

这可以防止 PHP 尝试再次发送标头。您甚至可以编写一个辅助函数来包装 ini_set() 函数,以使其更方便:

function session_reopen() {
    ini_set('session.use_only_cookies', false);
    ini_set('session.use_cookies', false);
    //ini_set('session.use_trans_sid', false); //May be necessary in some situations
    ini_set('session.cache_limiter', null);
    session_start(); //Reopen the (previously closed) session for writing.
}

Original related SO question/answer: https://stackoverflow.com/a/12315542/114558

原始相关 SO 问题/答案:https: //stackoverflow.com/a/12315542/114558

回答by Kevin Nelson

All of the answers here seem to be saying to use the session methods in ways that they were clearly not intended to be used...namely calling session_start()more than once.

这里的所有答案似乎都在说以明显不打算使用的方式使用会话方法……即session_start()多次调用。

The PHP website offers an example SessionHandlerInterface implementation that will work just like existing sessions but without locking the file. Just implementing their example interface fixed my locking issue to allow for concurrent connections on the same session without limiting my ability to add vars to the session. To prevent some race conditions, since the app's session isn't fully stateless, I did have to make a way to save the session mid-request without closing it so that important changes could save immediately after change and less important session vars could just save at the end of the request. See the below example for usage:

PHP 网站提供了一个示例 SessionHandlerInterface 实现,它将像现有会话一样工作,但不会锁定文件。只是实现他们的示例接口就修复了我的锁定问题,以允许在同一会话上进行并发连接,而​​不会限制我向会话添加变量的能力。为了防止某些竞争条件,由于应用程序的会话不是完全无状态的,我确实必须设法在不关闭它的情况下保存会话中间请求,以便重要的更改可以在更改后立即保存,而不太重要的会话变​​量可以只保存在请求结束时。有关用法,请参见以下示例:

Session::start();
echo("<pre>Vars Stored in Session Were:\n");print_r($_SESSION);echo("</pre>");

$_SESSION['one']    = 'one';
$_SESSION['two']    = 'two';
//save won't close session and subsequent request will show 'three'
Session::save(); 
$_SESSION['three']  = 'three';

If you replace that Session::start()with session_start()and Session::save()with session_write_close(), you'll notice that subsequent requests will never print out the third variable...it will be lost. However, using the SessionHandler (below), no data is lost.

如果您更换Session::start()session_start()Session::save()session_write_close(),你会发现,后续请求将永远不会打印出的第三个变量...它将会丢失。但是,使用 SessionHandler(如下),不会丢失任何数据。

The OOP implementation requires PHP 5.4+. However, you can provide individual callback methods in older versions of PHP. See docs.

OOP 实现需要 PHP 5.4+。但是,您可以在旧版本的 PHP 中提供单独的回调方法。请参阅文档

namespace {
    class Session implements SessionHandlerInterface {
        /** @var Session */
        private static $_instance;
        private $savePath;

        public static function start() {
            if( empty(self::$_instance) ) {
                self::$_instance = new self();
                session_set_save_handler(self::$_instance,true);
                session_start();
            }
        }
        public static function save() {
            if( empty(self::$_instance) ) {
                throw new \Exception("You cannot save a session before starting the session");
            }
            self::$_instance->write(session_id(),session_encode());
        }
        public function open($savePath, $sessionName) {
            $this->savePath = $savePath;
            if (!is_dir($this->savePath)) {
                mkdir($this->savePath, 0777);
            }

            return true;
        }
        public function close() {
            return true;
        }
        public function read($id) {
            return (string)@file_get_contents("$this->savePath/sess_$id");
        }
        public function write($id, $data) {
            return file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true;
        }
        public function destroy($id) {
            $file = "$this->savePath/sess_$id";
            if (file_exists($file)) {
                unlink($file);
            }

            return true;
        }
        public function gc($maxlifetime) {
            foreach (glob("$this->savePath/sess_*") as $file) {
                if (filemtime($file) + $maxlifetime < time() && file_exists($file)) {
                    unlink($file);
                }
            }

            return true;
        }
    }

回答by CMCDragonkai

After testing out Armel Larcier's work around. Here's my proposed solution to this problem:

在测试了 Armel Larcier 的工作之后。这是我针对这个问题提出的解决方案:

    ob_start();

    session_start();
    session_write_close();

    session_start();
    session_write_close();

    session_start();
    session_write_close();

    session_start();
    session_write_close();

    if(SID){

        $headers =  array_unique(headers_list());   

        $cookie_strings = array();

        foreach($headers as $header){
            if(preg_match('/^Set-Cookie: (.+)/', $header, $matches)){
                $cookie_strings[] = $matches[1];
            }
        }

        header_remove('Set-Cookie');

        foreach($cookie_strings as $cookie){
            header('Set-Cookie: ' . $cookie, false);
        }

    }

    ob_flush();

This will preserve any cookies that were created prior to working with sessions.

这将保留在使用会话之前创建的任何 cookie。

BTW, you may wish to register the above code as function for register_shutdown_function. Make sure to run ob_start() before the function, and ob_flush() inside the function.

顺便说一句,您可能希望将上述代码注册为 register_shutdown_function 的函数。确保在函数之前运行 ob_start(),并在函数内部运行 ob_flush()。