Symfony та основи HTTP

Дата оновлення перекладу 2022-05-19

Symfony та основи HTTP

Чудові новини! Вивчаючи Symfony, ви також вивчаєте основи мережі. Symfony побудована за моделлю HTTP Request-Response: це фундаментальна парадигма, яка стоїть майже за всією комунікацією в мережі.

В цій статті ви продетеся по основах HTTP та дізнаєтеся, як вони застосовуються у Symfony.

Запити та відповіді в HTTP

HTTP (Hypertext Transfer Protocol або протокол передачі гіпертекстових документів) - це текстова мова, яка дозволяє двом комп'ютерам обмінюватися повідомленнями один з одним. Наприклад, при перегляді свіжого коміксу xkcd, відбувається (приблизно) такий діалог:

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

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

Крок 1: Клієнт надсилає запит

Будь-який діалог в мережі починається з запиту. Запит - це текстове повідомлення, створене клієнтом (це може бути браузер, застосунок на смартфоні і т.д.) у спеціальному форматі, відомому як HTTP. Клієнт надсилає цей запит серверу, а потім чекає на відповідь.

Погляньте на першу частину взаємодії (запит) між браузером та веб-сервером xkcd:

На мові HTTP цей запит виглядатиме приблизно так:

1
2
3
4
GET / HTTP/1.1
Host: xkcd.com
Accept: text/html
User-Agent: Mozilla/5.0 (Macintosh)

Ці декілька рядків містять всю необхідну інформацію про те, який саме ресурс запитує клієнт. Перший рядок HTTP-запиту найважливіший - він містить 2 речі: HTTP-метод (GET) та URI (/).

URI (наприклад /, /contact і т.д.) - це унікальна адреса або місце, яке визначає запитуваний клієнтом ресурс. HTTP-метод (наприклад, GET) визначає, що саме клієнт хоче зробити із запитуваним ресурсом. HTTP-методи (їх іноді називають дієсловами) визначають декілька типових способів взаємодії із запитуваним ресурсом. Найвикористовуваніші:

GET
Отримати ресурс з серверу (наприклад, при перегляді сторінки);
POST
Створити ресурс на сервері (наприклад, при відправленні форми);
PUT/PATCH
Оновити ресурс на сервері (використовується в API);
DELETE
Видалити ресурс з серверу (використовується в API).

Запам'ятавши ці типи HTTP-методів, ви можете уявити собі як виглядатиме HTTP-запит на видалення запису у блозі:

1
DELETE /blog/15 HTTP/1.1

Note

Насправді всього існує дев'ять HTTP-методів, визначених у специфікації протоколу HTTP, але багато з них дуже малопоширені або ж підтримуються обмежено. У реальному світі, багато сучасних браузерів підтримують лише методи POST та GET в HTML формах. Тим не менше, інші HTTP-методи підтримуються в XMLHttpRequest.

На додаток до першого рядку, HTTP-запит завжди містить декілька інформаційних рядків, які називаються заголовками запиту. у заголовках може міститися різноманітна інформація, наприклад, запитуваний Host, формати відповіді, які підтримує клієнт (Accept) або ж там може бути опис додатку, який клієнт використовує для виконання запиту (User-Agent). Існує також багато інших заголовків, перелік яких ви можете знайти у Вікіпедії на сторінці список заголовків HTTP.

Крок 2: Сервер повертає відповідь

Як тільки сервер отримав запит, він точно знає, який ресурс потрібен клієнту (засновуючись на URI) і що клієнт хоче з цим ресурсом зробити (засновуючись на HTTP-методі). Наприклад, якщо ми маємо справу із GET-запитом, сервер готує запитуваний ресурс та повертає його у вигляді HTTP-відповіді. Розглянемо відповідь від веб-серверу xkcd:

Переклавши на мову HTTP, відповідь, відправлена назад у браузер, виглядатиме приблизно так:

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Date: Sat, 02 Apr 2011 21:05:05 GMT
Server: lighttpd/1.4.19
Content-Type: text/html

HTTP-відповідь містить запитуваний ресурс (в даному випадку це HTML-код сторінки), а також додаткові дані про саму відповідь. Перший рядок особливо важливий - він містить код стану HTTP відповіді (в даному випадку 200).

Статус-код доносить інформацію про результат виконання запиту назад до клієнту. Запит було виконано успішно. Або у ході запиту було припущено помилку? Існують різноманітні коди стану, одни з яких говорять про успішне виконання запиту, інші - вказують на помилки, треті повідомляють, що клієнту необхідно виконати якусь додаткову дію (наприклад, перейти на іншу сторінку у випадку перенаправлення). Повний список статус-кодів ви можете знайти на сторінці Вікіпедії список кодів стану HTTP.

Подібно запиту, HTTP-відповідь містить додаткову інформацію, яка називається HTTP-заголовками. Тіло одного й того ж ресурсу може бути повернено у багатьох різних форматах, включно з HTML, XML або JSON. Для того, щоб повідомити клієнту, який саме формат використовується, застосовується заголовок Content-Type зі значенням на кшталт text/html. Переглянути список основних типів даних можна на IANA: Список розповсюджених типів медіа.

Існує багато різноманітних заголовків, і деякі з них надають важливий функціонал. Наприклад, деякі заголовки можуть бути використані для створення системи кешування.

Запити, відповіді та веб-розробка

Обмін запитами-відповідями - це фундаментальний процес, на якому засновується вся комунікація в мережі. Цей процес настільки важливий та функціиональний, що його простота приголомшує.

Найважливіше полягає в наступному: незалежно від того, яку мову програмування ви використовуєте, який застосунок створюєте (веб, мобільний, JSON API) і навіть якої філософії програмування дотримуєтеся, кінцевою ціллю додатку завжди буде зрозуміти запит, а потім створити та повернути належну відповідь.

See also

Щоб дізнатися більше про специфікацію HTTP, прочитайте оригінальну специфікацію HTTP 1.1 RFC або HTTP Bis, яка є спробою пояснити початкову специфікацію і, крім того, постійно оновлюється.

Запити та відповіді в PHP

Як же вам обробити "запит" та створити "відповідь" за допомогою PHP? Насправді PHP трохи абстрагує вас від усього процесу:

1
2
3
4
5
6
$uri = $_SERVER['REQUEST_URI'];
$foo = $_GET['foo'];

header('Content-Type: text/html');
echo 'Запрошенный URI: '.$uri;
echo 'Значение параметра "foo": '.$foo;

Як би дивно це не звучало, цей крихітний застосунок насправді отримує інформацію з HTTP-запиту та використовує її для створення HTTP-відповіді. Замість того, щоб обробляти "сирий" HTTP-запит, PHP готує суперглобальні змінні, такі як $_SERVER та $_GET, які містять всю інформацію про запит. Аналогічно, замість того, щоб повертати текст відповіді, відформатований за правилами HTTP, ви можете використовувати PHP функцію header для створення заголовків відповідей та просто вивести на друк основний зміст, який стане тілом відповіді. PHP створить правильну HTTP-відповідь та поверне її клієнту:

1
2
3
4
5
6
7
HTTP/1.1 200 OK
Date: Sat, 03 Apr 2011 02:14:33 GMT
Server: Apache/2.2.17 (Unix)
Content-Type: text/html

Запитуваний URI: /testing?foo=symfony
Значення параметру "foo": symfony

Запити та відповіді в Symfony

На відміну від прямолінійного підходу PHP, Symfony надає два класи, які спрощують взаємодію з HTTP запитом та відповіддю.

Об'єкт Symfony Request

Клас Request - це об'єктно-орієнтоване уявлення HTTP-запиту. З його допомогою, вся інформація з запиту знаходиться прямо у вас перед очима:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use Symfony\Component\HttpFoundation\Request;

$request = Request::createFromGlobals();

// запитуваний URI (наприклад, /about) без параметрів запиту
$request->getPathInfo();

// вилучає змінні GET та POST, відповідно
$request->query->get('id');
$request->request->get('category', 'default category');

// вилучає змінні $_SERVER
$request->server->get('HTTP_HOST');

// вилучає екземпляр UploadedFile за ідентифікатором "attachment"
$request->files->get('attachment');

// вилучає значення $_COOKIE
$request->cookies->get('PHPSESSID');

// вилучає HTTP-заголовок запиту з нормалізованими ключами у нижньому реєстрі
$request->headers->get('host');
$request->headers->get('content_type');

$request->getMethod();    // наприклад, GET, POST, PUT, DELETE або HEAD
$request->getLanguages(); // список мов, які приймаються клієнтом, у масиві

В якості бонусу, клас Request виконує великий обсяг роботи у фоновому режимі, так що вам не доведеться піклуватися про багато речей. Наприклад, метод isSecure() перевіряє три різних значення в PHP, які вказують, чи підключається користувач за захищеним протоколом (HTTPS).

Об'єкт Symfony Response

Symfony також надає клас Response: просте РHP-уявлення HTTP-відповіді. Це дозволяє вашому додатку використовувати об'єктно-орієнтований інтерфейс для створення відповіді, який потім буде потрібно повернути клієнту:

1
2
3
4
5
6
7
8
9
10
11
12
use Symfony\Component\HttpFoundation\Response;

$response = new Response();

$response->setContent('<html><body><h1>Привет, мир!</h1></body></html>');
$response->setStatusCode(Response::HTTP_OK);

// встановлює заголовок HTTP-відповіді
$response->headers->set('Content-Type', 'text/html');

// видає HTTP-заголовок, а слід за ним зміст
$response->send();

Також існують особливі під-класи відповідей, які спрощують створення: JSON, перенаправлень, стримінгу файлів

Tip

Класи Request та Response є частиною самостійного компоненту під назвою symfony/http-foundation, який ви можете використовувати в будь-якому проекті PHP (незалежно від Symfony). Він також надає класи для роботи з сесіями, завантаженими файлами та ін.

Якщо б можливості Symfony закінчувалися на перерахованому вище, у вас би вже був набір інструментів для простого та швидкого отримання доступу до інформації з запиту, а також об'єктно-орієнтований інтерфейс для створення відповіді. Навіть коли ви опануєте потужніші можливості Symfony, спробуйте не забувати, що ціль вашого додатку завжди полягає в інтерпретації запиту та створенні відповідної відпоіді, засновуючись на логіці вашого додатку.

Подорож від запиту до відповіді

Як і HTTP-протокол, об'єкти Request та Response достатньо прості. Найскладніша частина створення додатку полягає в описі процесів, які відбуваються між отриманням запиту та відправкою відповіді. Іншими словами, справжня робота полягає в тому, щоб написати код, який інтерпретує інформацію з запиту та створює відповідь.

Ваш застосунок напевно виконує багато функцій, таких як відправлення електронних листів, обробка відправлених форм, збереження чогось в базі даних, відображення HTML-сторінок та захист змісту правилами безпеки, Чи можна впоратися з усіма цими завданнями таким чином, щоб ваш код залишався добре організованим та легким для підтримки? Symfony була створена спеціально, щоб вирішення цих проблем більше не лягало на ваші плечі.

Фронт-контролер

Традиційно додатки створювалися таким чином, щоб кожна "сторінка" сайту мала свій власний файл: (наприклад, index.php, contact.php, і т.д.).

При такому підході є цілий ряд проблем, включно з не гнучкістю URL (раптом вам знадобиться змінити blog.php на news.php і при цьому зберегти всі ваші посилання?). Ще однією проблемлю є необхідність вручну доповнювати кожний файл певним набором ключових файлів, які відповідають за безпеку, роботу з базами даних та дизайн сайту.

Набагато кращим рішенням буде використати фронт-контролер, єдиний PHP-файл, який відповідає за кожний запит, що поступає до вашого додатку. Наприклад,

/index.php ??????? index.php
/index.php/contact ??????? index.php
/index.php/blog ??????? index.php

Tip

Используя правила перенаправления (rewrite) в настройках веб-сервера, index.php в адресе не будет нужен и у вас будут красивые, чистые URLы (например, /show).

Тепер, кожний запит обробляється по одному і тому ж принципу. Замість того, щоб кожний URL відповідав окремому PHP-файлу - фронт-контролер виконується завжди та за допомогою маршрутизатору викликає різноманітні частини вашого додатку, в залежності від URL.

Дуже простий фронт-контролер може виглядати так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// index.php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();
$path = $request->getPathInfo(); // запрашиваемый URI

if (in_array($path, array('', '/'))) {
    $response = new Response('Добро пожаловать на главную страницу');
} elseif ('/contact' === $path) {
    $response = new Response('Обратная связь');
} else {
    $response = new Response('Страница не найдена', Response::HTTP_NOT_FOUND);
}
$response->send();

Це краще, але все одно багато однакової роботи! На щастя, Symfony знову поспішає на допомогу.

Потік у додатку Symfony

Застосунок, який використовує фреймворк Symfony теж використовує файл фронт-контролера. Але всередині, Symfony відповідальна за обробку кожного вхідного запиту та рішення, що необхідно зробити:

Вхідні запити обробляються компонентом Маршрутизатор і передаються у PHP-функціи, які повертають об'єкти Response.

Поки що може здаватися, що в цьому немає сенсу, але якщо ви продовжите читати, ви дізнаєтеся про маршрути та контролери: дві фундаментальні частини для створення сторінки. Але в процесі навчання не забувайте, що не важливо наскільки складним стає ваш застосунок, ваша робота залишається такою ж: читати інформацію з запиту та створювати відповідь.

Висновок: Потік запит-відовідь

Що ми зрозуміли на даний момент:

  1. Клієнт (наприклад, браузер) надсилає HTTP-запит (Request);
  2. Кодний запит запускає один і той же файл, під назвою "фронт-контролер";
  3. Фронт-контролер завантажує Symfony і передає інформацію про запит;
  4. Всередині, Symfony використовує шляхи і контролери для створення відповіді (Response): ми дізнаємося про це пізніше
  5. Symfony перетворює ваш об'єкт Response у текстові заголовки та тіло відповіді (тобто, HTTP-відповідь), яка надсилається назад до клієнта.