Nette Http 3.1: much smarter sessions

21 days ago by David Grudl     edit

Nette has Nette has always taken a cautious approach to sessions. It only started them automatically when users needed them. Which specifically means:

  1. when they write to it
  2. when they read from it and there is a session ID cookie

Because if the cookie doesn't exist, we don't need to run the session to know there's no data in it. So without running it, Nette might return the contents of the shopping cart as empty, etc.

Related to auto-starting is the problem of late session start, i.e. at a time when the output has already been sent from the server and HTTP headers can no longer be sent. When the session is started, a cookie with the session ID is sent, as well as headers Pragma and Expires. In order to minimize this problem, Nette, when set to autoStart: smart, which is the default, automatically started the session at startup if a session ID cookie existed.

However, there are some pitfalls that version 3.1.3 solves.

Data Access

Data in the session section is often accessed as variables:

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

However, due to a specific technical limitation of PHP, the echo $section->userName operation is detected as a write. Thus, even the above mentioned cart read $cart = $section->cart will start a session even if the cookie does not exist. Which spoils the efficiency of autostart.

There is a way around this in Nette, using the ArrayAccess syntax, which does not suffer from this problem:

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

However, the ArrayAccess syntax can be too magical for the user, and sometimes doesn't work as one might expect (e.g. $section['cart'][] = $item generates a notice Indirect modification of overloaded element has no effect). Therefore, in the latest versions of the 3.1 and 3.0 series, you will find a comprehensible set of methods set(), get() and remove() for accessing data. The expiration can also be set using set():

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

Thus, using these methods becomes the preferred way to access data in sessions.

Nette also no longer automatically starts a session when the application starts if there is a cookie with a session ID. This was not a system solution to the late session start problem. Because it would still occur if the cookie didn't exist.

Furthermore, Nette modifies the behavior when the session ID turns out to be invalid, i.e. when there is no corresponding file on disk, etc. Nette detected such a situation and generated a new ID and a clean file to prevent a possible ID spoofing by an attacker. Now, instead, it deletes the cookie and does not create the file at all. The reason for this is to avoid creating empty files on disk if an attacker is intentionally spoofing the session ID.

New autoStart Configuration

Option autoStart can now take new values always and never. The always turns on the session immediately after startup and is identical to option true. A new option is never, which turns off auto-start completely and only turns on the session if you call $session->start(). The default variant remains smart which, as mentioned above, no longer turns on the session after startup, but only in the case of a read or write. It is actually so identical to false.

The v3.0 series changes the default from smart to false, which also turns off autostart after startup.

Events $onStart, $onBeforeWrite

And finally, the Nette\Http\Session object now has $onStart and $onBeforeWrite events, so you can add callbacks that fire after a session starts or before it is written to disk and then terminated.

$session->onBeforeWrite[] = function () {
	// zapíšeme data do session
	$this->section->set('basket', $this->basket);
};

When testing the new behavior, keep in mind that Tracy starts a session in developer mode because it uses it to display redirect bars and AJAX requests in the Tracy Bar.

Further reading