Что происходит, когда вы вводите URL в адресную строку браузера и нажимаете Enter?

Ответ

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

  1. Поиск IP-адреса (DNS-запрос): Браузер должен преобразовать доменное имя (например, example.com) в IP-адрес. Поиск происходит по цепочке:

    • Кэш браузера
    • Кэш операционной системы (файл hosts)
    • Кэш роутера
    • DNS-сервер вашего интернет-провайдера (ISP)
    • Рекурсивный запрос к корневым DNS-серверам.
  2. Установка TCP-соединения: После получения IP-адреса браузер устанавливает TCP-соединение с сервером. Этот процесс известен как трехстороннее рукопожатие (3-way handshake):

    • SYN: Клиент отправляет пакет SYN (synchronize) на сервер.
    • SYN-ACK: Сервер отвечает пакетом SYN-ACK (synchronize-acknowledgment).
    • ACK: Клиент подтверждает получение пакетом ACK (acknowledgment). Соединение установлено.
  3. TLS-рукопожатие (если используется HTTPS): Для установки безопасного соединения поверх TCP происходит TLS-рукопожатие. Клиент и сервер договариваются о версии протокола, выбирают алгоритмы шифрования и обмениваются ключами для шифрования всего последующего трафика.

  4. Отправка HTTP-запроса: Браузер отправляет HTTP-запрос на сервер. Запрос включает:

    • Стартовую строку: метод (GET, POST), путь (/index.html), версия протокола (HTTP/1.1).
    • Заголовки (Headers): Host, User-Agent, Accept, Cookie и другие.
    • Тело запроса (Body): (опционально) данные формы, JSON и т.д., обычно для POST или PUT запросов.
  5. Обработка запроса на сервере: Сервер (например, написанный на Go) получает запрос и обрабатывает его:

    • Принимает соединение и парсит HTTP-запрос.
    • Маршрутизатор (роутер) направляет запрос соответствующему обработчику (handler).
    • Обработчик выполняет бизнес-логику: обращается к базе данных, другим сервисам, выполняет вычисления.
    • Генерируется HTTP-ответ.

    Пример простого сервера на Go:

    package main
    
    import (
        "fmt"
        "log"
        "net/http"
    )
    
    func handler(w http.ResponseWriter, r *http.Request) {
        log.Printf("Received request for: %sn", r.URL.Path)
        fmt.Fprintf(w, "Hello, World!")
    }
    
    func main() {
        http.HandleFunc("/", handler)
        log.Println("Starting server on :8080")
        if err := http.ListenAndServe(":8080", nil); err != nil {
            log.Fatal(err)
        }
    }
  6. Отправка HTTP-ответа: Сервер отправляет ответ браузеру. Ответ содержит:

    • Статус-код: 200 OK, 404 Not Found, 500 Internal Server Error и т.д.
    • Заголовки: Content-Type, Content-Length, Set-Cookie и т.д.
    • Тело ответа: HTML-страница, JSON, изображение и т.д.
  7. Рендеринг страницы браузером: Браузер получает ответ и:

    • Парсит HTML для построения DOM-дерева.
    • Парсит CSS для построения CSSOM-дерева.
    • Объединяет их для создания Render Tree.
    • Выполняет компоновку (Layout) и отрисовку (Paint) пикселей на экране.
    • Загружает и выполняет JavaScript-код.

Ответ 18+ 🔞

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

Первым делом, браузеру нужно понять, куда стучаться. Ты пишешь example.com, а сети нужны цифры, IP-адреса. Начинается великий поход за адресом, он же DNS-запрос. Браузер лезет по цепочке, как вор в квартиру, проверяя все тайники:

  1. Свой собственный кэш — «а не спрашивал ли я это недавно?».
  2. Кэш операционки (этот файл hosts, куда всякие вирусы любят гадить).
  3. Кэш роутера (который уже, блядь, месяц не перезагружали).
  4. DNS-сервер провайдера (который иногда такой кривой, что хуй с горы).
  5. И если и там нихуя, то запрос ползёт к корневым DNS-серверам, пока не найдёт того, кто знает, где живёт example.com. Всё, IP получили. Ура, можно идти дальше.

Дальше надо установить связь. Браузер стучится к серверу по TCP. Это как позвонить: «Алло, ты меня слышишь?». Называется трёхстороннее рукопожатие:

  • Браузер: «Эй, сервак, SYN!» (Давай синхронизируемся).
  • Сервер: «SYN-ACK, браток, я тут!» (Услышал, давай).
  • Браузер: «ACK, сука, поехали!» (Окей, связь есть).

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

Теперь, наконец-то, можно попросить то, зачем пришли. Браузер пакует HTTP-запрос и шлёт его. Внутри:

  • Стартовая строка: типа GET /index.html HTTP/1.1 (Метод, путь, протокол).
  • Заголовки: Куча служебной инфы — кто запрашивает (User-Agent), что понимает (Accept), куки, если есть.
  • Тело: Бывает, если отправляешь форму или JSON (например, при POST запросе).

Этот пакет летит по установленному безопасному каналу прямо в ебучку сервера.

На сервере начинается магия. Допустим, сервер написан на Go (отличный выбор, ёпта). Он ловит соединение, разворачивает запрос и смотрит: «А на какую, блядь, урлу пришло?». Маршрутизатор (роутер) ищет подходящий обработчик (handler) и передаёт ему управление. Тот уже делает что надо: лезет в базу данных, дергает другие сервисы, короче, выполняет бизнес-логику.

Вот, смотри, простейший сервак на Go, который просто отвечает «Hello, World!» на любой запрос:

package main

import (
    "fmt"
    "log"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    log.Printf("Received request for: %sn", r.URL.Path)
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    http.HandleFunc("/", handler)
    log.Println("Starting server on :8080")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

Обработчик сработал, ответ сгенерировал. Теперь сервер пакует HTTP-ответ и гонит его назад:

  • Статус-код: 200 OK (всё заебись), 404 (ни хуя не нашли), 500 (у сервера пиздец внутри).
  • Заголовки: Тип контента (Content-Type), размер, может, новые куки поставить.
  • Тело ответа: Самое главное — HTML, JSON, картинка, что угодно.

Ответ прилетает к браузеру. А вот тут начинается, блядь, самое интересное — рендеринг. Браузеру мало получить HTML, ему надо это всё нарисовать.

  1. Он парсит HTML и строит из тегов DOM-дерево.
  2. Парсит CSS и строит CSSOM-дерево (стили, блядь).
  3. Склеивает их в одно Render Tree (дерево отрисовки).
  4. Считает, где каждый элемент должен стоять (компоновка, или layout).
  5. И начинает красить пиксели на экране (отрисовка, или paint).
  6. Попутно, если в скриптах не написано какой-нибудь хуйни, грузит и выполняет JavaScript.

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