HTTP

Что за http в начале строки адреса страницы bednyakov.ru и других ресурсов?

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

HTTP работает по клиент-серверной модели. То есть во время взаимодействия существует две стороны: клиент –
запрашивает данные, сервер – отдает клиенту ответ на его запрос. Сейчас браузер играет роль клиента. Каждый раз, когда мы вводим в нем адрес сайта или кликаем по ссылке, происходит новый запрос к серверу, который формирует
HTML-страницу и возвращает ее браузеру, а он уже ее отображает. То же самое происходит и в мобильных приложениях. Все данные и логика большинства приложений находятся на сервере, а приложение просто обращается к нему, когда это требуется.

HTTP пронизывает всю разработку от бэка до фронта. Любой веб-разработчик пишет код, который
выполняет HTTP-запросы или, как минимум, формирует HTTP-ответы (простейшие сайты). Поэтому знание HTTP-протокола — это БАЗА.

Только зная HTTP вы будете делать крутые вещи:

  • Кеширование (техника ускорения вычислений и экономии ресурсов). Кеширование в HTTP очень сильно влияет на производительность web-приложений.
  • Отладка. Способность быстро находить источник проблемы и устранять его. Резко падает количество магии.
  • Производительность. Последние версии протокола позволяют очень эффективно использовать каналы связи.
  • Безопасность. Через сеть постоянно передаются секретные данные (пароли, ключи, личная информация). Держать их в секрете получится только зная как работает HTTP.
  • Дизайн (Архитектура). Правильное использование HTTP позволяет делать поведение сайтов и приложений (мобильных) гораздо более предсказуемым, устойчивым к ошибкам, простым в обслуживании.
  • Аутентификация. Способность сайтов запоминать пользователей тоже базируется на возможностях HTTP (cookies).

Для того чтобы посмотреть, как работает HTTP, мы сделаем запрос к серверу google и разберём, как он выглядит. Для этого используется специальная утилита, которая называется telnet. Наберите в терминале:

telnet google.com 80

Только что мы передали утилите адрес сайта и указали TCP порт. Далее происходит подключение к серверу по протоколу TCP, потому что HTTP – протокол прикладного уровня. Другими словами, он предназначен для общения между двумя программами (клиентом и сервером), находящимися на разных компьютерах. Но, сам по себе, HTTP не может соединять два удаленных компьютера. Для этого используются другие протоколы, среди которых TCP. Именно TCP позволяет соединить программы на удаленных компьютерах, создав канал для общения друг с другом. Для этого нужно знать два параметра: ip-адрес компьютера, к которому нужно подключиться, и порт, на котором «висит» нужная программа.

Команда telnet делает именно это, она выполняет соединение по TCP и только после этого входит в режим
взаимодействия по HTTP. При условии, что указан правильный ip-адрес и порт для соединения.

А откуда берется ip-адрес? Любой адрес сайта это просто имя, за которым скрывается ip-адрес. Имя задано для удобства, так его проще запомнить. Однако все сетевые программы, среди которых браузеры и telnet, выполняют преобразование имени сайта в его ip-адрес. Делается это с помощью системы DNS.

Почему порт имеет номер 80? Это общепринятое соглашение. Сайты, доступные по HTTP, доступны на порту 80, а по
HTTPS – на порту 443. Именно поэтому в браузерах порты не указываются, браузер подставляет их автоматически.

Вернемся к нашему соединению. Если оно прошло успешно, telnet выдаст строки:

Trying 142.250.74.46…
Connected to google.com.
Escape character is ‘^]’.

Теперь веб-сервер входит в режим ожидания HTTP-запроса. Осталось его послать. Причем сделать это нужно достаточно быстро, так как через несколько секунд, если запроса не будет, соединение будет автоматически закрыто и вы увидите строку «Connection closed by foreign host».

Запрос состоит из нескольких частей. Первая часть — request line. Вторая — заголовки.
В request line мы указываем специальное слово, ещё говорят глагол. В HTTP описаны разные глаголы, но мы сейчас не
будем вдаваться в подробности. Просто скажем, что они определяют, как реагировать на этот запрос. И в данном случае
мы будем использовать глагол HEAD. Он очень простой, и просит сервер отдать только заголовки, без содержимого.

Более распространённым является GET. Именно с помощью GET мы запрашиваем содержимое сайта.
После глагола указывается путь к ресурсу request URI. Если мы указываем /, это обозначает просто корень сайта. Дальше
всё, что нужно сделать, это указать название протокола и его версию. Сейчас мы рассматриваем только версии HTTP
1.0 и 1.1, это БАЗА. Между версиями есть принципиальные отличия, которые нужно хорошо знать и понимать. Версия 1.0, например, используется в различных целях утилитами командной строки. И больше об 1.0 знать ничего и не нужно. Пишем request line:

HEAD / HTTP/1.0

Что такое заголовки? Заголовки позволяют передавать дополнительную информацию, например браузеры
предоставляют информацию о себе, чтобы было понятно откуда идет запрос. Кроме этого они указывают какие форматы сжатия поддерживают, в каком формате готовы принимать ответ и так далее. Количество стандартных заголовков достаточно большое, помимо них можно добавлять любые свои.


Рассмотрим, как они выглядят. Мы указываем имя и через двоеточие какое-то значение: REFERER: value.
Заголовки часто указывают заглавными буквами, но регистр, на самом деле, не важен. Как и порядок заголовков. В каком бы порядке мы не передали заголовки, тело ответа будет разбираться только всё вместе.
Браузерами используется много заголовков, например user-agent. Этот заголовок используется для аналитики, а также,
когда необходимо адаптировать страницы сайта под разные экраны или браузеры. Но и без него наш запрос должен работать:

HEAD / HTTP/1.0
User-Agent: google сhrome

ВАЖНО!!! Поскольку это протокол, у него есть определенные правила, нарушать которые нельзя. HTTP — текстовый
протокол. Все правила основаны на простых соглашениях. Например, несколько заголовков отделяются друг от друга исключительно переводом строки. Мы не можем записать их в одну строку, через запятую или как-то ещё. Всё очень
строго.

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

Делаем запрос и изучаем ответ:

telnet google.com 80
Trying 142.250.74.46…
Connected to google.com.
Escape character is ‘^]’.
HEAD / HTTP/1.0

HTTP/1.0 200 OK
Content-Type: text/html; charset=ISO-8859-1
Content-Security-Policy-Report-Only: object-src ‘none’;base-uri ‘self’;script-src ‘nonce-lG8cNAoRHZjF0CbKj_Qa-w’ ‘strict-dynamic’ ‘report-sample’ ‘unsafe-eval’ ‘unsafe-inline’ https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp
P3P: CP=»This is not a P3P policy! See g.co/p3phelp for more info.»
Date: Fri, 09 Feb 2024 10:34:22 GMT
Server: gws
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Expires: Fri, 09 Feb 2024 10:34:22 GMT
Cache-Control: private
Set-Cookie: 1P_JAR=2024-02-09-10; expires=Sun, 10-Mar-2024 10:34:22 GMT; path=/; domain=.google.com; Secure
Set-Cookie: AEC=Ae3NU9PeqvGPxxSR0oZ5Rrx90W2zrRSuVninAbSWWojC_UNg6iU8mMQ9OQ; expires=Wed, 07-Aug-2024 10:34:22 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax
Set-Cookie: NID=511=F52dGEuNbhkLXufIEM17-KZw0EumNPIkyl98zRFr_d6d0AaNB3PFmz0GxHQgZSKLNTZXqZ5Zl3elDFRdjkBrK3a3wKfxerjjDv9dIeO5ncE9Ni-jtOMYrdUab5hcVdlanUIak4SCAlrV-S4N0o9Zh0hqWNoqyQ9EsvyfFvlYnec; expires=Sat, 10-Aug-2024 10:34:22 GMT; path=/; domain=.google.com; HttpOnly

Connection closed by foreign host.

Пришедший к нам response состоит из status line HTTP/1.0 200 OK. Это строка ответа, в которой указан протокол
(здесь он совпадает) и статус ответа 200 OK. В HTTP определено множество различных статусов (400, 500 и т.д.). Они
могут информировать, что информация была не найдена, были ошибки на сервере и т.д. Все статусы имеют
мнемоническое название, которое передается так же последним значением. 200 и OK обозначает, что всё прошло хорошо.

Далее выводится большое количество различных заголовков. В них нет ничего сложного, и их не нужно все учить (есть
какие-то общие, и они достаточно понятны). Все заголовки состоят из ключа, двоеточия и значения. Можно заметить, что есть вещи, связанные с кодировкой, кешированием. Некоторые заголовки специфичны для текущего сервера. Например, X-XSS-Protection: 0, где X указывает на то, что заголовок кастомный. Но никакой веб-сервер, никакой веб-браузер не будут ломаться при посылке таких дополнительных заголовков.

В HTTP 1.0 в конце после получения данных происходит закрытие соединения.
В конце мы видим строку «Connection closed by foreign host», запрос соединения был закрыт внешним
хостом. Так работает практически всё в интернете. Обычно сервера настроены на 30-секундный интервал и закрывают
соединение, если в течение этого интервала ничего не приходит. Новичкам с telnet работать тяжеловато, так как набрать запрос еще нужно успеть до закрытия соединения. Поэтому лучше делать записи в отдельном файле и после этого вставлять их в telnet.

В этой статье краем была затронута тема DNS (система доменных имен), которая помогает нам не запоминать цифры каждого устройства, к которому хочется подключиться, а использовать удобные названия типа bednyakov.ru. Она была разработана в 1984 году Полом Мокапетрисом для автоматизации процессов сопоставления IP-адресов и имен компьютеров, а также процессов обновления имен пользователей без необходимости вручную загружать файл с сайта. Эту тему желательно изучить самостоятельно, начиная с файла хостов. Для разработчика важны знания о принципах работы DNS-серверов и базовых записях ресурсов, благодаря которым можно ориентироваться в Интернете.

HTTP 1.1

Протокол HTTP 1.1 (ссылка кликабельна) расширяет возможности предыдущей версии и добавляет виртуальные хосты.

Предыдущая версия требует наличия только request line. В ней мы описываем, какой путь на сайте мы хотим посмотреть. Но упоминания сайта как такового здесь нет. При этом мы подключаемся по telnet к конкретному IP адресу. Следовательно можно сделать вывод, что понятие домена (доменного имени) при использовании HTTP 1.0 не важно. И так оно и есть, ведь эта версия была создана в те времена, когда считалось, что один IP адрес соответствует одному сайту. Но стремительный рост интернета показал, что долго так продолжаться не могло. И HTTP 1.1 ввел такое понятие как виртуальные хосты. С точки зрения реализации в протоколе HTTP, появилась одна небольшая деталь. Кроме request line стал обязательным ещё и заголовок, который называется host. Он определяет, какой именно домен должен быть возвращён с этого IP адреса.

HEAD / HTTP/1.1
host: bednyakov.ru

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

Сделаем GET-запрос с использованием протокола версии 1.1 и посмотрим, что он вернет:

telnet google.com 80
Trying 216.58.207.238...
Connected to google.com.
Escape character is '^]'.
GET / HTTP/1.1
host: google.com

HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/
Content-Type: text/html; charset=UTF-8
Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-nHW088iwNI1oyt0ofKIg-A' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp
Date: Fri, 09 Feb 2024 12:47:02 GMT
Expires: Sun, 10 Mar 2024 12:47:02 GMT
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 219
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>

Мы получили ответ, который отличается от 200. Здесь есть ещё одна интересная деталь, которая введена в HTTP 1.1, и на которую стоит обратить внимание. После выполнения запроса мы не выпали из telnet и не оказались в bash. Это значит, что подключение не было закрыто, и мы можем продолжить вводить данные.

Cделаем HEAD-запрос на тот же самый домен:

HEAD / HTTP/1.1
host: google.com

HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/
Content-Type: text/html; charset=UTF-8
Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-T9j0UWYEzZ4ZM6BtYb5fmg' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp
Date: Fri, 09 Feb 2024 12:51:13 GMT
Expires: Sun, 10 Mar 2024 12:51:13 GMT
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 219
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN

Мы снова получили ответ, но уже без body, поскольку использовали HEAD, а не GET-запрос.

HTTP 1.1 вводит ещё одно понятие по умолчанию, которое называется keep-alive. Означает, что соединение TCP,
по которому ходит HTTP, не закрывается (постоянное HTTP-соединение). Причём по умолчанию так должны себя вести все веб-сервера. Основная цель введения этой фичи в том, чтобы сократить использование ресурсов, уменьшить нагрузку на процессор, открывать меньше TCP-соединений (установка каждого TCP-соединения занимает время), уменьшить время ожидания (latency).

Когда мы открываем сайт, то обычно с одного домена грузится несколько ресурсов. keep-alive позволяет открывать и использовать одно соединение, которое не будет закрыто до тех пор, пока это не будет указано явно, либо не произойдет таймаут. Таймаут зависит от того, какой браузер и какой веб-сервер используется.
Мы также можем указать, что хотим закрыть соединение. Для этого после установки соединения и передачи стандартных заголовков нужно передать еще один заголовок. Он называется connection: close. Тогда keep-alive будет отключен, и после получения ответа мы увидим собщение, что хост закрыл соединение: Connection closed by foreign host.

Тело HTTP-запроса

HTTP запрос состоит из заголовков и опционального тела запроса. Для отделения заголовков от
тела существуют определенные правила. Давайте посмотрим на примере, как работать с body и каким образом посылать какие-то данные кроме заголовков. Сделаем HTTP запрос к хосту bednyakov.ru

telnet bednyakov.ru 80
Trying 95.163.238.76...
Connected to bednyakov.ru.
Escape character is '^]'.
GET / HTTP/1.1
Host: bednyakov.ru

HTTP/1.1 301 Moved Permanently
Server: ddos-guard
Connection: keep-alive
Keep-Alive: timeout=60
Set-Cookie: __ddg1_=X84MD2Rc7YC8yiX71J0b; Domain=.bednyakov.ru; HttpOnly; Path=/; Expires=Sat, 08-Feb-2025 13:09:50 GMT
Date: Fri, 09 Feb 2024 13:09:50 GMT
Content-Type: text/html
Location: https://bednyakov.ru:443/
Transfer-Encoding: chunked

A2
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx</center>
</body>
</html>

В ответ мы получаем какие-то заголовки и далее идёт тело. В данном случае это не какая-то страница нашего сайта, а просто страница которую отдаёт сервер. Тело сообщения на википедии.

Дополнительные материалы по теме: