PHP for beginners. Session

  • Tutorial
ElePHPant.  PHP for beginners.  Session

Have a nice day, everyone. Here is the first article in the PHP series for novice developers. It will be an unusual series of articles, there will not be echo "Hello World"here, there will be a hardcore from the life of PHP programmers with a small admixture of "homework" to consolidate the material.

I'll start with the sessions - this is one of the most important components that you have to work with. Not understanding the principles of his work - bloat up business. So in order to avoid problems, I will try to tell about all the possible nuances.

But for a start, in order to understand why we need a session, let us turn to the sources - to the HTTP protocol.

HTTP Protocol


The HTTP protocol is the HyperText Transfer Protocol - the “hypertext transfer protocol” —that is in fact - a text protocol, and it is easy to understand.
Initially it was meant that under this protocol only HTML would be transmitted, the name and the title, and now they just don’t send and = ^. ^ = And (• _ ㅅ _ •)

In order not to beat around the bush, let me give you an example of communication over the HTTP protocol.
Here is an example of a request that your browser sends it when you request a page http://example.com:

GET / HTTP/1.1
Host: example.com
Accept: text/html
<пустая строка>

Here is an example answer:

HTTP/1.1 200 OK
Content-Length: 1983
Content-Type: text/html; charset=utf-8
<html>
<head>...</head>
<body>...</body>
</html>

These are very simplified examples, but even here you can see what the HTTP request and response consist of:

  1. the starting line - for the request contains the method and path of the requested page, for the response - the protocol version and the response code
  2. headers - have a key-value format separated by a colon, each new header is written on a new line
  3. body of the message - directly HTML or data is separated from the headers by two line breaks, may be absent, as in the above request

So, sort of dealt with the protocol - it is simple, it has its history since 1992, so you can’t call it ideal, but what is - send a request - get an answer, and that's all, the server and the client are no longer connected. But such a scenario is by no means the only possible one, we can have authorization, the server should somehow understand that this request came from a specific user, i.e. client and server should communicate within a certain session. And yes, they came up with the following mechanism:

  1. When authorizing a user, the server generates and remembers a unique key - the session identifier, and reports it to the browser
  2. The browser saves this key, and with each subsequent request, it is sent

To implement this mechanism, cookies were created (cookies, cookies) - simple text files on your computer, by file for each domain (although some browsers are more advanced, and use a database for storing SQLite), while the browser imposes a limit on the number of records and the size of the stored data (for most browsers it is 4096 bytes, see RFC 2109 from 1997)
Those. if you steal a cookie from your browser, then you can go to your facebook page on your behalf? Do not worry, you can not do this, at least with facebook, and then I will show you one of the possible ways to protect against this type of attack on your users.

Let's now see how our request-response will change, be there authorization:

Request
POST /login/ HTTP/1.1
Host: example.com
Accept: text/html
login=Username&password=Userpass


Our method has changed to POST, and in the request body we have passed the login and password. If you use the GET method, the query string will contain a login and password, which is not very correct from an ideological point of view, and has a number of side effects in the form of logging (for example, in the same access.log) and caching of passwords in open form.

Response
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: KEY=VerySecretUniqueKey
<html>
<head>...</head>
<body>...</body>
</html>

The server’s response will contain a header Set-Cookie: KEY=VerySecretUniqueKey, which will force the browser to save this data into cookies, and the next time you access the server, they will be sent and recognized by the server:

Request
GET / HTTP/1.1
Host: example.com
Accept: text/html
Cookie: KEY=VerySecretUniqueKey
<пустая строка>

As you can see, the headers sent by the browser (Request Headers) and the server (Response Headers) are different, although there are also common ones for both requests and responses (General Headers)

The server has recognized our user by the sent cookies, and will further provide him with access to personal information. So, well, sort of dealt with HTTP and sessions, you can now return to PHP and its features.

PHP and session


I hope you already have PHP installed on your computer. then I will give examples, and they will need to run

PHP language was created to be the HTTP protocol - i.e. Its main task is to respond to an HTTP request and "die" by freeing memory and resources. Consequently, the session mechanism works in PHP not in automatic mode, but in manual mode, and you need to know what to call, and in what order.
Here you have an article on PHP is meant to die , or here it is in Russian , but it is better to set it aside for “later”.

First of all, you need to "start" the session - for this we use the function session_start () , create a file session.start.php with the following contents:

<?php
session_start();

Run the PHP built-in web server in the folder with your script:

php -S 127.0.0.1:8080

Launch the browser and open the Developer Tools (or whatever you have ) in it, then go to http://127.0.0.1:8080/session.start.php - you should see only a blank page, but do not rush to close it - look on the headers sent to us by the server: There will be a lot of things, we are only interested in this line in the server’s response (clean the cookie if there is no such line, and refresh the page):

Cookie



Set-Cookie: PHPSESSID=dap83arr6r3b56e0q7t5i0qf91; path=/

Having seen this, the browser will keep a cookie with the name `PHPSESSID`:

Browser session cookie

PHPSESSID- the default session name is adjusted from the php.ini config by the session.name directive , if necessary, the name can be changed in the configuration file itself or using the session_name () function

And now - we are updating the page, and we see that the browser sends this cookie to the server, you can try to refresh the page a couple of times, the result will be identical: Total, what we have - the theory coincided with the practice, and this is just fine. The next step is to save an arbitrary value in the session. For this, PHP uses a super-global variable , we will save the current time — for this we call the date () function :

Browser request with cookie



$_SESSION

session_start();
$_SESSION['time'] = date("H:i:s");
echo $_SESSION['time'];

Update the page and see the server time, update again - and the time is updated. Let's now make sure that the set time does not change with each page refresh:

session_start();
if (!isset($_SESSION['time'])) {
    $_SESSION['time'] = date("H:i:s");
}
echo $_SESSION['time'];

Update - time does not change, what you need. But at the same time, we remember that PHP is dying, which means that it stores this session somewhere, and we will find this place ...

All secret becomes clear


By default, PHP stores the session in files - the session.save_handler directive is responsible for this , the path by which the files are saved look for in the session.save_path directive , or use the session_save_path () function to get the required path.
In your configuration, the path to the files may not be specified, then the session files will be stored in the temporary files of your system - call the sys_get_temp_dir () function and find out where this hidden place is.

So, go along this path and find your session file (I have this file sess_dap83arr6r3b56e0q7t5i0qf91), open it in a text editor:

time|s:8:"16:19:51";

As you can see, this is our time, this is how tricky our session is stored, but we can make edits, change the time, or we can simply enter any string, why not:

time|s:13:"\m/ (@.@) \m/";

To convert this string to an array, you need to use the session_decode () function , for the inverse transformation - session_encode () - this is called serialization, that's just in PHP for sessions - it has its own - special, although you can use standard PHP serialization - write in session configuration directive .serialize_handler value php_serializeand you will be happy, and $_SESSIONcan be used without restrictions - as the index now you can use numbers and special characters |, and !in the name (for all 10+ years, never had :)

The task
Write your function similar in functionality session_decode(), here's a test data set for the session (to solve the knowledge of regular expressions is not required), take the text for conversion from the file of your current session:

$_SESSION['integer var'] = 123;
$_SESSION['float var'] = 1.23;
$_SESSION['octal var'] = 0x123;
$_SESSION['string var'] = "Hello world";
$_SESSION['array var'] = array('one', 'two', [1,2,3]);
$object = new stdClass();
$object->foo = 'bar';
$object->arr = array('hello', 'world');
$_SESSION['object var'] = $object;
$_SESSION['integer again'] = 42;


So that we have not tried? That's right - to steal cookies, let's launch another browser and add the same cookie to it. I wrote you a simple javascript for this, copy it into the browser console and run it, just do not forget to change the session ID to your own:

javascript:(function(){document.cookie='PHPSESSID=dap83arr6r3b56e0q7t5i0qf91;path=/;';window.location.reload();})()

Now you have both browsers looking at the same session. I mentioned above that I will talk about the ways of protection, consider the easiest way - we will tie the session to the browser, or rather to how the browser is presented to the server - we will memorize the User-Agent and check it every time:

session_start();
if (!isset($_SESSION['time'])) {
    $_SESSION['ua'] = $_SERVER['HTTP_USER_AGENT'];
    $_SESSION['time'] = date("H:i:s");
}
if ($_SESSION['ua'] != $_SERVER['HTTP_USER_AGENT']) {
    die('Wrong browser');
}
echo $_SESSION['time'];

It is more difficult to fake, but it is still possible, add more saving and checking here $_SERVER['REMOTE_ADDR']and $_SERVER['HTTP_X_FORWARDED_FOR'], and this will more or less be like protection from intruders encroaching on our cookies.

The keyword in the previous paragraph seems to be running in HTTPS protocol in real projects for a long time, so no one can steal them without physical access to your computer or smartphone


It is worth mentioning the session.cookie-httponly directive , thanks to it the session cookie will be inaccessible from JavaScript. In addition, if you look into the manual of the setcookie () function , you will notice that the last parameter is also responsible for HttpOnly. Keep this in mind - this setting allows you to effectively deal with XSS attacks in almost all browsers .

The task
Add to the code a check on the user's IP; if the check fails, delete the compromised session.

Steps


And now I will explain step by step the algorithm, how the session works in PHP, using the example of the following code (default settings):

session_start();
$_SESSION['id'] = 42;

  1. after calling session_start()PHP, it looks for a session identifier in the cookie by name written in session.name- this isPHPSESSID
  2. if there is no identifier, then it is created (see session_id () ), and creates an empty session file along the path session.save_pathnamed sess_{session_id()}, headers will be added to the server’s response, to set a cookie{session_name()}={session_id()}
  3. if the identifier is present, then look for the session file in the folder session.save_path:
    • We do not find - create an empty file with the name sess_{$_COOKIE[session_name()]}(ID can contain only characters in the range a-z, A-Z, 0-9, comma, and a minus sign)
    • find, read the file and unpack the data (see session_decode () ) into a super-global variable $_SESSION(the file is locked for reading / writing)
  4. when the script has finished its work, all the data is $_SESSIONpacked using session_encode()the file along the path session.save_pathwith the name sess_{session_id()}(the lock is removed)

The task
Set in your browser an arbitrary cookie value with the name PHPSESSID, let it be 1234567890, refresh the page, check that you have created a new filesess_1234567890

Is there life without cookies?


PHP can work with the session even if cookies are disabled in the browser, but then all the URLs on the site will contain a parameter with the ID of your session, and yes - do you need to configure this, but do you need it? I did not have to use it, but if I really want to - I’ll just say where to dig:


And if you need to store the session in the database?


To store the session in the database, you will need to change the session repository and tell PHP how to use it, the SessionHandlerInterface interface and the session_set_save_handler function have been created for this purpose .
Separately, I note that you do not need to write your own session handlers for redis and memcache - when you install these extensions, the corresponding handlers go along with them, so RTFM is our everything. Well, yes, the handler must be specified before the call session_start();)

The task
Реализуйте SessionHandlerInterface для хранения сессии в MySQL, проверьте, работает ли он.
Это задание со звёздочкой, для тех кто уже познакомился с базами данных.


When does a session die?


During the lifetime of the session meets the directive session.gc_maxlifetime . By default, this directive is equal to 1440 seconds (24 minutes), it should be understood so that if the session was not addressed within a specified time, the session will be considered “spoiled” and will wait for its turn for deletion.

Another question is interesting, can you ask it to the hardcore developers - when does PHP delete overdue session files? The answer is in the official manual, but not in an explicit form - so remember: the

garbage collection can be started when a function is called session_start(), the probability of starting depends on two directives session.gc_probability and session.gc_divisor, the first acts as a dividend, the second - the divisor, and by default these values ​​are 1 and 100, i.e. The probability that the collector will be launched and the session files will be deleted - approximately 1%.

The task
Измените значение директивы session.gc_divisor так, чтобы сборщик мусора запускался каждый раз, проверьте что это так и происходит.


The most trivial error


Error with more than half a million results in the issuance of Google:

Cannot send session cookie - headers already sent by
Cannot send session cache limiter - headers already sent

To get one, create a session.error.php file with the following contents:

echo str_pad(' ', ini_get('output_buffering'));
session_start();

In the second line, the strange "magic" is the focus with the output buffer, I will tell you about it in one of the following articles, so far consider it only a string of 4096 characters long, in this case, these are all spaces

Start by deleting the cookie and get the errors, though the text of the errors is different, but the essence is the same - the train is gone - the server has already sent the page contents to the browser, and sending the headers is too late, it will not work, and the coveted session identifier did not appear in the cookies. If you are stumbled with this error - look for a place where the text is displayed ahead of time, it can be a space before characters <?php, or after ?>in one of the included files, and it’s okay if it’s a space, maybe some thread is an unprintable character like BOM , so Be attentive, and this this infection will not touch you (as well ... Homeric laughter).

The task
Для проверки полученных знаний, я хочу, чтобы вы реализовали свой собственный механизм сессий и заставили приведенный код работать:

require_once'include/sess.php';
sess_start();
if (isset($_SESS["id"])) {
    echo $_SESS["id"];
} else {
    $_SESS["id"] = 42;
}

Для осуществления задуманного вам потребуется функция register_shutdown_function()



Lock


Another common mistake for newbies is an attempt to read the session file while it is locked by another script. Actually, this is not quite a mistake, this is a misunderstanding of the principle of blocking :)

But let's take another step:

  1. session_start() not only creates / reads a file, but also blocks it, so that no one can make edits at the time of the script execution, or read non-consistent data from the session file
  2. blocking is removed at the end of the script


"Stuck" in this error is very easy, create two files:

// start.php
session_start();
echo"OK";


// lock.php
session_start();
sleep(10);
echo"OK";


Now, if you open the page in the browser lock.php, and then open start.phpit in a new tab, you will see that the second page will open only after the first script that blocks the session file for 10 seconds has been processed.

There are a couple of options for how to avoid this phenomenon - "clumsy" and "thoughtful."

"Clumsy"
Use samopisny handler sessions in which "forget" to implement a lock :)
Slightly better option is to take the ready and turn off the lock (eg in memcached have this option - memcached.sess_locking ) O_o
spend hours on the debug code in search of rare pop-up error ...

"Thoughtful"
Where it is better to follow the session lock yourself, and take it off when it is not required:

- If you are sure that you do not need to make changes to the session data, use the option read_and_closewhen starting the session:


session_start([
    'read_and_close' => true
]);


Thus, the lock will be released immediately after reading the session data.

- If you still need to make changes to the session, after making these, close the session from the recording:


session_start();
// some changes
session_write_close();


The task
Чуть выше был приведён листинг двух файлов start.php и lock.php, создайте ещё файлы read-close.php и write-close.php, в которых вы будете контролировать блокировку перечисленными способами. Проверьте как работает (или не работает) блокировка.


Finally


In this article, you are given seven tasks, while they concern not only working with sessions , but also introduce you to MySQL and the functions of working with strings . To master this material - a separate article is not needed, the manual on the links provided is enough - no one will read it for you. Dare!

PS If you learned something new from the article - thank the author - zasharte article in social networks;)
PPS Yes, this is a cross-post article from my blog , but it is still relevant today :)

A series of articles "PHP for beginners":


Also popular now: