Symfony и основы HTTP

Поздравляем! Изучая Symfony, вы заодно изучаете основы сети. Symfony построена по модели HTTP Request-Response: это фундаментальная парадигма, которая стоит почти за всей коммуникацией в сети.

В этой статье вы пройдёте по основам HTTP и узнаете как они применяются в Symfony.

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

../_images/xkcd-full.png

И хотя настоящий язык более формальный, всё предельно просто. HTTP - это термин, используемый для описания этого простого языка, основанного на тексте. Цель вашего сервера всегда одна и та же - понимать простые текстовые запросы (requests) и возвращать текстовые ответы (responses).

Symfony построен вокруг этой реальности. Осознаете вы этот факт или нет, но вы используете HTTP каждый день. С помощью Symfony вы сможете вывести это умение на новый уровень.

Шаг 1: Клиент отправляет запрос

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

Взгляните на первую часть взаимодействия (запрос) между браузером и веб-сервером xkcd:

../_images/xkcd-request.png

На языке 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). Существует также много других заголовков, перечень которых вы можете найти в Википедии на странице List of HTTP header fields.

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

Как только сервер получил запрос, он точно знает, какой ресурс нужен клиенту (основываясь на URI) и что клиент хочет с этим ресурсом сделать (на основании HTTP-метода). Например, если мы имеем дело с GET-запросом, сервер готовит запрошенный ресурс и возвращает его в виде HTTP-ответа. Рассмотрим ответ от web-сервера xkcd:

../_images/xkcd-full.png

Переведя на язык 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

<html>
  <!-- ... HTML для комикса xkcd -->
</html>

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

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

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

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

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

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

Самое важное заключается в следующем: независимо от того, какой язык программирования вы используете, какое приложение создаёте (web, мобильное, JSON API) и даже какой философии программирования придерживаетесь, конечной целью приложения всегда будет понять запрос, а потом создать и вернуть подходящий ответ.

Чтобы узнать больше про спецификацию 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 ответствена за обработку каждого входящего запроса и решение, что надо сделать:

Поток запроса в Symfony

Входящие запросы обрабатываются Routing component и передаются в PHP-функции, которые возвращают объекты Response.

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

Итог: Поток запрос-ответ

Что мы поняли на данный момент:

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

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.