10. Сервери в PHP
Оскільки програми PHP можуть виконуватися сервером WEB, така програма стає серверною програмою, здатною обслуговувати декількох клієнтів. З точки зору клієнта, виклик сервісу WEB еквівалентний запиту до URL цього сервісу. Клієнт може бути написаний на будь-якій мові, зокрема на PHP. В останньому випадку використовуються мережеві функції, які ми щойно розглянули. Крім того, нам потрібно вміти «спілкуватися» зі службою WEB, тобто розуміти протокол http, що забезпечує зв’язок між веб-сервером та його клієнтами. Саме цьому присвячені наступні програми.
Веб-клієнт, описаний у розділі 9.2, дав нам змогу ознайомитися з частиною протоколу HTTP.

У найпростішому варіанті обмін даними між клієнтом і сервером відбувається так:
- клієнт встановлює з’єднання з портом 80 веб-сервера
- він надсилає запит щодо документа
- веб-сервер надсилає запитуваний документ і закриває з’єднання
- у свою чергу клієнт закриває з’єднання
Клієнт може мати різний характер: текст у форматі HTML, зображення, відео тощо. Це може бути існуючий документ (статичний документ) або документ, що генерується на льоту за допомогою скрипта (динамічний документ). В останньому випадку йдеться про веб-програмування. Скрипт для динамічного генерування документів може бути написаний на різних мовах: PHP, Python, Perl, Java, Ruby, C#, VB.net тощо.
Тут ми використовуємо PHP для динамічного формування текстових документів.
![]() |
- у [1] клієнт встановлює з’єднання з сервером, запитує скрипт PHP, надсилає або не надсилає параметри для цього скрипта
- У [2] веб-сервер запускає виконання скрипта PHP за допомогою інтерпретатора PHP. Цей скрипт генерує документ, який надсилається клієнту [3]
- сервер закриває з'єднання. Клієнт робить те саме.
Веб-сервер може обслуговувати декількох клієнтів одночасно. У складі програмного пакету WampServer веб-сервером є Apache — сервер з відкритим кодом, розроблений Apache Foundation (http://www.apache.org/). У наведених нижче додатках необхідно запустити WampServer. Це активує три програми: веб-сервер Apache, SGBD та MySQL, а також інтерпретатор PHP.
Скрипти, що виконуються веб-сервером, будуть написані за допомогою інструменту NetBeans. Наразі ми написали скрипти PHP, що виконуються в консольному режимі:
![]() |
Користувач використовує консоль, щоб запустити скрипт PHP та отримати його результати.
У наступних клієнт-серверних додатках
- скрипт клієнта виконується в консольному середовищі
- а серверний скрипт — у веб-контексті
![]() |
Скрипт PHP сервера не може знаходитися в довільному місці файлової системи. Адже веб-сервер шукає статичні та динамічні документи, які йому запитують, у місцях, визначених конфігурацією. За замовчуванням у налаштуваннях WampServer передбачено, що документи шукаються у папці <WampServer>/www, де <WampServer> — це папка встановлення WampServer. Отже, якщо веб-клієнт запитує документ D за допомогою URL [http://localhost/D], веб-сервер надасть йому документ D зі шляху [<WampServer>/www/D].
У наведених нижче прикладах ми розмістимо серверні скрипти в папці [www/exemples-web]. Якщо серверний скрипт має назву S.php, його буде запитано у веб-сервера за URL та [http://localhost/exemples-web/S.php]. У результаті йому буде надано документ [<WampServer>/www/exemples-web/S.php].
![]() |
Щоб створити серверний скрипт для за допомогою NetBeans, ми будемо діяти наступним чином:
![]() |
- у [1] ми створюємо новий проєкт
- у [2] вибираємо категорію [PHP] та проект [PHP Application]
![]() |
- у [3] ми даємо назву проекту
- у [4] ми обираємо папку для проекту
- у [5] ми вказуємо, що скрипт має виконуватися локальним веб-сервером (адреса скрипта матиме вигляд http://localhost/...). Локальним веб-сервером буде веб-сервер Apache з WampServer.
- у [6] ми вказуємо URL проекту. Тут ми вирішуємо, що скрипт S.php цього проєкту буде викликатися разом із URL та [http://localhost/exemples-web/S.php]. Згідно з вищесказаним, це означає, що шлях до скрипта S.php у файловій системі буде [<WampServer>/www/exemples-web/S.php]. Саме це вказано в [7]. Тут ми вимагаємо, щоб усі скрипти S.php з цього проєкту були скопійовані в дерево каталогів веб-сервера Apache.
- у [8] — новий проект.
Напишемо тестовий скрипт:
![]() |
- у [1] ми створюємо в проєкті [exemples-web] перший скрипт PHP
- у [2] ми даємо йому ім'я
- в [3], після створення надаємо йому такий вміст
Далі потрібно запустити WampServer.
![]() |
- у [4] ми виконуємо веб-скрипт [exemple1.php]. NetBeans запустить браузер, встановлений за замовчуванням на комп’ютері, і попросить його відобразити URL, [http://localhost/exemples-web/exemple1.php], [5]
- у форматі [6], браузер відображає те, що серверний скрипт надіслав клієнту.
Далі ми зустрінемо два типи веб-клієнтів:
- браузер, як описано вище. Ми зазначили, що веб-сервер надсилає відповідь у такому форматі: заголовки HTTP, порожній рядок, текст. Браузер відображає лише texte.
- скрипт PHP, який відобразить йому повну відповідь: заголовки HTTP, порожній рядок, текст.
Далі
- серверні скрипти будуть написані так, як [exemple1.php] вище
- скрипти клієнта будуть записані так само, як консольні скрипти, які ми писали до цього моменту.
10.1. Клієнтсько-серверний додаток для дати та часу
10.1.1. Сервер (web_01)
<?php
// час: кількість мілісекунд з 01.01.1970
// формат відображення дати та часу
// d: день у форматі двох цифр
// m: місяць (2 цифри)
// y: рік у 2 цифри
// H: година 0,23
// i: хвилини
// s: секунди
print date("d/m/y H:i:s",time());
По суті, наведений вище скрипт PHP виводить поточний час на екран. Однак під час виконання на веб-сервері потік № 1, який зазвичай пов'язаний з екраном, перенаправляється на з'єднання між сервером і клієнтом. Отже, у веб-контексті наведений вище скрипт надсилає поточну годину у вигляді тексту клієнту.
Запустимо цей скрипт у NetBeans:
![]() |
- у [1] запускаємо скрипт. Після цього запускається веб-браузер.
- у [2] — файл URL, запитуваний веб-браузером
- в [3] — текст, надісланий серверним скриптом
Клієнтський браузер використовує протокол HTTP для взаємодії з веб-сервером. Ми вже описували структуру цього протоколу.
Клієнт надсилає рядки тексту, які можна розкласти на три частини: заголовки HTTP, порожній рядок, документ. Документ, що надсилається на веб-сервер, найчастіше є порожнім або являє собою набір параметрів parami=vali, де vali — це значення, введене користувачем у HTML-форму.
Відповідь сервера має такий самий формат: заголовки HTTP, порожній рядок, документ, де document цього разу є документом, який запитує клієнтський браузер. Якщо клієнт передав параметри, наданий документ, як правило, залежить від цих параметрів.
У браузері Firefox можна переглянути фактичний обмін даними між клієнтом і веб-сервером. Існує плагін для Firefox під назвою Firebug, який дозволяє відстежувати цей обмін. Firebug доступний на URL [https://addons.mozilla.org/fr/firefox/addon/firebug/]. Якщо для перегляду цієї URL-адреси використовується браузер Firefox, можна завантажити цей плагін Firebug. Далі ми припускаємо, що плагін Firebug було завантажено та встановлено. Він доступний через пункт меню Firefox:
![]() |
У вікні браузера Firefox відкривається вікно Firebug. Це вікно також має власне меню:
![]() |
Щоб переглянути обмін даними між клієнтом і сервером під час запиту HTTP, ми надсилаємо запит URL [http://localhost/exemples-web/web_01.php] за допомогою браузера Firefox. Після цього вікно Firebug заповнюється інформацією:
![]() |
Вище наведено підсумок обміну даними між клієнтом і сервером:
- [1]: клієнт надіслав запит HTTP: GET /exemples-web/web_01.php HTTP/1.1, щоб отримати документ [web01.php]
- [2]: сервер надіслав відповідь: HTTP/1.1 200 OK, що вказує на те, що він знайшов запитуваний документ.
Firebug дозволяє переглянути повний обмін даними. Достатньо «розгорнути» URL:
![]() |
Вище ми бачимо заголовки HTTP, якими обмінюються клієнт (запит) і сервер (відповідь). Можна отримати вихідний код обміну, c.a.d, а також рядки тексту, що фактично обмінюються, [1]. Тоді ми отримуємо такий вихідний код:
![]() |
Щоб написати клієнтський скрипт для веб-сервера, нам достатньо відтворити поведінку браузера. Після встановлення з’єднання з сервером клієнтський скрипт міг би надіслати 8 рядків із наведеного вище запиту. Насправді не все є обов’язковим, і ми надішлемо лише такі три рядки:
- рядок 1: вказує запитуваний документ та використовуваний протокол HTTP
- рядок 2: вказує ім’я комп’ютера, на якому працює клієнтський скрипт
- рядок 3: вказує, що після обміну даними клієнт закриє з’єднання з сервером
Тепер розглянемо відповідь сервера. Ми знаємо, що її згенерував скрипт PHP [web_01.php]. Вище ми бачимо заголовки HTTP у відповіді. Код скрипта [web01.php] показує, що саме він їх не генерував. Згадаймо конфігурацію серверного скрипта:
![]() |
Саме веб-сервер згенерував заголовки HTTP у відповіді. Серверний скрипт може генерувати їх самостійно. Приклад цього ми розглянемо трохи далі.
Ми вже зазначали, що відповідь веб-сервера має такий вигляд: заголовки HTTP, порожній рядок, документ. Якщо документ є текстовим, його можна переглянути у вкладці [Réponse] у Firebug:
![]() |
Ця відповідь була згенерована скриптом [web_01.php].
10.1.2. Клієнт (client1_web_01)
Тепер ми напишемо клієнтський скрипт для попереднього сервісу. Ми знаємо, що клієнт повинен:
- встановити з’єднання з веб-сервером
- надіслати текст: заголовки HTTP, порожній рядок
- прочитати повну відповідь сервера, доки той не закриє з’єднання з клієнтом
- закрити з’єднання з сервером
Клієнтський скрипт виконується в консольному середовищі NetBeans:
![]() |
- у [1] клієнтський скрипт [client1_web_01.php] включено до проекту NetBeans [exemples]
- у [2] властивості проекту NetBeans [exemples]
- у [3] проект NetBeans [exemples] виконується в режимі «командного рядка», який ми також називали режимом «консолі».
Код клієнтського скрипта такий:
<?php
// дані
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/web_01.php";
// відкриття з'єднання на порту 80 сервера $HOTE
$connexion = fsockopen($HOTE, $PORT);
// помилка?
if (!$connexion) {
print "Erreur : $erreur\n";
exit;
}
// заголовки (headers) протоколу HTTP повинні закінчуватися порожнім рядком
// GET
fputs($connexion, "GET $urlServeur HTTP/1.1\n");
// Хост
fputs($connexion, "Host: localhost\n");
// З'єднання
fputs($connexion,"Connection: close\n");
// порожній рядок
fputs($connexion,"\n");
// сервер тепер відповідатиме на каналі $connexion. Він надішле всі
// свої дані, а потім закриє канал. Отже, клієнт зчитує все, що надходить з $connexion
// до закриття каналу
while ($ligne = fgets($connexion, 1000)) {
print "$ligne";
}//, поки
// клієнт, у свою чергу, закриває з’єднання
fclose($connexion);
// кінець
exit;
Коментарі
- рядок 8: встановлення з’єднання з сервером
- рядок 16: команда HTTP GET
- рядок 18: команда HTTP Host
- рядок 20: команда HTTP З'єднання
- рядок 22: порожній рядок
- рядки 26–28: читання всіх рядків тексту, надісланих сервером, доки він не закриє з’єднання.
- рядок 30: клієнт, у свою чергу, закриває з’єднання
Результати
Виконання клієнтського скрипта дає такі результати:
Коментарі
- рядки 1–7: відповідь HTTP від веб-сервера.
- рядок 8: порожній рядок, що позначає кінець заголовків HTTP
- рядки 9 і далі: документ. У даному випадку це простий текст, що відображає поточну дату та час. Це текст, який скрипт PHP виводить на вихід № 1.
- рядок 1: сервер відповідає, що знайшов запитуваний документ.
- рядок 2: поточна дата та час на сервері
- рядок 3: ідентифікатор веб-сервера
- рядок 4: вказує, що наступний документ — це документ, згенерований скриптом PHP
- рядок 5: кількість символів у документі
- рядок 6: сервер повідомляє, що після надсилання документа він закриє з’єднання
- рядок 7: вказує, що документ, надісланий сервером, є текстом у форматі HTML. У цьому випадку це не відповідає дійсності. Документ — це текст без особливого форматування. Якщо документ не є текстом у форматі HTML, про це має повідомити скрипт PHP. У цьому випадку ми цього не зробили.
10.1.3. Другий клієнт (client2_web_01)
Попередній клієнт відображав усе, що йому надсилав веб-сервер. На практиці зазвичай ігнорують заголовки HTTP у відповіді та використовують лише частину документа. Тут ми намагаємося отримати дату та час, надіслані сервером за допомогою скрипта PHP. Ми отримаємо цю інформацію за допомогою регулярного виразу.
<?php
// отримати інформацію, надіслану веб-сервером
// дані
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/web_01.php";
// відкриття з'єднання на порту 80 $HOTE
$connexion = fsockopen($HOTE, $PORT);
// помилка?
if (!$connexion) {
print "Erreur : $erreur\n";
exit;
}
// заголовки (headers) протоколу HTTP повинні закінчуватися порожнім рядком
// GET
fputs($connexion, "GET $urlServeur HTTP/1.1\n");
// Хост
fputs($connexion, "Host: localhost\n");
// З'єднання
fputs($connexion,"Connection: close\n");
// порожній рядок
fputs($connexion,"\n");
// сервер тепер відповідатиме на каналі $connexion. Він надішле всі
// ці дані, а потім закриє канал. Отже, клієнт зчитує все, що надходить з $connexion
// доти, доки не знайде потрібний рядок у форматі дд/мм/рр год:хв:сек
while ($ligne = fgets($connexion, 1000)) {
print "$ligne";
if (preg_match("/(\d\d)\/(\d\d)\/(\d\d) (\d\d):(\d\d):(\d\d)/", $ligne, $champs)) {
// звідки витягуються # полів
array_shift($champs); // видаляє перший елемент масиву полів
// витягуємо 6 полів у 6 змінних
list($j, $m, $a, $h, $i, $s) = $champs;
// виведення результату
print "\ndateheure=[$j,$m,$a,$h,$i,$s]\n";
}//if
}//while
// у свою чергу клієнт закриває з'єднання
fclose($connexion);
// кінець
exit;
Результати
10.2. Отримання сервером параметрів, надісланих клієнтом
У протоколі HTTP клієнт має два способи передачі параметрів веб-серверу:
- він запитує URL служби у формі
GET url?param1=val1¶m2=val2¶m3=val3… HTTP/1.0
де значення vali спочатку мають бути закодовані, щоб певні зарезервовані символи були замінені на їхні шістнадцяткові значення.
- він запитує URL від сервісу у вигляді
POST url HTTP/1.0
потім серед заголовків HTTP, надісланих на сервер, розміщує такий заголовок:
Наступні заголовки, надіслані клієнтом, закінчуються порожнім рядком. Після цього він може надіслати свої дані у вигляді
, де значення vali, як і для методу GET, повинні бути попередньо закодовані. Кількість символів, що надсилаються на сервер, повинна дорівнювати N, де N — це значення, заявлене в заголовку
Скрипт PHP, який отримує попередні параметри parami, надіслані клієнтом, отримує їхні значення з масиву:
- $_GET["parami"] для команди GET
- $_POST["parami"] для команди POST
10.2.1. Клієнт GET (client1_web_02)
Наведений нижче скрипт PHP надсилає три параметри [nom, prenom, age] на сервер.
<?php
// клієнт: надсилає ім'я, прізвище та вік на сервер за допомогою методу GET
// дані
$HOTE = "localhost";
$PORT = 80;
$URL = "/exemples-web/web_02.php";
list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
// підключення до веб-сервера
$connexion = fsockopen($HOTE, $PORT);
// повернення у разі помилки
if (!$connexion) {
print "Echec de la connexion au site ($HOTE,$PORT) : $erreur";
exit;
}//if
// відправлення інформації на сервер PHP
// інформація кодується
$infos = "prenom=" . urlencode(utf8_decode($prenom)) . "&nom=" . urlencode(utf8_decode($nom)) . "&age=" . urlencode("$age");
// відстеження консолі
print "infos envoyées au serveur (GET)=$infos\n";
print "URL demandée=[$URL?$infos]\n\n";
// заголовки (headers) протоколу HTTP повинні закінчуватися порожнім рядком
// GET
fputs($connexion, "GET $URL?$infos HTTP/1.1\n");
// Хост
fputs($connexion, "Host: localhost\n");
// З'єднання
fputs($connexion,"Connection: close\n");
// порожній рядок
fputs($connexion,"\n");
// сервер тепер відповідатиме на каналі $connexion. Він надсилатиме всі
// його дані, а потім закрити канал. Клієнт зчитує всі дані, що надходять від $connexion, аж до закриття каналу
while ($ligne = fgets($connexion, 1000))
print "$ligne";
// клієнт, у свою чергу, закриває з’єднання
fclose($connexion);
Коментарі
- рядок 7: URL-адреса серверного скрипта
- рядок 8: значення 3 параметрів
- рядок 10: встановлення з’єднання з веб-сервером
- рядок 18: кодування 3 параметрів. Ми працюємо зі скриптом, написаним у NetBeans, з кодуванням символів у форматі UTF-8. Отже, 3 значення параметрів з рядка 8 кодуються у форматі UTF-8. Функція utf8_decode перетворює їхнє кодування у ISO-8859-1. Після цього їх можна кодувати у форматі URL. Усі неалфавітні символи замінюються на %xx, де xx — шістнадцяткове значення символу. Пробіли замінюються на знак +.
- рядок 24: запитуваний URL має вигляд $URL?$infos, де $infos має вигляд nom=val1&prenom=val2&age=val3.
10.2.2. Сервер (web_02)
Сервер просто відображає те, що отримує.
<?php
// обробка помилок
ini_set("display_errors", "off");
// отримання сервером інформації, надісланої клієнтом
// тут ім'я=P&прізвище=N&вік=A
// ця інформація автоматично стає доступною у змінних
// $_GET['prenom'], $_GET['nom'], $_GET['age']
// вони передаються клієнту
// заголовок UTF-8
header("Content-Type: text/plain; charset=utf-8");
// параметри, надіслані на сервер
$prenom = isset($_GET['prenom']) ? $_GET['prenom'] : "";
$nom = isset($_GET['nom']) ? $_GET['nom'] : "";
$age = isset($_GET['age']) ? $_GET['age'] : "";
// відповідь клієнту
$réponse = "informations reçues du client [" .
utf8_encode(htmlspecialchars($prenom, ENT_QUOTES)) .
"," . utf8_encode(htmlspecialchars($nom, ENT_QUOTES)) .
"," . utf8_encode(htmlspecialchars($age, ENT_QUOTES)) . "]\n";
print $réponse;
Коментарі
- рядок 13: встановлює заголовок HTTP «Content-Type». За замовчуванням веб-сервер надсилає заголовок
, який вказує, що відповідь — це текст у форматі HTML. У цьому випадку відповідь буде текстом без форматування з символами, закодованими у форматі UTF-8:
Заголовки HTTP повинні надсилатися перед відповіддю сервера. Тому, як показано вище, виклик функції header має знаходитися перед будь-якою інструкцією print.
- рядки 16–18: 3 параметри зчитуються з масиву $_GET.
- рядок 21: формується рядок символів, який буде надіслано клієнту у відповідь. Деякі символи мають особливе значення в HTML і для відображення їх потрібно замінити на ентітети HTML. htmlspecialchars($string) замінює всі ці символи на їхні еквіваленти у рядку $string. Наприклад, символ $ перетворюється на &. Далі, оскільки в рядку 13 ми вказали, що відповіддю буде текст UTF-8, ми кодуємо отримані значення у форматі UTF-8.
- рядок 25: відповідь надсилається клієнту
Тест 1
Запустимо скрипт [web_02] із NetBeans. Після цього запуститься браузер для відображення URL [http://localhost/exemples-web/web_02.php]:
![]() |
- у [1] браузер відображає URL [http://localhost/exemples-web/web_02.php]. Оскільки ми не додали до цього URL-адреси параметрів, сервер відповів з порожніми параметрами. Нагадаємо, що відповідь сервера — це та, що записана за допомогою команди print.
- У [2] ми додаємо параметри до URL. Цього разу серверний скрипт успішно їх повертає.
Зазначимо, що для виконання серверного скрипта NetBeans не потрібний. Достатньо ввести URL серверного скрипта в браузері, щоб цей скрипт було виконано.
Тест 2
У NetBeans запускаємо клієнт [client1_web_02.php]. Отримуємо таку відповідь:
- рядок 1: кодування 3 параметрів. Бачимо, що символ û перетворився на %FB.
- рядок 12: відповідь сервера
10.2.3. Клієнт POST (client2_web_03)
HTTP-клієнт надсилає веб-серверу таку послідовність тексту: заголовки HTTP, порожній рядок, документ. У попередньому клієнті ця послідовність була такою:
Документа не було. Існує ще один спосіб передачі параметрів — метод, відомий як POST. У цьому випадку послідовність тексту, що надсилається на веб-сервер, є такою:
Цього разу параметри, які для клієнта GET були включені в заголовки HTTP, у клієнта POST є частиною документа, що надсилається разом із заголовками.
Скрипт клієнта POST має такий вигляд:
<?php
// клієнт: надсилає ім'я, прізвище та вік на сервер методом POST
// дані
$HOTE = "localhost";
$PORT = 80;
$URL = "/exemples-web/web_03.php";
list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
// підключення до веб-сервера
$connexion = fsockopen($HOTE, $PORT);
// повернення у разі помилки
if (!$connexion) {
print "Echec de la connexion au site ($HOTE,$PORT) : $erreur";
exit;
}//if
// відправлення інформації на сервер PHP
// інформація кодується
$infos = "prenom=" . urlencode(utf8_decode($prenom)) . "&nom=" . urlencode(utf8_decode($nom)) . "&age=" . urlencode("$age");
print "client : infos envoyées au serveur (POST) : $infos\n";
// здійснюється підключення до URL $URL шляхом відправлення (POST) параметрів
// заголовки (headers) протоколу HTTP повинні закінчуватися порожнім рядком
// POST
fputs($connexion, "POST $URL HTTP/1.1\n");
// Host
fputs($connexion, "Host: localhost\n");
// Connection
fputs($connexion,"Connection: close\n");
// Content-type
fputs($connexion, "Content-type: application/x-www-form-urlencoded\n");
// Content-length
// надсилається розмір (кількість символів) інформації, що буде надіслана
fputs($connexion, "Content-length: " . strlen($infos) . "\n");
// надсилається порожній рядок
fputs($connexion, "\n");
// надсилається інформація
fputs($connexion, $infos);
// сервер тепер відповість на каналі $connexion. Він надішле всі
// свої дані, а потім закриє канал. Клієнт зчитує все, що надходить з $connexion
// до закриття каналу
while ($ligne = fgets($connexion, 1000))
print "$ligne";
// клієнт, у свою чергу, закриває з’єднання
fclose($connexion);
Коментарі
- рядок 7: URL веб-сервісу, до якого підключиться клієнт POST. Цей веб-сервіс буде описано найближчим часом.
- рядок 8: параметри, які потрібно передати веб-сервісу
- рядок 10: підключення до веб-сервера
- рядок 18: кодування параметрів, які потрібно надіслати веб-сервісу
- рядок 23: команда Http POST
- рядок 25: команда Http Host
- рядок 27: команда Http Connection
- рядок 29: команда Http Content-type. Ми вже зустрічали цей заголовок HTTP. Він присутній щоразу, коли надсилається документ. Веб-сервер, який надсилає HTML-документ, використовує заголовок HTTP
Якщо він надсилає неформатований текст, він використовує заголовок HTTP
Наш клієнт POST надсилає документ, який є текстом у форматі param1=val1¶m2=val2&.... Цей тип документа має тип application/x-www-form-urlencoded. Ми не будемо пояснювати, чому, оскільки це змусило б нас пояснювати, що таке веб-форма.
- рядок 32: команда Content-length. Ми вже зустрічали цей заголовок HTTP. Він присутній щоразу, коли надсилається документ. Він вказує кількість байтів у документі.
- рядок 34: порожній рядок, що позначає кінець заголовків HTTP
- рядок 36: надсилання параметрів
- рядки 40–41: зчитування повної відповіді сервера
- рядок 43: закриття з’єднання
10.2.4. Сервер (web_03)
Веб-сервіс [web_03] виконує ті самі дії, що й веб-сервіс [web_02]. Він зчитує параметри, надіслані клієнтом POST, і відправляє їх назад клієнту. Його код такий:
<?php
// обробка помилок
ini_set("display_errors", "off");
// заголовок UTF-8
header("Content-Type: text/plain; charset=utf-8");
// отримання сервером інформації, надісланої клієнтом
// тут ім'я=P&прізвище=N&вік=A
// ця інформація автоматично стає доступною у змінних
// $_POST['prenom'], $_POST['nom'], $_POST['age']
// вони повертаються клієнту
// параметри, надіслані на сервер
$prenom = isset($_POST['prenom']) ? $_POST['prenom'] : "";
$nom = isset($_POST['nom']) ? $_POST['nom'] : "";
$age = isset($_POST['age']) ? $_POST['age'] : "";
// відповідь клієнту
$réponse = "informations reçues du client [" .
utf8_encode(htmlspecialchars($prenom, ENT_QUOTES)) .
"," . utf8_encode(htmlspecialchars($nom, ENT_QUOTES)) .
"," . utf8_encode(htmlspecialchars($age, ENT_QUOTES)) . "]\n";
print $réponse;
Коментарі
- рядки 14–16: параметри, надіслані клієнтом POST, стають доступними в масиві $_POST для веб-сервісу, який їх отримує.
- рядок 6: заголовок HTTP Content-Type. Може викликати подив те, що в заголовках HTTP відсутні заголовки HTTP та Content-Length, що вказують розмір документа, який повертається клієнту. Ми бачили, що веб-сервер за замовчуванням надсилає заголовки HTTP. Заголовок Content-Length є одним із них.
Результати
Після написання серверного скрипта в NetBeans він одразу стає доступним через сервер Apache під назвою WampServer. Нагадаємо, що це досягається за допомогою налаштувань (див. параграф 10). Запускаємо клієнт, який надсилає запит до сервера, і отримуємо таку відповідь:
- рядки 2–10: відповідь сервера
- рядки 2–8: заголовки HTTP
- рядок 10: документ
- рядок 6: заголовок HTTP Content-Length. Оскільки цей заголовок не був згенерований серверним скриптом, отже, його згенерував веб-сервер.
- рядок 8: єдиний заголовок, згенерований серверним скриптом
10.3. Отримання змінних середовища сервера WEB
Серверний скрипт виконується у веб-середовищі, яке йому відоме. Це середовище зберігається у словнику $_SERVER. Спочатку ми пишемо серверну програму, яка надсилає своїм клієнтам вміст цього словника.
10.3.1. Сервер (web_04)
<?php
// обробка помилок
ini_set("display_errors", "off");
// заголовок UTF-8
header("Content-Type: text/plain; charset=utf-8");
// клієнту надсилається список змінних, доступних у середовищі сервера
foreach ($_SERVER as $clé => $valeur) {
print "[$clé,$valeur]\n";
}
- пари (ключ, значення) зі словника $_SERVER надсилаються клієнтам.
Результат, який отримуємо, коли клієнтом є веб-браузер, такий:

Ось значення деяких змінних (для Windows. У Linux вони будуть іншими):
CMDE представляє заголовок HTTP, надісланий клієнтом. Ми маємо доступ до всіх цих заголовків. | |
шлях до виконуваних файлів на комп’ютері, на якому виконується серверний скрипт | |
шлях до командного інтерпретатора DOS | |
розширення виконуваних файлів | |
папка інсталяції Windows | |
підпис веб-сервера. Тут нічого немає. | |
тип веб-сервера | |
Інтернет-ім'я комп'ютера веб-сервера | |
порт, на якому працює веб-сервер | |
адреса IP комп'ютера, на якому розташований веб-сервер | |
адреса IP клієнта. У цьому випадку клієнт знаходився на тому самому комп'ютері, що й сервер. | |
порт зв’язку клієнта | |
корінь дерева документів, що обслуговуються веб-сервером | |
електронна адреса адміністратора веб-сервера | |
повний шлях до скрипту сервера | |
версія протоколу HTTP, що використовується веб-сервером | |
Http-команда, яку використовує клієнт. Їх є чотири: GET, POST, PUT, DELETE | |
параметри, надіслані разом із запитом GET /url?параметри | |
URL, запитуваний клієнтом. Якщо браузер запитує URL http://machine[:port]/uri, отримаємо REQUEST_URI=uri | |
$_SERVER['SCRIPT_FILENAME']=$_SERVER['DOCUMENT_ROOT'].$_SERVER['SCRIPT_NAME'] |
10.3.2. Клієнт (client1_web_04)
Клієнт просто відображає все, що йому надсилає сервер.
<?php
// дані
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/web_04.php";
// відкриття з'єднання на порту 80 $HOTE
$connexion = fsockopen($HOTE, $PORT);
// помилка?
if (!$connexion) {
print "Erreur : $erreur\n";
exit;
}
// встановлюється з’єднання з веб-сервером URL
// заголовки (headers) протоколу HTTP повинні закінчуватися порожнім рядком
// GET
fputs($connexion, "GET $urlServeur HTTP/1.1\n");
// Хост
fputs($connexion, "Host: localhost\n");
// З'єднання
fputs($connexion,"Connection: close\n");
// порожній рядок
fputs($connexion,"\n");
// сервер тепер відповідатиме на каналі $connexion. Він надішле всі
// свої дані, а потім закриє канал. Отже, клієнт зчитує все, що надходить з $connexion
// до закриття каналу
while ($ligne = fgets($connexion, 1000)) {
print "$ligne";
}//, поки
// клієнт, у свою чергу, закриває з’єднання
fclose($connexion);
// кінець
exit;
Результати
10.4. Управління сесіями WEB
У попередніх прикладах «клієнт/сервер» ми мали таку схему роботи:
- клієнт відкриває з’єднання з портом 80 сервера веб-служби
- він надсилає текстову послідовність: заголовки HTTP, порожній рядок, [document]
- у відповідь сервер надсилає послідовність того ж типу
- сервер закриває з’єднання з клієнтом
- клієнт закриває з'єднання з сервером
Якщо той самий клієнт незабаром надсилає новий запит до веб-сервера, між клієнтом і сервером створюється нове з’єднання. Сервер не може знати, чи клієнт, який підключається, вже відвідував його раніше, чи це перший запит. Між двома з’єднаннями сервер «забуває» свого клієнта. З цієї причини кажуть, що протокол HTTP є безстаномним протоколом. Проте серверу корисно запам’ятовувати своїх клієнтів. Так, якщо додаток є захищеним, клієнт надсилає серверу логін та пароль для автентифікації. Якщо сервер «забуває» свого клієнта між двома з’єднаннями, клієнт муситиме автентифікуватися під час кожного нового з’єднання, що є неприйнятним.
Щоб відстежувати клієнта, сервер діє наступним чином: під час першого запиту від клієнта він включає у свою відповідь ідентифікатор, який клієнт потім повинен надсилати йому з кожним новим запитом. Завдяки цьому ідентифікатору, який є унікальним для кожного клієнта, сервер може розпізнати клієнта. Тоді він може вести облік даних для цього клієнта у вигляді файлу, однозначно пов’язаного з ідентифікатором клієнта.
Технічно це відбувається так:
- у відповіді новому клієнту сервер включає заголовок HTTP Set-Cookie: MotClé=Ідентифікатор. Він робить це лише під час першого запиту.
- У наступних запитах клієнт буде надсилати свій ідентифікатор у заголовку HTTP Cookie: MotClé=Ідентифікатор, щоб сервер міг його розпізнати.
Можна запитати, як сервер дізнається, що має справу з новим клієнтом, а не з тим, хто вже відвідував сайт раніше. Про це йому свідчить наявність заголовка HTTP Cookie у заголовках HTTP клієнта. У нового клієнта цей заголовок відсутній.
Сукупність з’єднань певного клієнта називається сесією.
10.4.1. Файл конфігурації
Щоб управління сесіями працювало коректно з PHP, потрібно переконатися, що він налаштований правильно. У Windows його файл конфігурації — PHP.ini. Залежно від середовища виконання (консоль, веб), файл конфігурації [PHP.ini] слід шукати в різних папках. Щоб їх визначити, використовується такий скрипт:
У рядку 4 функція phpinfo надає інформацію про інтерпретатор PHP, який виконує скрипт. Зокрема, вона вказує шлях до використовуваного файлу конфігурації [PHP.ini].
У консольному середовищі отримуємо результат, подібний до такого:
рядок 2: головний файл конфігурації — c:\windows\PHP.ini
рядок 3: додатковий файл конфігурації — C:\serveursSGBD\wamp21\bin\PHP\php5.3.5\PHP.ini. Він дозволяє змінювати певні параметри конфігурації головного конфігураційного файлу.
У веб-середовищі отримуємо такий результат:

Додатковий файл конфігурації тут відрізняється від того, що використовується в консольному середовищі. Саме його ми й розглянемо. У цьому файлі міститься розділ session:
- рядок 1: дані сеансу клієнта зберігаються у файлі
- рядок 3: папка для збереження даних сесії. Якщо ця папка не існує, про це не повідомляється, і управління сесіями не працює.
- рядки 4–5: вказують, що ідентифікатор сеансу керується заголовками HTTP Set-Cookie та Cookie
- рядок 6: заголовок Set-Cookie матиме вигляд Set-Cookie: PHPSESSID=identifiant_de_session
- рядок 7: сесія клієнта не запускається автоматично. Серверний скрипт повинен явно запросити її за допомогою команди session_start().
10.4.2. Сервер 1 (web_05)
Управління ідентифікатором сеансу є прозорим для веб-сервісу. Цей ідентифікатор управляється веб-сервером. Веб-сервіс отримує доступ до сеансу клієнта за допомогою команди session_start(). З цього моменту веб-сервіс може читати та записувати дані в сеансі клієнта через словник $_SESSION. Наведений нижче код демонструє управління трьома лічильниками в рамках сеансу.
<?php
// обробка помилок
ini_set("display_errors", "off");
// заголовок UTF-8
header("Content-Type: text/plain; charset=utf-8");
// відкриття сесії
session_start();
// заносимо 3 змінні в сесію
if (!isset($_SESSION['N1'])) {
$_SESSION['N1'] = 0;
}
if (!isset($_SESSION['N2'])) {
$_SESSION['N2'] = 10;
}
if (!isset($_SESSION['N3'])) {
$_SESSION['N3'] = 100;
}
// збільшення значень 3 змінних
$_SESSION['N1']++;
$_SESSION['N2']++;
$_SESSION['N3']++;
// відправлення інформації клієнту
print "N1=".$_SESSION['N1']."\n";
print "N2=".$_SESSION['N2']."\n";
print "N3=".$_SESSION['N3']."\n";
// завершення сесії
session_close();
- рядок 9: запуск сеансу клієнта
- рядки 11–13: масив $_SESSION є словником пар (ключ, значення). Дані, внесені до цього словника, зберігаються протягом усіх запитів одного й того самого клієнта. Це — пам’ять клієнта на сервері.
- рядки 11–19: якщо три лічильники N1, N2, N3 не знаходяться в сесії, їх додають до неї.
- рядки 21–23: їх значення збільшують на одиницю
- рядки 25–27: їхні значення надсилаються клієнту
У взаємодії «клієнт — сервер» управління сеансом клієнта на сервері залежить від обох учасників — клієнта та сервера:
- сервер відповідає за надсилання ідентифікатора клієнту під час його першого запиту
- клієнт зобов’язаний надсилати цей ідентифікатор із кожним новим запитом. Якщо він цього не зробить, сервер вважатиме, що це новий клієнт, і згенерує новий ідентифікатор для нової сесії.
Результати
Як клієнт ми використовуємо веб-браузер. За замовчуванням (а точніше, згідно з налаштуваннями) він надсилає серверу ідентифікатори сеансу, які той йому надсилає. У міру надходження запитів браузер отримуватиме три лічильники, надіслані сервером, і бачитиме, як їхні значення збільшуються.
![]() |
- до [1], перший запит до веб-сервісу [web_05]
- до [2], третій запит показує, що лічильники дійсно збільшуються. Значення лічильників дійсно запам’ятовуються у міру надходження запитів.
Скористаємося Firebug, щоб переглянути заголовки HTTP, якими обмінюються сервер і клієнт. Закриваємо Firefox, щоб завершити поточну сесію з сервером, знову відкриваємо його та вмикаємо Firebug. Надсилаємо запит до сервісу |web_05]:
![]() |
Вище ми бачимо ідентифікатор сесії, надісланий сервером у відповіді на перший запит клієнта. Він використовує заголовок HTTP Set-Cookie.
Зробимо новий запит, оновивши (F5) сторінку у веб-браузері:
![]() |
З цього можна зробити два висновки:
- веб-браузер повертає ідентифікатор сесії із заголовком HTTP Cookie.
- у своїй відповіді веб-сервіс більше не включає цей ідентифікатор. Тепер саме клієнт відповідає за його надсилання у кожному своєму запиті.
10.4.3. Клієнт 1 (client1_web_05)
Тепер ми пишемо клієнтський скрипт на основі попереднього серверного скрипта. У процесі управління сесією він повинен поводитися як веб-браузер:
- у відповіді сервера на свій перший запит він повинен знайти ідентифікатор сеансу, який сервер йому надсилає. Він знає, що знайде його в заголовку HTTP Set-Cookie.
- У кожному наступному запиті він повинен надсилати серверу отриманий ідентифікатор. Він зробить це за допомогою заголовка HTTP Cookie.
Код клієнта виглядає так:
<?php
// дані
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/web_05.php";
// тестування
$cookie = "";
for ($i = 0; $i < 5; $i++) {
list($erreur, $cookie, $N1, $N2, $N3) = connecte($HOTE, $PORT, $urlServeur, $cookie);
print "----------------------------\n";
print "client(erreur,cookie,N1,N2,N3)=[$erreur,$cookie,$N1,$N2,$N3]\n";
print "----------------------------\n";
}
// завершення
exit;
function connecte($HOTE, $PORT, $urlServeur, $cookie) {
// підключає клієнта до ($HOTE,$PORT,$urlServeur)
// надсилає файл cookie $cookie, якщо він не порожній
// відображає всі рядки, отримані у відповіді
// відкриття з'єднання на порту 80 сервера $HOTE
$connexion = fsockopen($HOTE, $PORT);
// помилка?
if (!$connexion)
return array("erreur lors de la connexion au serveur ($HOTE, $PORT)");
// встановлюється з’єднання з $urlserveur
// заголовки (headers) протоколу HTTP повинні закінчуватися порожнім рядком
// GET
fputs($connexion, "GET $urlServeur HTTP/1.1\n");
// Хост
fputs($connexion, "Host: localhost\n");
// Підключення
fputs($connexion, "Connection: close\n");
// кукі надсилається, якщо він не порожній
if ($cookie) {
fputs($connexion, "Cookie: $cookie\n");
}//if
// надсилається порожній рядок
fputs($connexion, "\n");
// виводиться відповідь веб-сервера
// і дбаємо про те, щоб отримати можливий файл cookie та значення Ni
$N = "";
while ($ligne = fgets($connexion, 1000)) {
print "$ligne";
// файл cookie — лише під час першої відповіді
if (!$cookie) {
if (preg_match("/^Set-Cookie: (.*?)\s*$/", $ligne, $champs)) {
$cookie = $champs[1];
}
}
// значення N1
if (preg_match("/^N1=(.*?)\s*$/", $ligne, $champs))
$N1 = $champs[1];
// значення N2
if (preg_match("/^N2=(.*?)\s*$/", $ligne, $champs))
$N2 = $champs[1];
// значення N3
if (preg_match("/^N3=(.*?)\s*$/", $ligne, $champs))
$N3 = $champs[1];
}//while
// з'єднання закрито
fclose($connexion);
// повернення
return array("", $cookie, $N1, $N2, $N3);
}
Коментарі
- рядки 3–16: головна програма
- рядки 18–67: функція connecte
- рядки 9–14: клієнт 5 разів звертається до сервера та виводить послідовні значення лічильників N1, N2 та N3. Якщо сесія управляється правильно, ці лічильники повинні збільшуватися на 1 з кожним новим запитом.
- рядок 10: функція connecte використовує параметри $HOTE, $PORT, $urlServeur для підключення клієнта до веб-сервісу. Параметр $cookie представляє ідентифікатор сеансу. Під час першого виклику це порожній рядок. Під час наступних викликів це ідентифікатор сеансу, надісланий сервером у відповідь на перший виклик клієнта. Функція connecte повертає у результатах значення трьох лічильників $N1, $N2, $N3, ідентифікатор сеансу $cookie та можливу помилку $erreur.
- рядок 18: функція connecte має характеристики класичного HTTP-клієнта. Ми коментуємо лише нові елементи.
- рядки 30–40: відправлення заголовків HTTP.
- рядки 36–38: якщо ідентифікатор сеансу відомий, його надсилають на сервер
- рядки 44–66: обробка всіх рядків тексту, надісланих сервером
- рядки 47–51: якщо ідентифікатор сеансу ще не отримано, його витягують із заголовка HTTP Set-Cookie за допомогою регулярного виразу.
- рядки 53–54: лічильник N1 також отримується за допомогою регулярного виразу
- рядки 56–57, 59–60: те саме стосується лічильників N2 та N3
- рядок 63: закриття з’єднання з сервером.
- рядок 65: повернення результатів у вигляді масиву.
Результати
Виконання клієнтського скрипта призводить до відображення такого виводу в консолі NetBeans:
- рядок 5: у першій відповіді сервер надсилає ідентифікатор сеансу. У наступних відповідях він його більше не надсилає.
- Чітко видно, що веб-сервер зберігає значення (N1, N2, N3) протягом усіх запитів клієнта. Це називається відстеженням сесії.
Два наступні приклади показують, що також можна зберігати значення масиву або об’єкта.
10.4.4. Сервер 2 (web_06)
Наступний серверний скрипт демонструє, що в сесію можна помістити масив або словник.
<?php
// обробка помилок
ini_set("display_errors", "off");
// заголовок UTF-8
header("Content-Type: text/plain; charset=utf-8");
// відкриття сеансу
session_start();
// збереження масиву та словника
// ініціалізація або зміна масиву
if (isset($_SESSION['tableau'])) {
for ($i = 0; $i < count($_SESSION['tableau']); $i++) {
$_SESSION['tableau'][$i]++;
}
} else {
for ($i = 0; $i < 10; $i++) {
$_SESSION['tableau'][$i] = $i * 10;
}
}
// ініціалізація або зміна словника
if (isset($_SESSION['dico'])) {
foreach (array_keys($_SESSION['dico']) as $clé) {
$_SESSION['dico'][$clé]++;
}
} else {
$_SESSION['dico'] = array("zéro" => 0, "dix" => 10, "vingt" => 20);
}
// надсилання інформації клієнту
print "tableau=" . join(",", $_SESSION['tableau']) . "\n";
print "dico=";
foreach ($_SESSION['dico'] as $clé => $valeur) {
print "($clé,$valeur) ";
}
print "\n";
Коментарі
- рядки 17–19: масив спочатку додається до сесії, якщо його там ще немає
- рядки 12–15: якщо він уже там є, то значення його елементів збільшуються на 1
- рядок 27: словник із числовими значеннями додається до сесії, якщо його там ще немає
- рядки 22–25: якщо він уже є, його числові значення збільшуються на 1
- рядки 30–35: масив і словник надсилаються клієнту
10.4.5. Клієнт 2 (client1_web_06)
<?php
// дані
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/web_06.php";
// тестування
$cookie = "";
for ($i = 0; $i < 5; $i++) {
connecte($HOTE, $PORT, $urlServeur, $cookie);
}
// завершення
exit;
function connecte($HOTE, $PORT, $urlServeur, &$cookie) {
// підключає клієнта до ($HOTE,$PORT,$urlServeur)
// надсилає файл cookie $cookie, якщо він не порожній
// відображає всі рядки, отримані у відповіді
// файл cookie передається за посиланням для спільного використання між
// викликаною програмою та програмою, що викликає
// відкриття з'єднання на порту $PORT програми $HOTE
$connexion = fsockopen($HOTE, $PORT);
// помилка?
if (!$connexion)
return array("erreur lors de la connexion au serveur ($HOTE, $PORT)");
// заголовки (headers) протоколу HTTP повинні закінчуватися порожнім рядком
// GET
fputs($connexion, "GET $urlServeur HTTP/1.1\n");
// Хост
fputs($connexion, "Host: localhost\n");
// З'єднання
fputs($connexion, "Connection: close\n");
// файл cookie надсилається, якщо він не порожній
if ($cookie) {
fputs($connexion, "Cookie: $cookie\n");
}
// надсилання порожнього рядка
fputs($connexion, "\n");
// виводиться відповідь веб-сервера
// і дбаємо про те, щоб отримати можливий файл cookie
while ($ligne = fgets($connexion, 1000)) {
print "$ligne";
// файл cookie — тільки під час першого відповіді
if (!$cookie) {
if (preg_match("/^Set-Cookie: (.*?)\s*$/", $ligne, $champs)) {
$cookie = $champs[1];
}
}
}
// закриваємо з'єднання
fclose($connexion);
// повернення
return "";
}
Код клієнта аналогічний коду клієнта, що вже було прокоментовано.
Результати
10.4.6. Сервер 3 (web_07)
Наступний серверний скрипт демонструє, що об’єкт можна додати до сесії.
<?php
// обробка помилок
ini_set("display_errors", "off");
// заголовок UTF-8
header("Content-Type: text/plain; charset=utf-8");
// відкриття сеансу
session_start();
// ініціалізація або зміна об’єкта «Особа»
if (isset($_SESSION['personne'])) {
$personne = $_SESSION['personne'];
// збільшується вік
$personne->setAge($personne->getAge() + 1);
} else {
// визначається особа
$_SESSION['personne'] = new Personne("paul", "langévin", 10);
}
// відображення для клієнта
print "personne=".$_SESSION['personne']."\n";
// кінець
exit;
// ----------------------------------------------------------------
class Personne {
// атрибути класу
private $prénom;
private $nom;
private $âge;
// методи getter та setter
public function getPrénom() {
return $this->prénom;
}
public function getNom() {
return $this->nom;
}
public function getAge() {
return $this->âge;
}
public function setPrénom($prénom) {
$this->prénom = $prénom;
}
public function setNom($nom) {
$this->nom = $nom;
}
public function setAge($age) {
$this->âge = $age;
}
// конструктор
function __construct($prénom, $nom, $âge) {
// використовуємо методи set
$this->setPrénom($prénom);
$this->setNom($nom);
$this->setAge($âge);
}
// метод toString
function __toString() {
return "[$this->prénom,$this->nom,$this->âge]";
}
}
Коментарі
- рядок 17: об’єкт типу Personne додається до сесії, якщо його там ще немає.
- рядки 11–15: якщо він уже є, його вік збільшується на 1
- рядок 20: об’єкт типу Personne надсилається клієнту.
10.4.7. Клієнт 3 (client1_web_07)
<?php
// дані
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/web_07.php";
// тести
$cookie = "";
for ($i = 0; $i < 5; $i++) {
connecte($HOTE, $PORT, $urlServeur, $cookie);
}//if
// кінець
exit;
function connecte($HOTE, $PORT, $urlServeur, &$cookie) {
...
}
Коментарі
- рядок 15: функція connecte ідентична функції з попереднього клієнтського скрипта
Результати




















