Skip to content

10. Сервери в PHP

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

Веб-клієнт, описаний у розділі 9.2, дав нам змогу ознайомитися з частиною протоколу HTTP.

Image

У найпростішому варіанті обмін даними між клієнтом і сервером відбувається так:

  • клієнт встановлює з’єднання з портом 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
2
3
GET /exemples-web/web01.php HTTP/1.1
Host: localhost
Connection: close
  • рядок 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
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Date: Wed, 17 Aug 2011 13:35:00 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Content-Length: 17
Connection: close
Content-Type: text/html

17/08/11 13:35:00

Коментарі

  • рядки 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;

Результати

HTTP/1.1 200 OK
Date: Wed, 17 Aug 2011 14:14:51 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Content-Length: 17
Connection: close
Content-Type: text/html

17/08/11 14:14:51
dateheure=[17,08,11,14,14,51]

10.2. Отримання сервером параметрів, надісланих клієнтом

У протоколі HTTP клієнт має два способи передачі параметрів веб-серверу:

  • він запитує URL служби у формі

GET url?param1=val1&param2=val2&param3=val3… HTTP/1.0

де значення vali спочатку мають бути закодовані, щоб певні зарезервовані символи були замінені на їхні шістнадцяткові значення.

  • він запитує URL від сервісу у вигляді

POST url HTTP/1.0

потім серед заголовків HTTP, надісланих на сервер, розміщує такий заголовок:

Content-length=N

Наступні заголовки, надіслані клієнтом, закінчуються порожнім рядком. Після цього він може надіслати свої дані у вигляді

val1&param2=val2&param3=val3…

, де значення vali, як і для методу GET, повинні бути попередньо закодовані. Кількість символів, що надсилаються на сервер, повинна дорівнювати N, де N — це значення, заявлене в заголовку

Content-length=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». За замовчуванням веб-сервер надсилає заголовок
Content-Type: text/html

, який вказує, що відповідь — це текст у форматі HTML. У цьому випадку відповідь буде текстом без форматування з символами, закодованими у форматі UTF-8:

Content-Type: text/plain; charset=utf-8

Заголовки HTTP повинні надсилатися перед відповіддю сервера. Тому, як показано вище, виклик функції header має знаходитися перед будь-якою інструкцією print.

  • рядки 16–18: 3 параметри зчитуються з масиву $_GET.
  • рядок 21: формується рядок символів, який буде надіслано клієнту у відповідь. Деякі символи мають особливе значення в HTML і для відображення їх потрібно замінити на ентітети HTML. htmlspecialchars($string) замінює всі ці символи на їхні еквіваленти у рядку $string. Наприклад, символ $ перетворюється на &amp. Далі, оскільки в рядку 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]. Отримуємо таку відповідь:

infos envoyées au serveur (GET)=prenom=jean-paul&nom=de+la+h%FBche&age=45
URL demandée=[/exemples-web/web_02.php?prenom=jean-paul&nom=de+la+h%FBche&age=45]

HTTP/1.1 200 OK
Date: Wed, 17 Aug 2011 14:31:01 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Content-Length: 59
Connection: close
Content-Type: text/plain; charset=utf-8

informations reçues du client [jean-paul,de la hûche,45]
  • рядок 1: кодування 3 параметрів. Бачимо, що символ û перетворився на %FB.
  • рядок 12: відповідь сервера

10.2.3. Клієнт POST (client2_web_03)

HTTP-клієнт надсилає веб-серверу таку послідовність тексту: заголовки HTTP, порожній рядок, документ. У попередньому клієнті ця послідовність була такою:

GET /url?paramètres HTTP/1.1
… autres entêtes HTTP
ligne vide

Документа не було. Існує ще один спосіб передачі параметрів — метод, відомий як POST. У цьому випадку послідовність тексту, що надсилається на веб-сервер, є такою:

POST /url HTTP/1.1
… autres entêtes HTTP
ligne vide
paramètres

Цього разу параметри, які для клієнта 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
Content-type : text/html

Якщо він надсилає неформатований текст, він використовує заголовок HTTP

Content-type : text/plain

Наш клієнт POST надсилає документ, який є текстом у форматі param1=val1&param2=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). Запускаємо клієнт, який надсилає запит до сервера, і отримуємо таку відповідь:

client : infos envoyées au serveur (POST) : prenom=jean-paul&nom=de+la+h%FBche&age=45
HTTP/1.1 200 OK
Date: Sat, 20 Aug 2011 12:55:19 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Content-Length: 59
Connection: close
Content-Type: text/plain; charset=utf-8

informations reçues du client [jean-paul,de la hûche,45]
  • рядки 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 надсилаються клієнтам.

Результат, який отримуємо, коли клієнтом є веб-браузер, такий:

Image

Ось значення деяких змінних (для Windows. У Linux вони будуть іншими):

HTTP_CMDE
CMDE представляє заголовок HTTP, надісланий клієнтом. Ми маємо доступ до всіх цих заголовків.
PATH
шлях до виконуваних файлів на комп’ютері, на якому виконується серверний скрипт
COMSPEC
шлях до командного інтерпретатора DOS
PATHEXT
розширення виконуваних файлів
WINDIR
папка інсталяції Windows
SERVER_SIGNATURE
підпис веб-сервера. Тут нічого немає.
SERVER_SOFTWARE
тип веб-сервера
SERVER_NAME 
Інтернет-ім'я комп'ютера веб-сервера
SERVER_PORT
порт, на якому працює веб-сервер
SERVER_ADDR
адреса IP комп'ютера, на якому розташований веб-сервер
REMOTE_ADDR
адреса IP клієнта. У цьому випадку клієнт знаходився на тому самому комп'ютері, що й сервер.
REMOTE_PORT
порт зв’язку клієнта
DOCUMENT_ROOT
корінь дерева документів, що обслуговуються веб-сервером
SERVER_ADMIN
електронна адреса адміністратора веб-сервера
SCRIPT_FILENAME
повний шлях до скрипту сервера
SERVER_PROTOCOL
версія протоколу HTTP, що використовується веб-сервером
REQUEST_METHOD
Http-команда, яку використовує клієнт. Їх є чотири: GET, POST, PUT, DELETE
QUERY_STRING
параметри, надіслані разом із запитом GET /url?параметри
REQUEST_URI
URL, запитуваний клієнтом. Якщо браузер запитує URL http://machine[:port]/uri, отримаємо REQUEST_URI=uri
SCRIPT_NAME
$_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;

Результати

HTTP/1.1 200 OK
Date: Sat, 20 Aug 2011 14:11:58 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Content-Length: 1353
Connection: close
Content-Type: text/plain; charset=utf-8

[HTTP_HOST,localhost]
[HTTP_CONNECTION,close]
[PATH,C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files\Microsoft SQL Server\90\Tools\binn\;...;]
[SystemRoot,C:\Windows]
[COMSPEC,C:\Windows\system32\cmd.exe]
[PATHEXT,.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC]
[WINDIR,C:\Windows]
[SERVER_SIGNATURE,]
[SERVER_SOFTWARE,Apache/2.2.17 (Win32) PHP/5.3.5]
[SERVER_NAME,localhost]
[SERVER_ADDR,127.0.0.1]
[SERVER_PORT,80]
[REMOTE_ADDR,127.0.0.1]
[DOCUMENT_ROOT,C:/serveursSGBD/wamp21/www/]
[SERVER_ADMIN,admin@localhost]
[SCRIPT_FILENAME,C:/serveursSGBD/wamp21/www/exemples-web/web_04.php]
[REMOTE_PORT,54552]
[GATEWAY_INTERFACE,CGI/1.1]
[SERVER_PROTOCOL,HTTP/1.1]
[REQUEST_METHOD,GET]
[QUERY_STRING,]
[REQUEST_URI,/exemples-web/web_04.php]
[SCRIPT_NAME,/exemples-web/web_04.php]
[PHP_SELF,/exemples-web/web_04.php]
[REQUEST_TIME,1313849518]

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] слід шукати в різних папках. Щоб їх визначити, використовується такий скрипт:

1
2
3
4
<?php

// інформація PHP
phpinfo();

У рядку 4 функція phpinfo надає інформацію про інтерпретатор PHP, який виконує скрипт. Зокрема, вона вказує шлях до використовуваного файлу конфігурації [PHP.ini].

У консольному середовищі отримуємо результат, подібний до такого:

1
2
3
4
5
6
Configuration File (PHP.ini) Path => C:\Windows
Loaded Configuration File => C:\serveursSGBD\wamp21\bin\PHP\php5.3.5\PHP.ini
Scan this dir for additional .ini files => (none)
Additional .ini files parsed => (none)

рядок 2: головний файл конфігурації — c:\windows\PHP.ini

рядок 3: додатковий файл конфігурації — C:\serveursSGBD\wamp21\bin\PHP\php5.3.5\PHP.ini. Він дозволяє змінювати певні параметри конфігурації головного конфігураційного файлу.

У веб-середовищі отримуємо такий результат:

Image

Додатковий файл конфігурації тут відрізняється від того, що використовується в консольному середовищі. Саме його ми й розглянемо. У цьому файлі міститься розділ session:

[Session]
session.save_handler = files
session.save_path = "C:/serveursSGBD/wamp21/tmp"
session.use_cookies = 1
session.use_only_cookies = 1
session.name = PHPSESSID
session.auto_start = 0
session.cookie_lifetime = 0
session.cookie_path = /
session.cookie_domain =
session.serialize_handler = PHP
session.cache_expire = 180
session.hash_function = 0
  • рядок 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:

HTTP/1.1 200 OK
Date: Sun, 21 Aug 2011 13:59:17 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Set-Cookie: PHPSESSID=ohiqtkv7hu2b26kdshjtqms9p7; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 18
Connection: close
Content-Type: text/plain; charset=utf-8

N1=1
N2=11
N3=101
----------------------------
client(erreur,cookie,N1,N2,N3)=[,PHPSESSID=ohiqtkv7hu2b26kdshjtqms9p7; path=/,1,11,101]
----------------------------
HTTP/1.1 200 OK
Date: Sun, 21 Aug 2011 13:59:18 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 18
Connection: close
Content-Type: text/plain; charset=utf-8

N1=2
N2=12
N3=102
----------------------------
client(erreur,cookie,N1,N2,N3)=[,PHPSESSID=ohiqtkv7hu2b26kdshjtqms9p7; path=/,2,12,102]
----------------------------
HTTP/1.1 200 OK
Date: Sun, 21 Aug 2011 13:59:18 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 18
Connection: close
Content-Type: text/plain; charset=utf-8

N1=3
N2=13
N3=103
----------------------------
client(erreur,cookie,N1,N2,N3)=[,PHPSESSID=ohiqtkv7hu2b26kdshjtqms9p7; path=/,3,13,103]
----------------------------
HTTP/1.1 200 OK
Date: Sun, 21 Aug 2011 13:59:18 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 18
Connection: close
Content-Type: text/plain; charset=utf-8

N1=4
N2=14
N3=104
----------------------------
client(erreur,cookie,N1,N2,N3)=[,PHPSESSID=ohiqtkv7hu2b26kdshjtqms9p7; path=/,4,14,104]
----------------------------
HTTP/1.1 200 OK
Date: Sun, 21 Aug 2011 13:59:18 GMT
Server: Apache/2.2.17 (Win32) PHP/5.3.5
X-Powered-By: PHP/5.3.5
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 18
Connection: close
Content-Type: text/plain; charset=utf-8

N1=5
N2=15
N3=105
----------------------------
client(erreur,cookie,N1,N2,N3)=[,PHPSESSID=ohiqtkv7hu2b26kdshjtqms9p7; path=/,5,15,105]
  • рядок 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 "";
}

Код клієнта аналогічний коду клієнта, що вже було прокоментовано.

Результати

HTTP/1.1 200 OK
...
Set-Cookie: PHPSESSID=6lvttr0uhpj5q3sl91h4h7p322; path=/
...

tableau=0,10,20,30,40,50,60,70,80,90
dico=(zéro,0) (dix,10) (vingt,20) 

HTTP/1.1 200 OK
...

tableau=1,11,21,31,41,51,61,71,81,91
dico=(zéro,1) (dix,11) (vingt,21) 

HTTP/1.1 200 OK
...

tableau=2,12,22,32,42,52,62,72,82,92
dico=(zéro,2) (dix,12) (vingt,22) 

HTTP/1.1 200 OK
...

tableau=3,13,23,33,43,53,63,73,83,93
dico=(zéro,3) (dix,13) (vingt,23) 

HTTP/1.1 200 OK
...

tableau=4,14,24,34,44,54,64,74,84,94
dico=(zéro,4) (dix,14) (vingt,24) 

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 ідентична функції з попереднього клієнтського скрипта

Результати

HTTP/1.1 200 OK
...

personne=[paul,langévin,10]

HTTP/1.1 200 OK
...

personne=[paul,langévin,11]

HTTP/1.1 200 OK
...

personne=[paul,langévin,12]

HTTP/1.1 200 OK
...

personne=[paul,langévin,13]

HTTP/1.1 200 OK
...

personne=[paul,langévin,14]