Когда браузер показывает «не удалось подключиться», виден только итог. Цена ошибки — изменить код приложения, не проверив, что запрос вообще не прошёл TLS, или принять ответ кэша за ответ origin-сервера. Для точной диагностики нужно восстановить цепочку по наблюдаемым границам, а не угадывать виновника по одному коду.
В этой статье разложим запрос на переходы и посмотрим, какие поля подтверждают каждый переход. Практический результат — короткая таблица: какой лог или команда отвечает на конкретный вопрос. Пример запускается локально на Node и показывает обмен HTTP-заголовками без обращения к внешнему узлу.
Пять границ одного запроса
DNS превращает имя в адрес, TCP устанавливает поток байтов, TLS защищает его и связывает с именем, HTTP передаёт метод и путь, а приложение формирует ответ. Посредник может добавить свой статус или заголовок на каждом шаге. Поэтому поле server в ответе не доказывает, что ответ сформирован именно вашим приложением.
| Граница | Что можно утверждать | Что проверить |
|---|---|---|
| DNS | имя разрешилось в адрес | ответ A/AAAA и выбранный адрес |
| TCP | порт принял соединение | connect error и время установки |
| TLS | сертификат подходит имени | SAN, chain, protocol version |
| HTTP | получен статус и заголовки | method, path, status, headers |
| Приложение | обработан контракт endpoint | лог маршрута и request id |
Заголовок не равен доказательству источника
Заголовок Via, Server или пользовательский X-Request-Id помогает построить гипотезу, но это данные сообщения, а не криптографическая аттестация сервера. Посредник может удалить или переписать поля. Для сопоставления запроса с серверным логом нужен идентификатор, который генерируется на входе доверенного компонента и сохраняется без смены формата.
У HTTP/2 и HTTP/3 целевой authority может передаваться не так, как привычная строка Host. Поэтому в диагностической записи сохраняйте логическое имя назначения и не делайте вывод о виртуальном хосте по одному полю. Сопоставляйте его с тем, что использовал TLS-клиент.
Удобная минимальная запись выглядит так: метод, нормализованный путь, статус, request id, длительность, размер ответа и класс ошибки. Секреты и произвольные query-параметры не входят в журнал по умолчанию. Нормализация важна: если один компонент пишет полный URL, а другой — только путь, поиск по записи будет давать ложные пропуски.
Учебный обмен с заголовками
Локальный сервер ниже возвращает два заголовка и JSON-тело. Вход — HTTP-запрос с Accept. Ожидаемый результат — 200, тип содержимого и один идентификатор. Это реальный обмен между клиентом и сервером, но он не проверяет TLS или поведение прокси.
import { createServer } from 'node:http';
const server = createServer((request, response) => {
response.writeHead(200, {
'content-type': 'application/json; charset=utf-8',
'x-request-id': 'local-001',
});
response.end(JSON.stringify({ method: request.method, path: request.url }));
});
server.listen(0, 'localhost', async () => {
const port = server.address().port;
const endpoint = 'http://localhost:' + port + '/orders';
const response = await fetch(endpoint);
console.log(response.status, response.headers.get('x-request-id'));
console.log(await response.json());
server.close();
});Результат содержит 200 local-001 и объект с методом GET и путём /orders. Если убрать заголовок из ответа, клиент всё равно получит 200: это показывает, что request id — средство сопоставления, а не условие корректности HTTP. В рабочей системе нужно договориться, какой компонент отвечает за его создание и где он попадает в лог.
TLS меняет порядок проверки
При HTTPS нельзя начинать с ответа приложения. Клиент сначала отправляет ClientHello, сервер выбирает параметры и предъявляет сертификат, затем стороны завершают рукопожатие. Проверка имени происходит относительно hostname, который клиент считает целевым. Если соединение идёт через proxy, отдельно фиксируйте имя proxy и имя origin: это две разные проверки.
Сертификат с правильной цепочкой, но неправильным SAN — ошибка имени. Сертификат с правильным SAN, который не доверен локальному хранилищу, — ошибка доверия. Сертификат с истёкшим сроком — ошибка времени. Эти причины нельзя объединять в «проблему SSL»: для каждой нужен собственный ожидаемый результат и свой владелец исправления.
Кэш и промежуточный ответ
HTTP допускает intermediaries (посредников), а RFC 9110 отдельно описывает кэширование и маршрутизацию. Ответ может быть свежим объектом кэша, перенаправлением или сообщением origin. Проверяйте Age, Cache-Control, ETag, Via и время изменения тела, но не делайте вывод о кэше по одному заголовку: политика конкретного посредника может быть сложнее.
| Поле | Пример | Риск при сборе |
|---|---|---|
| method | GET | малый, если путь обезличен |
| path | /orders | query может содержать секрет |
| status | 200 | не показывает причину сам по себе |
| durationMs | 42 | нужно знать часы и границы замера |
| requestId | local-001 | нельзя принимать внешний id без правила |
| responseSize | 31 | не заменяет проверку тела |
Порядок проверки по границам
- Зафиксировать имя, адрес и режим proxy отдельно; query-параметры очистить.
- Проверить DNS и TCP независимым клиентом, сохранив только код ошибки и длительность.
- Для HTTPS сверить hostname, SAN, цепочку и время действия сертификата.
- Снять HTTP-статус и выбранные заголовки, затем сопоставить request id с логом доверенного входа.
- Сравнить ответ с origin и кэшем, если между клиентом и приложением есть intermediary.
- Записать причину только на том уровне, который подтверждён наблюдением.
Ограничения и следующий шаг
Локальный обмен не показывает потери пакетов, балансировку, корпоративный proxy и особенности браузерного хранилища. Поле x-request-id в учебном сервере задано вручную; в настоящем сервисе его происхождение и доверенная зона должны быть описаны отдельно. Не смешивайте диагностическую метку с секретом или пользовательским идентификатором.
Следующий шаг — добавить к одной безопасной проверке два вывода: сетевой этап и серверный лог. Сначала докажите, что запрос достиг доверенного входа, потом связывайте его с handler. Такой порядок уменьшает область поиска и не заставляет приложение отвечать за сбой, произошедший раньше.
Проверяемые источники
- RFC 9110 — HTTP Semantics — IETF Standards Track, June 2022. Нужен для различения методов, статусов, заголовков, маршрутизации и ответа посредника. Граница применимости: Не объясняет конкретную конфигурацию прокси, DNS или причину ошибки в вашем сервисе.
- RFC 8446 — The Transport Layer Security (TLS) Protocol Version 1.3 — IETF Standards Track, August 2018. Нужен для разбора рукопожатия TLS 1.3, проверки имени узла и сообщения об ошибке сертификата. Граница применимости: Не подтверждает доверие к конкретному центру сертификации и не заменяет проверку ключевого материала.