Nette Http 3.1: much smarter sessions

3 years ago by David Grudl  

Nette has always approached sessions with caution. It started them automatically only when the user needed them, in order to conserve server resources. The new version 3.1 takes this philosophy even further and brings improvements that make working with sessions even more efficient.

When are sessions automatically started?

  1. when writing to them
  2. when reading from them and the browser has sent a cookie with the session ID

Because if the cookie doesn't exist, we don't need to start the session to know there's no data in it. So even without starting, Nette can return for example the shopping cart content as empty, etc.

This entire mechanism has certain pitfalls that the new version 3.1.5 addresses.

Late initialization

In the entire session management process, three elements are important: the session identifier (ID) contained in the cookie sent by the browser, the corresponding file on disk or database record containing session data, and the session ID that the server sends to the browser when first starting the session (so it can store it as a cookie).

The automatic startup is related to the problem of late session initialization, which occurs when output has already been sent from the server and HTTP headers can no longer be sent. When starting a session, you need to send a cookie with the session ID, as well as the Pragma and Expires headers, which is not possible after sending output. To prevent this problem, with the setting “autoStart: smart” (which is the default value), Nette automatically started the session at application startup if a cookie with session ID existed. This is changing, see below.

Accessing Data

Data in a session section is often accessed similarly to variables:

$section = $session->getSection('unique name');
$section->userName = 'john';
echo $section->userName;

However, due to a specific technical feature of PHP, the operation echo $section->userName is detected as a write. This means that even when we only want to read data (like cart contents using $cart = $section->cart), PHP evaluates it as a write and starts the session, even if the cookie doesn't exist. This eliminates the advantage of efficient autostart, which was supposed to start sessions only when truly necessary.

This problem can be bypassed using ArrayAccess syntax, which correctly distinguishes between reading and writing:

$section['userName'] = 'john';
echo $section['userName'];  // this is a read operation

However, ArrayAccess syntax might be too magical for users and sometimes doesn't work as expected (e.g., $section['cart'][] = $item generates a notice Indirect modification of overloaded element has no effect).

Therefore, in the latest versions of both 3.1 and 3.0 series, you'll find a clear set of methods set(), get(), and remove() for accessing data, which don't have the disadvantages of previous approaches and work correctly with session autostart.

Using set(), you can also set expiration:

$section->set('flash', $message, '30 seconds');

Using these methods thus becomes the preferred approach to data in sessions. Nette uses them internally everywhere.

Nette has updated its behavior in situations where a user sends an ID in a cookie that doesn't correspond to any file on disk or database record. Previously, Nette would generate a new ID and create a new session file to prevent possible ID spoofing by an attacker. Now, instead of that, Nette deletes the cookie and doesn't create a file at all. This is to prevent empty files from being created on disk if an attacker deliberately spoofs session IDs.

New autoStart Configuration

In the configuration file, the autoStart option can now take the values always and never. The first one turns on the session always right after start and is identical to the true variant. The new option is never, which completely turns off automatic starting, and the session is only turned on if you call $session->start() yourself. The default option remains smart, but its behavior changes from version 3.1, as I mentioned in the introduction. It no longer turns on the session automatically after start but does so only when reading or writing. The value false has identical behavior from version 3.1.

In the older v3.0 series, the default setting is conversely changing from smart to false, which turns off autostart after launch.

Events $onStart, $onBeforeWrite

Finally, the Nette\Http\Session object now has events $onStart and $onBeforeWrite, so you can add callbacks that are triggered after session start or before it's written to disk and subsequently closed. These events are particularly useful for debugging and detecting places in code where automatic session startup occurs, which helps you optimize session work.

$session->onBeforeWrite[] = function () {
	// we'll write data to the session
	$this->section->set('basket', $this->basket);
};

When testing the new behavior, remember that in development mode, Tracy starts the session because it uses it to display redirection bars and AJAX requests in the Tracy Bar. From version 2.9, Tracy no longer uses sessions, precisely to better debug session functionality.