3.6 Как работают клиенты с серверами

В предыдущем параграфе вы узнали, как работает интернет и что он состоит из клиентов и серверов.

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

  • Как устроено взаимодействие клиентов и серверов.
  • Что такое API и зачем они нужны программистам.
  • Какие бывают API.

Приступим!

Что произошло, когда вы открыли эту страницу

Ваш браузер — клиент — самостоятельно не умеет подключаться к серверу: для этого нужно «железо» (например, сетевая карта или Wi-Fi-модуль) и операционная система (ОС).

2.7

Поэтому браузер обращается к ОС и говорит: «Нужно получить данные, которые лежат по адресу https://education.yandex.ru/handbook. Дай, пожалуйста, IP».

Операционная система отправляет запрос к DNS-серверу — чаще всего интернет-провайдера, — чтобы узнать IP-адрес сайта. Если он неизвестен, она отправляет запросы к более «высоким» DNS-серверам, пока наконец не получит ответ.

ОС получила IP. Она передаёт его браузеру. Браузер снова просит её: «Подключись к серверу на этом IP, порт 443, протокол HTTPS». HTTPS — более защищённая версия HTTP.

Но ОС не может просто так заявиться к серверу и сказать: «А ну-ка дай мне данные!» — как и мы не можем написать в ЗАГС «Дайте справку». Надо составить обращение по установленной форме — запрос. Как мы уже знаем, форму обращения определяет протокол HTTP. Как доставить данные — протокол TCP.

ОС формулирует запрос к серверу и с помощью сетевого стека разбивает его на пакеты — пронумерованную последовательность битов. Затем она с помощью сетевой карты обращается к ОС сервера и предлагает установить соединение. Для этого они обмениваются тремя сообщениями:

— Привет! Хочу получить данные, буду отправлять запрос. Номер моего первого пакета — 100, размер — 10 байт. Порт — 443, протокол — HTTPS.

— Привет! Номер записал. Номер моего ответного пакета будет 500. Устанавливаем соединение?

— Номер записал. Подтверждаю, устанавливаем.

Этот процесс называется TCP Handshake. Три сообщения (SYN, SYN-ACK, ACK) позволяют синхронизировать взаимные ожидания и установить надёжную связь, чтобы в случае сбоя при отправке-получении пакета не нужно было начинать процесс обмена данным с самого начала.

Продолжим. Мы используем протокол HTTPS. Это значит, что сервер и клиент не могут просто так начать обмениваться данными. Поэтому клиент говорит:

— Чуть не забыл. Хочу проверить, что вы точно не мошенник. Вот перечень шифров и случайное число. Выбирайте.

— Нет, что вы, ну какой мошенник! Выбираю такой-то шифр и вот моё случайное число. И кстати, у меня ещё сертификаты есть!
Клиент изучает сертификат: не просрочен ли, кем выдан, соответствует ли домену и так далее. И продолжает:

— Да, всё в порядке. С помощью шифра и наших чисел я кое-что зашифровал. Расшифруйте их и скажите, что у вас получилось.
Расшифровывает и называет.

— Всё верно! Давайте обменяемся секретными ключами.

— По рукам.

Этот процесс называется TLS Handshake (Transport Layer Security), и он гарантирует, что злоумышленники не смогут перехватить ваши данные и от вашего имени обратиться к серверу, например, чтобы перевести деньги на свой счёт. Поэтому HTTPS-протокол безопаснее, чем HTTP.

Продолжим. Соединение установлено, ваша сетевая карта начинает отправлять пакеты, сетевая карта севера получает их. Операционная система сервера с помощью сетевого стека распаковывает их и собирает вместе. Передаёт их в программу-сервер.

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

Поскольку мы запрашиваем данные, это будет GET-запрос. В ответ на GET-запрос сервер N отвечает данными, если они у него есть, конечно. Или ошибкой, если данных нет. Но раз вы читаете эту страницу, всё в порядке!

Далее веб-сервер обращается к ОС сервера: «Хочу ответить на запрос. Вот данные!» ОС разбивает их на пакеты и через сетевую карту отправляет клиенту.

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

Браузер получил только HTML — описание, из каких элементов состоит страница. Он сканирует это и смотрит, нет ли там ссылок. Видит ссылку на CSS-документ (описание, как должны выглядеть элементы на странице).

Браузеру нужно получить эти данные. Он снова просит ОС сделать запрос… Да, шаги выше повторяются. Да, для каждого вида данных: картинок, JS-кода, шрифтов и так далее.

Затем браузер отрисовывает страницу. Как это происходит в деталях, рассказывать не будем, иначе закопаемся. Коротко отметим, что это происходит в несколько этапов:

  1. Загрузка данных (это мы выполнили).
  2. Преобразование HTML и CSS для удобной работы с ними.
  3. Расчёт размеров, позиции и взаиморасположения элементов на экране в зависимости от его размеров.
  4. Стилизация элементов: применение цветов, теней, анимаций.
  5. Смешивание слоёв.
А в деталях?

Целиком — от получения HTML и CSS и до того, как страница отобразится на экране, — процесс называется Critical Rendering Path. Он включает парсинг, стилизацию, рендер и отрисовку.

Подробнее обо всех нюансах и этапах можно прочитать по ссылке.

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

И когда сервер ответит — браузер заново выполнит шаги 3, 4 и 5, чтобы актуализировать информацию.

В идеальных условиях: небольшой сайт, высокая скорость соединения, мощное железо на клиенте и сервере — все процессы займут от 1 до 3 секунд. Просто невероятно быстро, учитывая, сколько действий потребовалось совершить! И это мы их очень схематично описали, потому что под фразой «исполняется JavaScript-код» скрывается ещё несколько десятков операций.

И ещё несколько сотен, если добавить взаимодействие браузера, дисплея и видеокарты.

Очень важная ремарка

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

Браузер ищет сохранённые CSS, картинки, JS.

ОС может использовать DNS-кэш, чтобы не запрашивать IP-адрес заново.

Это нужно, чтобы не тратить время на вычисления того, что уже было вычислено. То есть чтобы сайт открывался как можно быстрее и вы не грустили. Потому что иначе провести все вот эти вот сложные вычисления и запросы и уложиться в 1–3 секунды нереально.

Поэтому телефоны иногда приходится очищать от устаревших кэшированных данных — это и называют «мусором».

Если подытожить: есть заранее согласованные правила, по которым общаются клиент и сервер. Эти правила могут сильно различаться в зависимости от специфики конкретного приложения.

API

API расшифровывается как Application Programming Interface — интерфейс программирования приложения. Если на пальцах: это правила и инструменты, с помощью которых одна программа может общаться с другой.

В примере выше API — все возможные URL, по которым браузер (клиент) может общаться с сервером.

Запросы на одни URL возвращают HTML-разметку, на другие — только данные. Вы это могли увидеть выше, когда JavaScript попросил браузер сбегать за данными о том, просмотрен параграф или нет.

Браузер мог отправить запрос на https://education.yandex.ru/handbook/is-paragraph-done. Только в ответ он получит не HTML и прочий контент, а true, если параграф прочитан, и false — если нет. Такой адрес бесполезно открывать в браузере: вы ничего не увидите.

Поведение, как реагировать на запрос клиента, определяет бэкенд-разработчик, который программировал веб-сервер. Собственно, он и решает, какие URL будут возвращать HTML, а какие — работать с данными.

Мы не случайно говорим «работать», потому что клиент может не только запрашивать данные, но и модифицировать их.

Для организации API часто используется подход под названием REST — передача репрезентативного состояния. REST добавляет разные типы команд, которые могут быть вызваны.

  • GET — запрос данных.
  • POST — отправка новых данных.
  • PUT — изменение данных.
  • DELETE — удаление данных.

Предположим, у нас есть ещё один клиент, который работает с сайтом хендбуков. Это компьютер контент-менеджера Ильи, который заходит в секретный раздел сайта — админ-панель. С её помощью он загружает новые параграфы, редактирует существующие и удаляет ненужные.

Илье надо опубликовать в хендбуке новый параграф в третьей главе. Он откроет админку, перенесёт текст, сделает другие необходимые действия и нажмёт кнопку «Опубликовать». И на веб-сервер отправится запрос.

POST /computer-science-handbook/3/6

Веб-сервер примет его, изучит, корректные ли данные переданы, выберет нужные (например, текст параграфа) и отправит запрос в базу данных: «Найди хендбук с ID = computer-science-handbook, в нём — главу с ID = 3, создай в ней параграф с ID = 6 и сохрани в нём текст».

А если данные некорректны?

Тогда бэкенд вернёт клиенту ошибку. А ещё он вернёт ошибку, если, например, база данных не сможет найти такой хендбук, или главу, или параграф.

Задача программиста, который писал веб-сервер, — продумать все возможные сценарии и настроить отправку корректных ошибок. А задача программиста, который писал админку для Ильи, — настроить приём ошибок и выдачу их в человекочитаемом формате.

Если это были хорошие программисты, то Илья увидит всплывающее уведомление: «Невозможно создать параграф: хендбук computer-science-handbook не существует». Или: «Невозможно создать параграф: главы 3 не существует».

А если он увидит просто текст Error или просто белый экран — значит, кто-то из программистов поленился!

Пройдёт пара дней. Внимательный читатель заметит какую-то ошибку в тексте, и Илье придёт уведомление. Он снова откроет админку, внесёт изменения и снова нажмёт «Опубликовать». И тогда на веб-сервер отправится запрос:

PUT /computer-science-handbook/3/6

Веб-сервер сделает всё то же самое, что и выше, только отправит в базу данных другой запрос: «Найди хендбук с ID = computer-science-handbook, в нём — главу с ID = 3, в ней — параграф с ID = 6 и обнови в нём текст».

А как браузер поймёт, когда отправлять PUT, а когда POST?

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

Например, перед отправкой запроса о публикации параграфа будет код, в котором сначала отправится запрос:

GET /computer-science-handbook/3/6

И дальше решение будет приниматься от результата. Если запрос успешный, значит, параграф уже есть и нужно отправить PUT, чтобы его изменить. Если ошибочный — нужно отправлять POST.

Пройдёт время, глава станет неактуальной. Тогда Илья по традиции откроет админку и нажмёт кнопку «Удалить». На веб-сервер отправится запрос:

DELETE /computer-science-handbook/3/6

Ну а что будет дальше — вы уже и так знаете.

Сейчас коротко остановимся и проговорим одну важную вещь:

API позволяет работать с клиентами разных типов.

Например, Илья обновляет параграф через админку на веб-сайте. Но какой-нибудь заботливый программист может написать приложение для Android, которое будет работать с API хендбуков: отправлять запросы на те же URL. Другой программист напишет программу для Windows или macOS.

Как они будут устроены, нам неважно, — это детали реализации. Важно, что они будут работать, потому что эти приложения (клиенты) всего лишь отправляют HTTP-запросы по специальным URL, которые определяются API.

API-протоколы

2.7

Выше мы рассмотрели, как организуется API c помощью протокола HTTP. Как вы понимаете, это не единственный способ. Далее коротко рассмотрим некоторые:

  • WebSocket
  • RPC
  • MQTT
  • Вебхуки

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

Вот, например, HTTP. Он самый простой, но не подходит для обмена данными в реальном времени, потому что работает в формате «запрос — ответ»: чтобы взаимодействие с сервером было в реальном времени, нужно каждые X секунд запрашивать данные и тут же публиковать свои.

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

Для таких целей умные люди придумали другой протокол — WebSocket.

WebSocket

Суть простая: клиент и сервер устанавливают непрерывную связь и обмениваются сообщениями.
Этот протокол подходит, когда приложению необходим постоянный обмен данными и требуется быстрая реакция. Самый подходящий пример — чаты. Клиент подключается к серверу и по этому каналу получает обновление: «Есть новое сообщение для тебя» — и отправляет туда же свои сообщения для другого клиента.

RPC

Он же «удалённый вызов процедуры», используется для более сложных ситуаций. Взаимодействует с разными серверами и выполняет команду так, как если бы она работала у клиента. Обычно это используется, когда вместе с командой надо передавать большой контекст, а также для ускорения.

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

MQTT

Работает по принципу «издатель и подписчик». Чаще всего его можно встретить в инфраструктуре «умного дома» для общения между устройствами.

Есть сервер, который называется «брокер». Сами устройства вроде датчиков и розеток являются «издателями», они отсылают данные о своём состоянии. «Брокер» собирает из них очереди и ждёт, когда к нему придут последние участники, «клиенты-подписчики», чтобы отдать им данные. Примером такого «подписчика» может быть система рисования графиков температуры в доме, она будет запрашивать у «брокера» данные о температуре.

Вебхуки

Или их ещё называют WebHooks. В этом случае клиент сначала отсылает серверу сообщение: «Я вот тут. Если что-то произойдёт, расскажи об этом» — и просто ждёт.

А когда нужное событие наступает, сервер присылает сообщение по указанному адресу. Например, так может быть организована работа ботов в чатах. Приложение бота подключено, но ничего не делает до тех пор, пока бота не упоминают. Сервер может прислать боту сообщение: «Эй, про тебя спрашивали», и тогда бот начнёт выполнять свои задачи.


Вот и всё! Мы разобрали основные концепции клиент-серверного взаимодействия и связали всё воедино: как они общаются между собой и за что отвечает браузер/приложение, а за что — операционная система и железо.

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



Чтобы добавить в заметки выделенный текст, нажмите Ctrl + E
Предыдущий параграф3.5. Что такое интернет
Следующий параграф3.7. Что вы узнали из этой главы