Найти в Дзене
GROWCAMP

Путь к HTTP/2 . Ч.1: 1991-2009

HTTP - это протокол, который должен знать каждый веб-разработчик, поскольку он обеспечивает работу всей сети, и знание его определенно поможет вам разрабатывать эффективные приложения. В этой статье я собираюсь разбрать, что такое HTTP, как он появился, где он находится сегодня и как мы здесь оказались. Что такое HTTP? Прежде всего, что такое HTTP? HTTP — это протокол связи прикладного уровня на основе TCP/IP, который стандартизирует взаимодействие клиента и сервера друг с другом. Он определяет, как контент запрашивается и передается через Интернет. Под протоколом прикладного уровня я подразумеваю просто уровень абстракции, который стандартизирует то, как взаимодействуют хосты (клиенты и серверы), и сам он зависит от TCP/IP в получении запросов и ответов между клиентом и сервером. По умолчанию используется TCP-порт 80, но можно использовать и другие порты. А вот HTTPS использует порт 443. HTTP/0.9 - Однострочник (1991) Первой задокументированной версией HTTP был HTTP/0.9, который был п
Оглавление

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

В этой статье я собираюсь разбрать, что такое HTTP, как он появился, где он находится сегодня и как мы здесь оказались.

Что такое HTTP?

Прежде всего, что такое HTTP? HTTP — это протокол связи прикладного уровня на основе TCP/IP, который стандартизирует взаимодействие клиента и сервера друг с другом. Он определяет, как контент запрашивается и передается через Интернет. Под протоколом прикладного уровня я подразумеваю просто уровень абстракции, который стандартизирует то, как взаимодействуют хосты (клиенты и серверы), и сам он зависит от TCP/IP в получении запросов и ответов между клиентом и сервером. По умолчанию используется TCP-порт 80, но можно использовать и другие порты. А вот HTTPS использует порт 443.

HTTP/0.9 - Однострочник (1991)

Первой задокументированной версией HTTP был HTTP/0.9, который был представлен в 1991 году. Это был самый простой протокол из когда-либо существовавших; у него был один метод, называемый GET. Если бы клиенту нужно было получить доступ к какой-либо веб-странице на сервере, он бы сделал простой запрос, как показано ниже:

GET /index.html

И ответ от сервера выглядел бы следующим образом:

(response body)
(connection closed)

То есть сервер получит запрос, ответит HTML-кодом в ответ, и как только содержимое будет передано, соединение будет закрыто. В этой версии

  • Не было заголовков
  • GET был единственным доступным методом
  • Ответ всегда был в виде HTML-документа

Как вы можете видеть, протокол на самом деле был не более чем ступенькой к тому, что должно было произойти.

HTTP/1.0 - 1996

В 1996 году появилась следующая версия HTTP, то есть HTTP/1.0, которая значительно улучшилась по сравнению с первоначальной версией.

В отличие от HTTP/0.9, который был разработан только для ответа в формате HTML, HTTP/1.0 теперь мог работать и с другими форматами ответов, такими как изображения, видеофайлы, обычный текст или любой другой тип контента. В него добавлено больше методов (например, POST и HEAD), изменены форматы запросов/ответов, добавлены заголовки HTTP как к запросу, так и к ответам, добавлены коды состояния для идентификации ответа, введена поддержка набора символов, включены многокомпонентные типы контента, авторизация, кэширование, кодирование контента и многое другое.

Вот как может выглядеть пример запроса и ответа HTTP/1.0:

GET / HTTP/1.0
Host: kamranahmed.info
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
Accept: */*

Как вы можете видеть, наряду с запросом клиент также отправил свою личную информацию, требуемый тип ответа и т.д. В то время как в HTTP/0.9 клиент никогда не мог отправить такую информацию, потому что не было заголовков.

Пример ответа на приведенный выше запрос может выглядеть следующим образом:

HTTP/1.0 200 OK
Content-Type: text/plain
Content-Length: 137582
Expires: Thu, 05 Dec 1997 16:00:00 GMT
Last-Modified: Wed, 5 August 1996 15:55:28 GMT
Server: Apache 0.84
(response body)
(connection closed)

В самом начале ответа мы видим HTTP/1.0 (HTTP, за которым следует номер версии), затем есть код состояния 200, за которым следует фраза причины (или описание кода состояния).

В этой новой версии заголовки запросов и ответов по-прежнему сохранялись в кодировке ASCII, но тело ответа могло быть любого типа, например изображения, видео, HTML, обычный текст или любой другой тип контента. Итак, теперь этот сервер может отправлять клиенту любой тип контента; вскоре после введения версии 1.0 термин "гипертекст" в HTTP стал неправильным. HMTP (Hypermedia transfer protocol) - протокол передачи гипермедиа, возможно, имел бы больше смысла, но, похоже, мы застряли с устоявшимся названием навсегда.

Одним из основных недостатков HTTP/1.0 было то, что нельзя отправлять несколько запросов во время одного соединения. То есть всякий раз, когда клиенту что-то понадобится от сервера, он должен открыть новое TCP-соединение, и после выполнения одного маленького запроса соединение закрывалось. И для выполнения любого следующего запроса необходимо было новое соединение. Почему это плохо? Что ж, давайте предположим, что вы посещаете веб-страницу, содержащую 10 изображений, 5 таблиц стилей и 5 файлов javascript, в общей сложности 20 элементов, которые необходимо извлечь при выполнении запроса на эту веб-страницу. Поскольку сервер закрывает соединение, как только запрос выполнен, каждый раз состоится серия из 20 отдельных подключений, где каждый из элементов будет обрабатываться по очереди. Такое количество подключений приводит к серьезному снижению производительности, поскольку установление каждого нового TCP-соединения требует еще одного медленного трехэтапного рукопожатия.

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

  • SYN (SYNchronize) - Клиент выбирает случайное число, скажем, x, и отправляет его на сервер.
  • SYN ACK (SYNchronize-ACKnowledgement) - Сервер подтверждает запрос, отправляя пакет ACK обратно клиенту, который состоит из случайного числа, выбранного сервером, например, y, и числа x+1, где x - это число, отправленное клиентом.
  • ACK (ACKnowledge) - Клиент увеличивает число y, полученное от сервера, и отправляет обратно пакет ACK с номером y+1.

Как только трехэтапное рукопожатие завершено, может начаться обмен данными между клиентом и сервером. Следует отметить, что клиент может начать отправку данных приложения, как только он отправит последний пакет ACK, но серверу все равно придется ждать получения пакета ACK, чтобы выполнить запрос.

Трехэтапное рукопожатие
Трехэтапное рукопожатие

Некоторые реализации HTTP/1.0 пытались решить эту проблему, введя новый заголовок Connection: keep-alive, который должен был сообщать серверу: "Эй, сервер, не закрывайте это соединение, оно мне снова нужно". Но это не получило широкой поддержки, и проблема все еще сохранялась.

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

HTTP/1.1 - 1999

Всего через 3 года после HTTP/1.0 в 1999 году была выпущена следующая версия, то есть HTTP/1.1, которая внесла много улучшений по сравнению с предшественником. Основные улучшения по сравнению с HTTP/1.0 :

  • Добавлены новые методы PUT, PATCH, OPTIONS, DELETE;
  • Идентификация Имени хоста. Заголовок хоста стал обязательным (ранее не требовался);
  • Непрерывное соединение. Как обсуждалось выше, в HTTP/1.0 был только один запрос на соединение, и соединение закрывалось, как только запрос был выполнен, что привело к снижению производительности и задержкам. HTTP/1.1 ввел непрерывные соединения, т.е. соединения не закрываются по умолчанию и остаются открытыми, что позволяет выполнять несколько последовательных запросов. Чтобы закрыть соединения, в запросе доступен заголовок Connection: close. Клиенты обычно отправляют этот его в последнем запросе, чтобы безопасно закрыть соединение;
  • Конвейерная обработка (Pipelining). Также была введена поддержка конвейерной обработки, при которой клиент может отправлять несколько запросов на сервер, не дожидаясь ответа от сервера по одному и тому же соединению, и сервер отправляет ответ в той же последовательности, в которой были получены запросы. Но как клиент узнает, когда загрузка первого ответа завершается и начинается следующий, спросите вы! Что ж, для решения этой проблемы, в ответе существует заголовок Content-Length, который клиенты могут использовать для определения, где заканчивается один ответ, чтобы начать ожидание следующего.

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

Но с этим подходом все еще существовала проблема. Что делать, если данные динамические, и сервер не может заранее определить длину содержимого? Что ж, в таком случае вы действительно не можете извлечь выгоду из постоянных подключений, не так ли?! Чтобы решить эту проблему, HTTP/1.1 ввел фрагментированную кодировку. В таких случаях сервер может опустить длину содержимого в пользу фрагментированной кодировки (подробнее об этом чуть позже). Однако, если ни один из них не доступен, то соединение должно быть закрыто в конце запроса.

  • Фрагментированная передача. В случае динамического контента, когда сервер не может точно определить его длину при начале передачи, он может начать отправлять содержимое по частям (фрагмент за фрагментом) и добавлять длину содержимого для каждого фрагмента при его отправке. И когда все фрагменты отправлены, т.е. вся передача завершена, он отправляет пустой фрагмент, т.е. тот, у которого длина содержимого установлена равной нулю, чтобы идентифицировать клиента, который завершил передачу. Для того, чтобы уведомить клиента о фрагментированной передаче, сервер включает заголовок Transfer-Encoding: chunked;
  • Дайджест-аутентификация. Логин, пароль, адреса сервера и случайные данные обрабатываются хеш-функцией и отправляются по сети в виде хеш-суммы, предоставляя таким образом больший уровень защиты, чем базовая аутентификация в HTTP/1.0, при которой данные отправляются в открытом виде).
  • Прокси-аутентификация. Прокси-сервер — это дополнительное звено между вами и интернетом. Некий посредник, который отделяет человека от посещаемого сайта. Создает условия, при которых сайт думает, что прокси — это и есть реальный человек, только не вы. С прокси-сервером сайты не знают, кто именно их посещает. Базовые атаки будут направлены именно на прокси. Возможно получать доступ к контенту, который существует только в определенной локации. Возможно ускорить доступ к некоторым ресурсам в интернете. Возможно получить доступ к заблокированным страницам: сайтам, мессенджерам и т.д.
  • Кэширование
  • Диапазоны байтов
  • Наборы символов
  • Согласование языка
  • Клиентские файлы cookie
  • Улучшенная поддержка сжатия
  • Новые коды статуса

...и многое другое

Не будем подробно останавливаться на всех функциях HTTP/1.1 в этой статье, поскольку каждая из них могла бы стать самостоятельной темой.

HTTP/1.1 введен в 1999 году и был стандартом в течение многих лет. Хотя он значительно улучшился по сравнению со своим предшественником, с течением времени он все-таки стал устаревать, поскольку веб меняется каждый день. Загрузка веб-страницы в наши дни требует больше ресурсов, чем когда-либо. Простая веб-страница должна открывать более 30 подключений. Но ведь у HTTP/1.1 непрерывные соединения, зачем их нужно так много?! Действительно! Причина в том, что в HTTP/1.1 может быть только одно незавершенное соединение в каждый момент времени. HTTP/1.1 попытался исправить это, введя конвейерную обработку, но это не полностью решило проблему из-за застревания в начале очереди запросов. Медленный или тяжелый запрос может блокировать последующие, и как только запрос застревает в конвейере, ему придется ждать выполнения следующих запросов. Чтобы исправить эти недостатки HTTP/1.1, разработчики начали внедрять обходные пути: использование таблиц спрайтов, закодированных изображений в CSS, огромных объединенных файлов CSS/Javascript, шардинг доменов (метод ускорения загрузки страниц, заставляющий браузеры открывать больше одновременных подключений, чем обычно разрешено) и т.п.