Symfony и основы HTTP

Дата обновления перевода 2021-06-02

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) и URL (/).

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-запрос всегда содержит несколько информационных строк, именуемых заголовками запроса (headers). В заголовках может содержаться различная информация, например, запрошенный Host, форматы ответа, которые поддерживает клиент (Accept) или же там может быть описание приложения, которое клиент использует для выполнения запроса (User-Agent). Существует также много других заголовков, перечень которых вы можете найти в Википедии на странице список заголовков HTTP.

Шаг 2: Сервер возвращает ответ

Как только сервер получил запрос, он точно знает, какой ресурс нужен клиенту (основываясь на URI) и что клиент хочет с этим ресурсом сделать (на основании HTTP-метода). Например, если мы имеем дело с GET-запросом, сервер готовит запрошенный ресурс и возвращает его в виде HTTP-ответа. Рассмотрим ответ от web-сервера 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-ответ (response) содержит запрошенный ресурс (в данном случае это HTML-код страницы), а также дополнительные данные о самом ответе. Первая строка особенно важна - она содержит код состояния (status code) HTTP ответа (в данном случае 200).

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

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

Существует много различных заголовков, и некоторые из них предоставляют важный функционал. Например, некоторые заголовки могут быть использованы для создания системы кэширования.

Запросы, ответы и веб-разработка

Обмен запросами-ответами - это фундаментальный процесс, на котором основывается вся коммуникация в сети. Этот процесс настолько важен и функционален, что его простота изумляет.

Самое важное заключается в следующем: независимо от того, какой язык программирования вы используете, какое приложение создаёте (web, мобильное, 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) без query параметров
$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 достаточно просты. Самая сложная часть создания приложения заключается в описании процессов, которые происходят между получением запроса и отправкой ответа. Другими словами, настоящая работа заключается в том, чтобы написать код, который интерпретирует информацию из запроса и создаёт ответ.

Ваше приложение наверняка выполняет много функций, таких как отправка email'ов, обработка отправленных форм, сохранение чего-либо в базу данных, отображение 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-ответ), который отправляется обратно клиенту.