Asked  7 Months ago    Answers:  5   Viewed   41 times

How do I reopen a session in PHP without getting header already sent warnings?

After setting all the session vars I like to set, I close the session with session_write_close(). I do this, because as long as the session is open, there may be only one active connection from the same client. But I like to have multiple parallel ones.

However if I like to set another session variable at a later point, I need to reopen the session with session_start() once again. This works, but as I already send code to the client it prints "headers already sent"-warnings. Why is it trying to set the cookie again? The cookie is already set. Only thing I need is to gain access to writing the session files on the server again.

Well, I can suppress them. But is there a way of reopening a session, that has been closed with session_write_close without re-sending the Cookie-header? The Cookie-header is already sent correctly by the first session_start(). So the second one just needs to give me back access to writing to the session files stored on the web server.

<?php
session_start();
// setting all the session vars I like to set
session_write_close(); // <-- // To allow parallel requests by the same user, while this script is still running

// Code that takes some time to execute
// It also prints output, so no more cookie headers after this point

@session_start(); // <-- works, but I like to use it without suppressing warnings
$_SESSION['key'] = 'new value I like to store';
session_write_close();
?>

 Answers

94
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);
ini_set('session.cache_limiter', null);
session_start(); // second session_start

This will prevent php from calling php_session_send_cookie() a second time.
See it working.

Though restructuring the scripts still seems to be the better option...


For PHP 7.2+, you will basically need to re-implement session cookies to avoid errors and warnings:

ini_set('session.use_only_cookies', false);
ini_set('session.use_cookies', false);
ini_set('session.use_trans_sid', false);
ini_set('session.cache_limiter', null);

if(array_key_exists('PHPSESSID', $_COOKIE))
    session_id($_COOKIE['PHPSESSID']);
else {
    session_start();
    setcookie('PHPSESSID', session_id());
    session_write_close();
}

session_start();
...
session_write_close();
...

session_start(); // second session_start
Wednesday, March 31, 2021
 
Len_D
answered 7 Months ago
85

Memcache is your best bet for a lot of reasons:

  1. It's REALLY fast - Everything's in memory, and it's highly optimized for situations just like yours (and caching in general :)
  2. It's distributed - This means that if you have multiple web / app servers running, they can all access the same cache
  3. You can pool multiple servers for memcache - If you've got a few servers that are relatively underutilized (or several dedicated cache servers), you can pool them all together into one big cache
  4. It's super-scalable (for the reasons mentioned prior)
  5. It's got great PHP support - The PECL package for memcache was recently updated with a lot of new goodness
  6. You can even store your user sessions in memcache - just set it up in your php.ini file. This is much faster than storing sessions in databases, and allows your sessions to persist across multiple web hosts (if you're in a load balanced situation)... this will also give your site a bit of a performance boost as there's no need to hit the filesystem / database for session info on every request.

... and many more ;)

As to some of your concerns about memory footprint of individual cached items you've got a few options. My initial thought is to just give it a whirl, see how big these cache items really get (you can find several open-source things to monitor the actual cache usage, such as cacti). I think they'll be smaller than you'd think.

If they're not, I'd suggest re-thinking your cache strategy as far as what you actually cache, for how long, etc. Maybe you could build the feed from several things already in the cache (i.e. cache individual user data, and then build the feed for a person from all those individual items in cache). There are a lot of good articles out there on that front, just search 'em out :)

Wednesday, March 31, 2021
 
petersaints
answered 7 Months ago
29

As it turns out, @hakre was on the right path, but the change didn't solve the problem. The problem is that Plesk directly assigns *.php files to be processed through php_fpm. In the Virtual Host Apache configuration file we have...

<Files ~ (.php$)>
    SetHandler proxy:unix:///var/www/vhosts/system/wwphelps.com/php-fpm.sock|fcgi://127.0.0.1:9000
</Files>

This is how Plesk is specifically attaching one version of PHP over another by domain name. And anything that doesn't fall into this rule is interpreted by Apache's global rules, which for me are looking at a different install of PHP. (Why Plesk doesn't have a global override to point at their own PHP installs is a bit of a wonder, but I suspect this is a bug they've never encountered before.) So, logically, we'd need only add for each file we want to process without the .php suffix...

<Files action1>
    SetHandler proxy:unix:///var/www/vhosts/system/wwphelps.com/php-fpm.sock|fcgi://127.0.0.1:9000
</Files>

It is true that you need to do this, but for me it didn't work as advertised. My web page simply said "Access Denied" and my error files pointed me to FPM's security.limit_extensions paramter. In other words, despite specifically identifying a file I wanted to use without a suffix, FPM rejected it anyway. Here's where I got lazy. I reset the variable to nothing. In Plesk that's done by creating or modifying a php.ini file inside the domain's conf directory and adding (including the header if it's not already there)...

[php-fpm-pool-settings]
security.limit_extensions =

Restart Apache and Bob's your uncle.

According to a really rapid Google search, the ability to directly modify FPM parameters from inside Plesk is still up for debate.

Now, this comes with a price. From the perspective of the FPM socket, suddenly any file in you're web root "could" be executed as a PHP file, including images customers upload and you blindly put in your [ROOT]/images directory. You're partially saved by the fact that unless you've told Apache otherwise, just any old file won't be interpreted as a PHP file. However, you'd be better protected if all files you upload through your site are (a) thoroughly vetted to be sure they are what they claim to be and (b) are either saved outside the web root or in a DB so that nobody can "execute" them by referring to them directly.

Finally, there ought to be a way to override in a config file those files that I want to intentionally violate security.limit_extensions. Unfortunately, FPM and Apache don't appear to talk to each other, otherwise the use of the block would completely override security.limit_extensions. For all I know there's a way to do it. I'd be curious to know, but I have a working solution, so I'm back to work.

Friday, May 28, 2021
 
employeegts
answered 5 Months ago
94

The problem:

session_start() relies on $_COOKIE[session_name()], so, if you edit the cookie value to something like #$#$FDSFSR#"#"$"#$" or simply empty it (not delete the cookie) and refresh a page with your code:

if (!session_id()) {
    session_start();
}

The following warning is generated:

PHP Warning: session_start(): The session id is too long or contains illegal characters, valid characters are a-z, A-Z, 0-9 and '-,' in /home/username/public_html/session_start.php on line 7

This happens because php is checking if session_id() exists and, in fact, it exists, but contains illegal characters not allowed as session_id name.

A valid session id may contain only digits, letters A to Z (both upper and lower case), comma and dash ([-,a-zA-Z0-9]) between 1 and 128 characters.


My solution:

Check if $_COOKIE[session_name()] is set and contains a valid session_id prior to session_start(), otherwise, delete the session cookie and only then session_start(), something like:

function safeSession() {
    if (isset($_COOKIE[session_name()]) AND preg_match('/^[-,a-zA-Z0-9]{1,128}$/', $_COOKIE[session_name()])) {
        session_start();
    } elseif (isset($_COOKIE[session_name()])) {
        unset($_COOKIE[session_name()]);
        session_start(); 
    } else {
        session_start(); 
    }
}

start the session:

safeSession();

NOTES:

1 - session_name is defined on your php.ini as session.name = SOMETHING (default is PHPSESSID), so, you may be looking for a cookie matching session.name. You can use the session_name() function to retrieve it.

2 - Session cookie manipulation can be used by hackers to dump information from your server (username and path) if ini_set('display_errors', 1); is set.

3 - session_regenerate_id(true) works but, because it checks the current session_id prior to assign a new one, generates warnings.

4 - I've tested the code with several invalid session names and no errors or warnings were generated, everything worked and intended.


References:

session.c Source Code

Saturday, May 29, 2021
 
SpiderLinked
answered 5 Months ago
70

You need to unset the session vars. See http://php.net/manual/de/function.session-unset.php

Means, put session_unset() before you destroy the session.

Saturday, May 29, 2021
 
sholsinger
answered 5 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :