PHP sessions are blocking, ignoring session_write_close - php

I am trying to serve two near simultaneous requests originating from the same browser session.
Consider the following minimal example:
<?php
session_start();
$sessionId = session_id();
session_write_close();
$file = sys_get_temp_dir() . '/lock_test';
if (!file_exists($file)) {
touch($file);
sleep(5);
unlink($file);
echo 'done! ' . $sessionId;
} else {
echo 'locked! ' . $sessionId;
}
The second request should result in the "locked" output but it always waits for the first request to complete and then outputs "done".
Xdebug is not running. PHP version is 5.5.
Edit:
Voting to close this as a duplicate. The linked question suggests that to get around this issue, append a random variable. So I would suggest appending a requestTime variable and setting it to a timestamp with microseconds.

This behaviour is OK. If both requests share the same session and you are using files to store session data, PHP needs to flock() the session file to prevent it from getting corrupted by multiple, concurring requests which would attempt to write to the file. (Of course PHP will always flock() the session file, but having multiple concurring requests which share the same session, you can see it in effect)

Related

How to find out that session has expired in PHP?

I'm trying to improve the session management for web applications. My major issue is the session expiration and how to deal with it. For that I'd like to find out if the session is still available or not. I'm using the default file based sessions (PHP 7.1, Apache 2.4, Fedora/RHEL) and it is cookie based.
What I've found out is that the session GC gets executed when session_start() is called. It is not with the begin or end of the script execution, it happens with this function. What seems odd to me is that if session_start() and the GC consider the session as expired and want to delete the corresponding session file, $_SESSION gets populated regularly first, then the file will be deleted. That's surprising.
With that behaviour only the next following request leads to an empty $_SESSION. I would expect this with the call before - the one that deletes the file. So if I'd like to know whether the session has expired in the current request I would have to check if the session file still exists after the session_start() call. That seems strange to me.
Are there any other or better ways to check that a session has expired than looking into the file system?
I thought I could just check if $_SESSION is empty to determine that the session was renewed - but that is obviously not possible.
Update: I've found the following bug reports dealing with the issue: this and that. There's also a SO entry about the expiration problem. Current PHP source: php_session_start calls php_session_initialize calls php_session_gc.
You may want to play with this script (unreal settings are just for testing purposes):
ini_set('session.gc_maxlifetime', 2); // Session gets expired after 2 seconds
ini_set('session.gc_divisor', 1); // Delete expired session files immediately
ini_set('session.save_path', '/some/safe/path/for/testing'); // Must be accessible for the server
//ini_set('session.use_strict_mode', true); // Uncomment if the id should be renewed, what makes no difference here
echo "Session files before session_start call<br>";
listSessionFiles();
session_start();
echo "Session files after session_start call<br>";
listSessionFiles();
echo "<hr>";
echo "Session id: " . session_id() . "<br>";
echo "Session content: " . print_r($_SESSION, true);
$_SESSION['x'] = time(); // Populate the session with something
function listSessionFiles() {
echo "<ul>";
$none = true;
$dir = dir(ini_get('session.save_path'));
while ($entry = $dir->read()) {
if (preg_match('/^sess_/', $entry)) {
echo "<li>" . $entry . "</li>";
$none = false;
}
}
$dir->close();
if ($none) echo "<li>None</li>";
echo "</ul>";
}
Just reload the page some times. Wait at least more than two seconds. Otherwise the session does not expire.
One way to circumvent the problem is to use cookies with a certain lifetime (below the session lifetime). If the cookie expires it won't be sent to the server. PHP will then create a new session when session_start() is called, so $_SESSION will be empty.
This might be enough to find out that the session is not available. Although one cannot distinguish in PHP if the session has expired or anything went wrong. You can just tell that no session data is available and do appropiate things then (amongst other also destroy the newly created empty session).

How can I user session_start() in a loop with no warning/error?

I am trying to implement a Server-Sent Event where the server will send the clients new messages.
The challenge here is that the server updated sessions and then echo their content.
The problem is that I will need to have that in one infinite loop.
Here is what I have done.
while(true){
session_start();
header("Content-Type: text/event-stream" . PHP_EOL);
header("Cache-Control: no-cache" . PHP_EOL);
//configure the connection
$conf = new ICWS\Config\Config($url, $stationName);
//create a new instance of icws
$attrebutes = array('AccoRDI_mid','AccoRDI_account_id');
$icws = new ICWS\Connection($conf, $attrebutes, true);
$messaging = new ICWS\Messaging($icws);
$messaging->processMessages();
session_write_close();
$result = $messaging->getCallsQueue();
echo 'event: getMessagingQueue' . PHP_EOL;
echo 'data: ' . json_encode( $result) . PHP_EOL;
ob_end_flush();
flush();
usleep($sleepTime * 1000000);
}
The problem here is that I get the following warning/notices
Warning: session_start(): Cannot send session cache limiter - headers already sent and
Notice: ob_end_flush(): failed to delete and flush buffer. No buffer to delete or flush i
The reason that I need the session_start() in the loop is because I will need to unlock the session file immediately after reading it before the script sleeps.
The idea is to
Start/resume session
Update session values
Display the content of the session
Unlock/release the session to allow other processes to use it
Go back to step 1
What can I do to avoid the errors here?
In your specific case... I would do without sessions. A session is just a serialized object into a temporary (well, garbage-collected) file. Who's to stop you from writing your own alternate session handler?
In its most basic form, leaving aside error checking and recovery, it's something like
function getAlternateSessionData() {
$raw = file_get_contents(getAlternateSessionFileName());
return unserialize($raw);
}
and the same with file_put_contents() to save the session. You do have to consider how to guard against concurrent access to the same session file by two different server processes. But you can send the session ID cookie at the beginning only, thus never actually "restarting" the session.
Another possibility would be to implement your alternate session using memory - if the server process does not terminate - or something like Redis (you can do that with ordinary sessions too, but those would send a session cache limiter).
You can also use session_set_save_handler() to override the file reading and writing functions of PHP with functions of your own, leaving all the session handling (cookies, etc.) intact. By handling yourself the session file management, you would be able to commit the session whenever you wanted. Still, you would need to consider carefully how to go with concurrent access to the session files, even if they are compatible with PHP's own session handler (if they aren't, you will have to include your session handler in every script accessing the "new" sessions).
As for ob_end_flush(), I believe that's because you never did call ob_start(). You can check the ob* level using ob_get_level() and flush it securely before the loop using ob_end_clean(), then instate a ob_start()/ob_end_flush() pair inside the loop.
Instead of starting the session for each loop, start it only if it hasn't already been started.
For PHP 5.4 and higher:
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
For PHP 5.3 and lower:
if(session_id() == '') {
session_start();
}
You can't call ob_end_flush() without calling ob_start() first, but You can just use ob_flush() for what you are trying to do.
Lastly, are you doing this in a browser? The browser will give up on the page if it takes to long too respond, and looping indefinitely will eventually cause a time out. You'd be better off using ajax/javascript, or a meta refresh tag instead of looping on the PHP side.

Php, share SESSION between requests, forcing session_id

My goal is to share session between requests, I meant every request could reach a data. First I was thinking that simply sharing via filesystem could be good, but I find out this by myself:
session_id('0');
session_start();
echo session_id();
var_dump ($_SESSION);
if (!isset($_SESSION['x']))
{
$_SESSION['x'] = 0;
}
$_SESSION['x']++;
var_dump ($_SESSION);
this way I can see the same from browsers. My question is, is it a good practice?
EDIT: here is the full working version:
$m = microtime(true);
session_start();
if (session_id() == '0') // this happens when somehow our session id sticks, it should not happen ever, but if so, lets erase it
{
setcookie (session_name(), '', time() - 3600);
session_destroy();
session_write_close();
echo 'reload'; die;
}
if (!isset($_SESSION['x']))
{
$_SESSION['x'] = 0;
}
$_SESSION['x']++;
$saveId = session_id();
session_write_close();
// switch to common storage
session_id('0');
session_start();
if (!isset($_SESSION['common']))
{
$_SESSION['common'] = 0;
}
$_SESSION['common']++;
session_write_close();
// back to our own session
session_id($saveId);
session_start();
echo $_SESSION['x'].'<br>'.(microtime(true) - $m); die;
I dont thing its very time consuming.
It's tricky to know if SESSION is the right place to put this data, but it's worthwhile bearing some things in mind.
SESSION is designed to store data related to an individual user's visit to your site (normally being distinguished the combination of machine and browser, thanks to the session id being stored in a client side cookie).
Default behaviour of the PHP session handler is to:
Store the data in a file on the server.
Block concurrent access to that file.
It is possible to have multiple sessions for a given request, but that means ensuring you start and end each session and ensure that you keep track of the session IDs - I'm not entirely sure how you would do this without manually writing data into the client's cookie.
All in all you'll probably find that your performance using the session will be slower that just checking the existence of the file anyway (which is simpler than using the session, in terms of work done by PHP).
That said, if you're writing to that file then you're just going to have concurrency issues that you'll have to solve in much the same way as php sessions do anyway.
I'd say, if you're writing data, then look to your DB. It's what they're designed for.
If you don't want to write to your primary DB and have good reason for that, then maybe consider something like a memcache DB, or some other secondary storage.

Access all server session variables

I thought about some helper to manage session variables on my development localhost.
I would like to read,change and delete session variables from ALL vhosts on my machine.
As far i know, from PHP i can access only current host session variables, which will be populated to $_SESSION variable, after call to session_start. That's not enough for my needs.
After some research (ex. Access active sessions in PHP) i found solution :
Load all files from php session directory
Use session_decode to load data to $_SESSION variable
Read/change/delete some variables
Use session_encode to encode my session back to file
There is any better method to do that?
Maybe there is already dedicated tool for that task?
EDIT:
Another solution which i now currenty use is just enable debugger in IDE for desired project, and use watches/stack window to edit session.
EDIT2:
I dont want any project-specific solution like storing session data in database. This should work "out-of-the-box", for any project.
For better understanding, use example:
In magento admin panel grid filters are stored in session variables.
When you enable broken filter there is an error page, and you can't easily disable this filter without removing session cookie, or setting debugger for it.
I spend some time once with a script that programatically "expires" server sessions to obtain info about user inactivity. On that occasion, I had a database with session_ids to transverse, but it can be done with directory listing (note that are php's compilation specific advices on code)
//Preserve runtime variable
$pre_config = ini_get('session.use_cookies');
//Check previous session_start();
if($previous_sid = session_id()){
//close and save current session
session_write_close();
}
//To determine where php session files are stored, see http://stackoverflow.com/questions/4927850/location-for-session-files-in-apache-php
if(($save_path = session_save_path()) === ''){
// PHP compilation specific
// There are diferences between CLI (saves in sys_get_temp_dir()) and mod_php (where session_save_path() works) on Fedora 12
$save_path = '/var/lib/php/session/';
session_save_path('/var/lib/php/session/');
}
//Avoid new cookies from other session_start()
ini_set('session.use_cookies', false);
//Do the magic
if ($handle = opendir($save_path)) {
while (false !== ($entry = readdir($handle))) {
if (strpos($entry, 'sess_') === 0) {
$session_id = str_replace('sess_' , '', $entry);
session_id($session_id);
session_start();
//Do whatever you want with this session, using $_SESSION
session_write_close();
}
}
closedir($handle);
}
//Restore previous configuration
ini_set('session.use_cookies', $pre_config);
//Restore previous session
if($previous_sid) {
session_id($previous_sid);
session_start();
}
I do not recommend running this code on client request. Remember that session_start block script execution while "session id" is in use on another request. And I think that is not recommended to run it by a CLI script, because PHP embedded on WebServers and PHP CLI uses different environment and configurations (besides the fact that the running user of a PHP CLI must be the same user that runs the WebServer to prevent permission deny).
I think that the ideal solution is to trigger requests to this script with a HTTP GET periodically (by linux crontab or similar), of course this solution depends on final use been planned. If HTTP request trigger can be done, the part of the code that saves previous settings and session can be eliminated.

What happens to the $_SESSION array if a PHP session times out in the middle of a request?

I have always wondered, if a PHP session times out during the middle of executing a script, will the contents of the $_SESSION array still be available until script execution ends? For example:
session_start();
if(! isset($_SESSION['name'])) {
echo 'Name is not set';
exit;
}
// imagine there is a bunch of code here and that the session times out while
// this code is being executed
echo 'Name is ', $_SESSION['name']; // will this line throw an error?
Is it practical to copy session variables to the local scope so I can read them later on in the script without having to keep checking for a session time out? Something like:
session_start();
if(isset($_SESSION['name'])) {
$name = $_SESSION['name'];
} else {
echo 'Name is not set';
exit;
}
// bunch of code here
echo 'Name is ', $name;
don't worry about such things. Nothing will happen to the session. It's initialised by sessioni_start() and $_SESSION will be always available within your script.
The default three-hour session lifetime is reset each time you open the session (see session_cache_expire), so the only way a session could time out in the middle of a request is if a request takes three hours to process. By default PHP requests time out after just 30 seconds, so there's no danger of session expiry during a request. Furthermore, the $_SESSION variable won't suddenly change in the middle of a request. It's populated when the session starts, and that's it.
The variables are copied into the $_SESSION global at the initial request, so it has the same effect as copying it to a local variable.
However, for clarity sake, it makes sense to copy it to a local variable. Especially if you plan to use the variable several times. It can be difficult to read code that has $_SESSION['variable'] all over the place.
What you needed to understand is how sessions work. A client accessing a script using a $_SESSION super global only knows the key to the session that belongs to them (Stored in Cookie/URL). This means the session data itself has nothing to do with the client. If you have the key to the session data you want to use then you can use it. Older versions of PHP had some security holes because sessions where stored somewhere that was easily accessible (I don't remember details).
Basically, if you have the session id in a PHP script you have access to that session unless the memory on the machine is flushed/harddrive is corrupt (ie Computer Restart/Device Failure).
Hope this helps, otherwise go to php.net and dive into the details on how sessions work.

Categories