Весь сучасний веб побудований на моделі взаємодії клієнта і сервера. Як вона працює:
браузер користувача (клієнт) відправляє на сервер запит з адресою сайту (URL);
сервер отримує запит і віддає клієнту запитаний контент.
Для реалізації процесу використовується універсальний протокол HTTP.
Програмувати на PHP можна і без знання протоколу HTTP. Але для вирішення низки завдань потрібно знати, як саме працює веб-сервер. Адже PHP - це насамперед серверна мова програмування.
👉 Протокол HTTP дуже простий і складається з трьох частин:
Стартовий рядок
Заголовків запиту/відповіді;
Тіла запиту/відповіді.
Спочатку йде стартова строка, список заголовків, потім порожній рядок, після нього (якщо є) тіло запиту/відповіді.
І клієнт, і сервер можуть надсилати один одному заголовки і тіло відповіді. У клієнта стартова строка та доступні заголовки будуть одні, у сервера - інші. Розглянемо, який вигляд має робота за протоколом HTTP, коли користувач хоче завантажити головну сторінку соціальної мережі "Facebook".
Браузер користувача встановлює з'єднання із сервером facebook.com і надсилає такий запит:
GET / HTTP/2
Host: www.facebook.com
Сервер приймає запит і надсилає відповідь:
HTTP/2 200
vary: Accept-Encoding
content-encoding: zstd
set-cookie: ...
x-fb-rlafr: 0
content-security-policy-report-only: ...
content-security-policy: ...
document-policy: force-load-at-top
cross-origin-opener-policy: same-origin-allow-popups
pragma: no-cache
cache-control: private, no-cache, no-store, must-revalidate
expires: Sat, 01 Jan 2000 00:00:00 GMT
x-content-type-options: nosniff
x-xss-protection: 0
x-frame-options: DENY
origin-agent-cluster: ?0
strict-transport-security: max-age=15552000; preload
content-type: text/html; charset="utf-8"
x-fb-debug: ...
date: Sat, 21 Jan 2023 16:45:23 GMT
priority: u=3,i
alt-svc: h3=":443"; ma=86400
<!DOCTYPE html>
<html lang="ru" id="facebook" class="no_js">
<head><meta charset="utf-8" /><meta name="referrer" content="origin-when-crossorigin" id="meta_referrer" />
<!-- решта контенту сторінки нижче -->
Браузер приймає відповідь і показує готову сторінку.
Нам цікавий найперший крок, де браузер ініціює запит до сервера facebook.com.
Це стартовий рядок запиту. Тут визначається кілька важливих параметрів:
Метод, яким буде запитано контент;
Адреса сторінки;
Версія протоколу.
GET - це метод (дієслово), який застосовується для доступу до вказаної сторінки. GET використовується дуже часто, тому що говорить серверу про те, що клієнт хоче отримати вказаний документ. Є й інші методи, один із них ми розглянемо вже в наступному розділі.
Після методу йде вказівка на адресу сторінки - URI (універсальний ідентифікатор ресурсу). Ми запитуємо головну сторінку сайту, тому використовується просто слеш - /. Наприкінці рядка вказана версія протоколу, майже завжди це буде HTTP/2.
Після рядка із зазначенням основних параметрів йде перерахування заголовків. Вони передають серверу додаткову корисну інформацію: назву і версію браузера, мову, кодування, параметри кешування тощо.
Серед заголовків, які передаються під час кожного запиту, є один обов'язковий і найважливіший - це заголовок Host. Він визначає адресу домену, який запитує браузер клієнта.
Сервер, отримавши запит, шукає у себе сайт із доменом із заголовка Host, а також зазначену сторінку. Якщо запитаний сайт і сторінку знайдено, клієнту надсилається відповідь: HTTP/2 200. Така відповідь означає, що документ знайдений і буде надіслано клієнту.
👉 Загальна структура стартового рядка відповіді:
HTTP/Версія Код стану [Пояснення]
Найбільше тут цікавий саме код стану, він же код відповіді сервера. У цьому прикладі код відповіді - 200, що означає: сервер працює, документ знайдено, його буде передано клієнту.
Не завжди все йде гладко.
Наприклад, запитаний документ відсутній або сервер перевантажений. У такому разі клієнт не отримає контент, а код відповіді буде відмінним від 200.
404 - якщо сервер доступний, але запитаний документ не знайдено;
503 - якщо сервер не може обробляти запити з технічних причин.
Специфікація HTTP визначає 63 різних кодів HTTP.
Для версії 2 пояснення може бути відсутнім
Після стартового рядка йдуть заголовки, а потім тіло відповіді.
У PHP є всі можливості для взаємодії з HTTP:
Отримання тіла запиту;
Отримання заголовків запиту;
Додавання/зміна заголовків відповіді;
Управління тілом відповіді.
Розберемо все по порядку.
Тіло запиту - це інформація, яку передав браузер під час запиту сторінки. Але тіло запиту присутнє тільки, якщо браузер запросив сторінку методом POST. Річ у тім, що POST - це метод, спеціально призначений для надсилання даних на сайт. Найчастіше метод POST браузер задіює в момент відправлення форми. У цьому разі тілом запиту буде вміст форми.
У PHP-сценарії всі дані відправленої форми будуть доступні в спеціальному масиві $_POST. Детальніше про це написано в наступному розділі, присвяченому формам.
Нагадаємо ще раз, що заголовки запиту - це метаінформація, надіслана браузером під час запиту сценарію.
PHP автоматично витягує такі заголовки і поміщає їх у спеціальний масив - $_SERVER. Варто зазначити, що в цьому масиві, крім заголовків, є й інша інформація. Значення заголовків запиту знаходяться під ключами, які починаються з HTTP_. Детально весь вміст цього масиву описано в офіційній документації.
Приклад, як отримати попередню сторінку, з якої перейшов користувач:
print($_SERVER['HTTP_REFERER']);
У PHP-сценарії можна керувати всіма заголовками відповіді, які потраплять до користувача разом із контентом сторінки. Це можливо, тому що PHP працює на стороні веб-сервера і має з ним дуже тісну інтеграцію. Ось приклади сценаріїв, коли стане в пригоді управління заголовками відповіді:
Кешування;
Переадресація користувача;
Встановлення cookies;
Надсилання файлів;
Передача додаткової інформації браузеру.
Заголовки відповіді потрібні для виконання безлічі важливих завдань.
У PHP є функція для надсилання або зміни заголовків: header().
Вона приймає ім'я і значення заголовка і додає його до списку з усіх заголовків, які підуть у браузер користувача після закінчення роботи сценарію.
Наприклад, так виконується перенаправлення користувача на іншу сторінку:
header("Location: /index.php");
За переадресацію відповідає заголовок з ім'ям Location, а через двокрапку задається значення - адреса сторінки для переходу.
Є одне обмеження: заголовки не можна надсилати, якщо користувачеві до цього моменту вже надіслали будь-який контент. Тобто якщо показати щось на екрані, наприклад, через функцію print(), то після цього заголовки поміняти вже не вийде.
Усе, що PHP виводить на екран, є вмістом відповіді. Іншими словами, виклики функцій print, echo або показ тексту через шорт-теги є тілом відповіді, яке потрапляє до браузера користувачеві.
Ми звикли, що на нашому сайті кожен PHP-сценарій відповідає за одну сторінку. Відвідувач сайту вводить в адресний рядок шлях, який складається з імені домену та імені PHP-сценарію. Наприклад, так: http://weather-diary.ua/day.php.
Але як бути, якщо одна сторінка має показувати різну інформацію?
На сайті щоденника спостережень за погодою ми зробили окрему сторінку, щоб показувати на ній інформацію про погоду з історії за один конкретний день. Тобто сторінка одна, але показує різні дані, залежно від обраного дня.
Також користувачі хочуть додати в закладки адреси сторінок з потрібними їм днями. Виходить, що, маючи тільки один сценарій, зробити сторінку, здатну показувати щоденник погоди за будь-який день неможливо? Зовсім ні!
URI - це унікальний ідентифікатор ресурсу. Ресурс у нашому випадку - це повний шлях до сторінки сайту. І ось який вигляд може мати ресурс для показу погоди за конкретний день:
http://weather-diary.ua/day.php?date=2017-10-15
Розберемо, з чого складається цей URI.
Тут є ім'я домену: weather-diary.ua.
Потім іде ім'я сценарію: day.php.
А все, що йде після - це параметри запиту.
Параметри запиту - це нібито додаткові атрибути адреси сторінки. Вони відокремлюються від імені сторінки знаком запиту. У прикладі вище параметр запиту тільки один: date=2017-10-15.
Ім'я цього параметра: date, значення: 2017-10-15.
Параметрів запиту може бути кілька, тоді вони розділяються знаком амперсанду: ?date=2017-10-15&tscale=celsius.
У прикладі вище вказується два аргументи: дата і одиниця виміру температури.
Тепер в адресі сторінки використовуються параметри запиту, але яка нам від цього користь? Вона полягає в тому, що якщо ім'я сторінки викликає до виконання відповідний PHP-сценарій, то параметри запиту перетворюються на спеціальні зовнішні змінні в цьому сценарії. Тобто якщо в адресі присутні такі параметри, то їх легко отримати всередині коду сценарію і виконати з ними якісь дії. Наприклад, показати погоду за конкретний день в обраних одиницях виміру.
Якщо є зовнішні змінні, то як їх прочитати?
Усі параметри запиту перебувають у спеціальному, асоціативному масиві $_GET, а отже, сценарій, викликаний за такою адресою: day.php?date=2017-10-15&tscale=celsius, матиме в цьому масиві два значення з ключами date і scale.
Запит на отримання даних за обраний день має такий вигляд:
<?php
$date = $_GET['date'] ?? date('Y-m-d');
$sql = sprintf('SELECT * FROM weather WHERE day = "%s"', mysqli_real_escape_string($con, $date));
У першому рядку прикладу вище ми отримуємо значення параметра date, а якщо він відсутній, то використовуємо поточну дату як обраний день.
Ніколи не покладайтеся на існування параметра в масиві $_GET і робіть перевірку або функцією isset(), або як у цьому прикладі.
Іноді потрібно зробити зворотну операцію: сформувати адресу сторінки, включивши туди потрібні параметри запиту з масиву.
Скажімо, на сторінці погодного щоденника треба поставити посилання на наступний і попередній день. Потрібно також зберегти обрану одиницю вимірів. Тобто необхідно зберегти поточні параметри запиту, змінити значення одного з них (день), і сформувати нове посилання.
Ось як це можна зробити:
<?php
$params = $_GET;
$date = $params['date'] ?? date('Y-m-d');
$tomorrow = date('Y-m-d', strtotime('tomorrow', strtotime($date)));
$params['date'] = $tomorrow;
$url = basename(__FILE__) . '/?' . http_build_query($params);
print($url);
Тут ми використовували дві функції:
basename(__FILE__) - отримує ім'я поточного сценарію;
http_build_query() - перетворює асоціативний масив у рядок запиту.